blob: a3c24f4af5c6a629a7afe67f25e3948f68a47cd9 [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() {
Dirk Doughertyb87e3002014-11-18 19:34:34 -080023
24 // show lang dialog if the URL includes /intl/
25 //if (location.pathname.substring(0,6) == "/intl/") {
26 // var lang = location.pathname.split('/')[2];
27 // if (lang != getLangPref()) {
28 // $("#langMessage a.yes").attr("onclick","changeLangPref('" + lang
29 // + "', true); $('#langMessage').hide(); return false;");
30 // $("#langMessage .lang." + lang).show();
31 // $("#langMessage").show();
32 // }
33 //}
Scott Main7e447ed2013-02-19 17:22:37 -080034
Scott Main0e76e7e2013-03-12 10:24:07 -070035 // load json file for JD doc search suggestions
Scott Main719acb42013-12-05 16:05:09 -080036 $.getScript(toRoot + 'jd_lists_unified.js');
Scott Main7e447ed2013-02-19 17:22:37 -080037 // load json file for Android API search suggestions
38 $.getScript(toRoot + 'reference/lists.js');
39 // load json files for Google services API suggestions
Scott Main9f2971d2013-02-26 13:07:41 -080040 $.getScript(toRoot + 'reference/gcm_lists.js', function(data, textStatus, jqxhr) {
Scott Main7e447ed2013-02-19 17:22:37 -080041 // once the GCM json (GCM_DATA) is loaded, load the GMS json (GMS_DATA) and merge the data
42 if(jqxhr.status === 200) {
Scott Main9f2971d2013-02-26 13:07:41 -080043 $.getScript(toRoot + 'reference/gms_lists.js', function(data, textStatus, jqxhr) {
Scott Main7e447ed2013-02-19 17:22:37 -080044 if(jqxhr.status === 200) {
45 // combine GCM and GMS data
46 GOOGLE_DATA = GMS_DATA;
47 var start = GOOGLE_DATA.length;
48 for (var i=0; i<GCM_DATA.length; i++) {
49 GOOGLE_DATA.push({id:start+i, label:GCM_DATA[i].label,
50 link:GCM_DATA[i].link, type:GCM_DATA[i].type});
51 }
52 }
53 });
54 }
55 });
56
Scott Main0e76e7e2013-03-12 10:24:07 -070057 // setup keyboard listener for search shortcut
58 $('body').keyup(function(event) {
59 if (event.which == 191) {
60 $('#search_autocomplete').focus();
61 }
62 });
Scott Main015d6162013-01-29 09:01:52 -080063
Scott Maine4d8f1b2012-06-21 18:03:05 -070064 // init the fullscreen toggle click event
65 $('#nav-swap .fullscreen').click(function(){
66 if ($(this).hasClass('disabled')) {
67 toggleFullscreen(true);
68 } else {
69 toggleFullscreen(false);
70 }
71 });
Scott Main3b90aff2013-08-01 18:09:35 -070072
Scott Maine4d8f1b2012-06-21 18:03:05 -070073 // initialize the divs with custom scrollbars
74 $('.scroll-pane').jScrollPane( {verticalGutter:0} );
Scott Main3b90aff2013-08-01 18:09:35 -070075
Scott Maine4d8f1b2012-06-21 18:03:05 -070076 // add HRs below all H2s (except for a few other h2 variants)
Scott Mainc29b3f52014-05-30 21:18:30 -070077 $('h2').not('#qv h2')
78 .not('#tb h2')
79 .not('.sidebox h2')
80 .not('#devdoc-nav h2')
81 .not('h2.norule').css({marginBottom:0})
82 .after('<hr/>');
Scott Maine4d8f1b2012-06-21 18:03:05 -070083
84 // set up the search close button
85 $('.search .close').click(function() {
86 $searchInput = $('#search_autocomplete');
87 $searchInput.attr('value', '');
88 $(this).addClass("hide");
89 $("#search-container").removeClass('active');
90 $("#search_autocomplete").blur();
Scott Main0e76e7e2013-03-12 10:24:07 -070091 search_focus_changed($searchInput.get(), false);
92 hideResults();
Scott Maine4d8f1b2012-06-21 18:03:05 -070093 });
94
95 // Set up quicknav
Scott Main3b90aff2013-08-01 18:09:35 -070096 var quicknav_open = false;
Scott Maine4d8f1b2012-06-21 18:03:05 -070097 $("#btn-quicknav").click(function() {
98 if (quicknav_open) {
99 $(this).removeClass('active');
100 quicknav_open = false;
101 collapse();
102 } else {
103 $(this).addClass('active');
104 quicknav_open = true;
105 expand();
106 }
107 })
Scott Main3b90aff2013-08-01 18:09:35 -0700108
Scott Maine4d8f1b2012-06-21 18:03:05 -0700109 var expand = function() {
110 $('#header-wrap').addClass('quicknav');
111 $('#quicknav').stop().show().animate({opacity:'1'});
112 }
Scott Main3b90aff2013-08-01 18:09:35 -0700113
Scott Maine4d8f1b2012-06-21 18:03:05 -0700114 var collapse = function() {
115 $('#quicknav').stop().animate({opacity:'0'}, 100, function() {
116 $(this).hide();
117 $('#header-wrap').removeClass('quicknav');
118 });
119 }
Scott Main3b90aff2013-08-01 18:09:35 -0700120
121
Scott Maine4d8f1b2012-06-21 18:03:05 -0700122 //Set up search
123 $("#search_autocomplete").focus(function() {
124 $("#search-container").addClass('active');
125 })
126 $("#search-container").mouseover(function() {
127 $("#search-container").addClass('active');
128 $("#search_autocomplete").focus();
129 })
130 $("#search-container").mouseout(function() {
131 if ($("#search_autocomplete").is(":focus")) return;
132 if ($("#search_autocomplete").val() == '') {
133 setTimeout(function(){
134 $("#search-container").removeClass('active');
135 $("#search_autocomplete").blur();
136 },250);
137 }
138 })
139 $("#search_autocomplete").blur(function() {
140 if ($("#search_autocomplete").val() == '') {
141 $("#search-container").removeClass('active');
142 }
143 })
144
Scott Main3b90aff2013-08-01 18:09:35 -0700145
Scott Maine4d8f1b2012-06-21 18:03:05 -0700146 // prep nav expandos
147 var pagePath = document.location.pathname;
148 // account for intl docs by removing the intl/*/ path
149 if (pagePath.indexOf("/intl/") == 0) {
150 pagePath = pagePath.substr(pagePath.indexOf("/",6)); // start after intl/ to get last /
151 }
Scott Mainac2aef52013-02-12 14:15:23 -0800152
Scott Maine4d8f1b2012-06-21 18:03:05 -0700153 if (pagePath.indexOf(SITE_ROOT) == 0) {
154 if (pagePath == '' || pagePath.charAt(pagePath.length - 1) == '/') {
155 pagePath += 'index.html';
156 }
157 }
158
Scott Main01a25452013-02-12 17:32:27 -0800159 // Need a copy of the pagePath before it gets changed in the next block;
160 // it's needed to perform proper tab highlighting in offline docs (see rootDir below)
161 var pagePathOriginal = pagePath;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700162 if (SITE_ROOT.match(/\.\.\//) || SITE_ROOT == '') {
163 // If running locally, SITE_ROOT will be a relative path, so account for that by
164 // finding the relative URL to this page. This will allow us to find links on the page
165 // leading back to this page.
166 var pathParts = pagePath.split('/');
167 var relativePagePathParts = [];
168 var upDirs = (SITE_ROOT.match(/(\.\.\/)+/) || [''])[0].length / 3;
169 for (var i = 0; i < upDirs; i++) {
170 relativePagePathParts.push('..');
171 }
172 for (var i = 0; i < upDirs; i++) {
173 relativePagePathParts.push(pathParts[pathParts.length - (upDirs - i) - 1]);
174 }
175 relativePagePathParts.push(pathParts[pathParts.length - 1]);
176 pagePath = relativePagePathParts.join('/');
177 } else {
178 // Otherwise the page path is already an absolute URL
179 }
180
Scott Mainac2aef52013-02-12 14:15:23 -0800181 // Highlight the header tabs...
182 // highlight Design tab
183 if ($("body").hasClass("design")) {
184 $("#header li.design a").addClass("selected");
Dirk Doughertyc3921652014-05-13 16:55:26 -0700185 $("#sticky-header").addClass("design");
Scott Mainac2aef52013-02-12 14:15:23 -0800186
smain@google.com6040ffa2014-06-13 15:06:23 -0700187 // highlight About tabs
188 } else if ($("body").hasClass("about")) {
189 var rootDir = pagePathOriginal.substring(1,pagePathOriginal.indexOf('/', 1));
190 if (rootDir == "about") {
191 $("#nav-x li.about a").addClass("selected");
192 } else if (rootDir == "wear") {
193 $("#nav-x li.wear a").addClass("selected");
194 } else if (rootDir == "tv") {
195 $("#nav-x li.tv a").addClass("selected");
196 } else if (rootDir == "auto") {
197 $("#nav-x li.auto a").addClass("selected");
198 }
Scott Mainac2aef52013-02-12 14:15:23 -0800199 // highlight Develop tab
200 } else if ($("body").hasClass("develop") || $("body").hasClass("google")) {
201 $("#header li.develop a").addClass("selected");
Dirk Doughertyc3921652014-05-13 16:55:26 -0700202 $("#sticky-header").addClass("develop");
Scott Mainac2aef52013-02-12 14:15:23 -0800203 // In Develop docs, also highlight appropriate sub-tab
Scott Main01a25452013-02-12 17:32:27 -0800204 var rootDir = pagePathOriginal.substring(1,pagePathOriginal.indexOf('/', 1));
Scott Mainac2aef52013-02-12 14:15:23 -0800205 if (rootDir == "training") {
206 $("#nav-x li.training a").addClass("selected");
207 } else if (rootDir == "guide") {
208 $("#nav-x li.guide a").addClass("selected");
209 } else if (rootDir == "reference") {
210 // If the root is reference, but page is also part of Google Services, select Google
211 if ($("body").hasClass("google")) {
212 $("#nav-x li.google a").addClass("selected");
213 } else {
214 $("#nav-x li.reference a").addClass("selected");
215 }
216 } else if ((rootDir == "tools") || (rootDir == "sdk")) {
217 $("#nav-x li.tools a").addClass("selected");
218 } else if ($("body").hasClass("google")) {
219 $("#nav-x li.google a").addClass("selected");
Dirk Dougherty4f7e5152010-09-16 10:43:40 -0700220 } else if ($("body").hasClass("samples")) {
221 $("#nav-x li.samples a").addClass("selected");
Scott Mainac2aef52013-02-12 14:15:23 -0800222 }
223
224 // highlight Distribute tab
225 } else if ($("body").hasClass("distribute")) {
226 $("#header li.distribute a").addClass("selected");
Dirk Doughertyc3921652014-05-13 16:55:26 -0700227 $("#sticky-header").addClass("distribute");
228
229 var baseFrag = pagePathOriginal.indexOf('/', 1) + 1;
230 var secondFrag = pagePathOriginal.substring(baseFrag, pagePathOriginal.indexOf('/', baseFrag));
231 if (secondFrag == "users") {
232 $("#nav-x li.users a").addClass("selected");
233 } else if (secondFrag == "engage") {
234 $("#nav-x li.engage a").addClass("selected");
235 } else if (secondFrag == "monetize") {
236 $("#nav-x li.monetize a").addClass("selected");
237 } else if (secondFrag == "tools") {
238 $("#nav-x li.disttools a").addClass("selected");
239 } else if (secondFrag == "stories") {
240 $("#nav-x li.stories a").addClass("selected");
241 } else if (secondFrag == "essentials") {
242 $("#nav-x li.essentials a").addClass("selected");
243 } else if (secondFrag == "googleplay") {
244 $("#nav-x li.googleplay a").addClass("selected");
245 }
246 } else if ($("body").hasClass("about")) {
247 $("#sticky-header").addClass("about");
Scott Mainb16376f2014-05-21 20:35:47 -0700248 }
Scott Mainac2aef52013-02-12 14:15:23 -0800249
Scott Mainf6145542013-04-01 16:38:11 -0700250 // set global variable so we can highlight the sidenav a bit later (such as for google reference)
251 // and highlight the sidenav
252 mPagePath = pagePath;
253 highlightSidenav();
Dirk Doughertyc3921652014-05-13 16:55:26 -0700254 buildBreadcrumbs();
Scott Mainac2aef52013-02-12 14:15:23 -0800255
Scott Mainf6145542013-04-01 16:38:11 -0700256 // set up prev/next links if they exist
Scott Maine4d8f1b2012-06-21 18:03:05 -0700257 var $selNavLink = $('#nav').find('a[href="' + pagePath + '"]');
Scott Main5a1123e2012-09-26 12:51:28 -0700258 var $selListItem;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700259 if ($selNavLink.length) {
Scott Mainac2aef52013-02-12 14:15:23 -0800260 $selListItem = $selNavLink.closest('li');
Scott Maine4d8f1b2012-06-21 18:03:05 -0700261
262 // set up prev links
263 var $prevLink = [];
264 var $prevListItem = $selListItem.prev('li');
Scott Main3b90aff2013-08-01 18:09:35 -0700265
Scott Maine4d8f1b2012-06-21 18:03:05 -0700266 var crossBoundaries = ($("body.design").length > 0) || ($("body.guide").length > 0) ? true :
267false; // navigate across topic boundaries only in design docs
268 if ($prevListItem.length) {
smain@google.comc91ecb72014-06-23 10:22:23 -0700269 if ($prevListItem.hasClass('nav-section') || crossBoundaries) {
Scott Main5a1123e2012-09-26 12:51:28 -0700270 // jump to last topic of previous section
271 $prevLink = $prevListItem.find('a:last');
272 } else if (!$selListItem.hasClass('nav-section')) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700273 // jump to previous topic in this section
274 $prevLink = $prevListItem.find('a:eq(0)');
275 }
276 } else {
277 // jump to this section's index page (if it exists)
278 var $parentListItem = $selListItem.parents('li');
279 $prevLink = $selListItem.parents('li').find('a');
Scott Main3b90aff2013-08-01 18:09:35 -0700280
Scott Maine4d8f1b2012-06-21 18:03:05 -0700281 // except if cross boundaries aren't allowed, and we're at the top of a section already
282 // (and there's another parent)
Scott Main3b90aff2013-08-01 18:09:35 -0700283 if (!crossBoundaries && $parentListItem.hasClass('nav-section')
Scott Maine4d8f1b2012-06-21 18:03:05 -0700284 && $selListItem.hasClass('nav-section')) {
285 $prevLink = [];
286 }
287 }
288
Scott Maine4d8f1b2012-06-21 18:03:05 -0700289 // set up next links
290 var $nextLink = [];
Scott Maine4d8f1b2012-06-21 18:03:05 -0700291 var startClass = false;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700292 var isCrossingBoundary = false;
Scott Main3b90aff2013-08-01 18:09:35 -0700293
Scott Main1a00f7f2013-10-29 11:11:19 -0700294 if ($selListItem.hasClass('nav-section') && $selListItem.children('div.empty').length == 0) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700295 // we're on an index page, jump to the first topic
Scott Mainb505ca62012-07-26 18:00:14 -0700296 $nextLink = $selListItem.find('ul:eq(0)').find('a:eq(0)');
Scott Maine4d8f1b2012-06-21 18:03:05 -0700297
298 // if there aren't any children, go to the next section (required for About pages)
299 if($nextLink.length == 0) {
300 $nextLink = $selListItem.next('li').find('a');
Scott Mainb505ca62012-07-26 18:00:14 -0700301 } else if ($('.topic-start-link').length) {
302 // as long as there's a child link and there is a "topic start link" (we're on a landing)
303 // then set the landing page "start link" text to be the first doc title
304 $('.topic-start-link').text($nextLink.text().toUpperCase());
Scott Maine4d8f1b2012-06-21 18:03:05 -0700305 }
Scott Main3b90aff2013-08-01 18:09:35 -0700306
Scott Main5a1123e2012-09-26 12:51:28 -0700307 // If the selected page has a description, then it's a class or article homepage
308 if ($selListItem.find('a[description]').length) {
309 // this means we're on a class landing page
Scott Maine4d8f1b2012-06-21 18:03:05 -0700310 startClass = true;
311 }
312 } else {
313 // jump to the next topic in this section (if it exists)
314 $nextLink = $selListItem.next('li').find('a:eq(0)');
Scott Main1a00f7f2013-10-29 11:11:19 -0700315 if ($nextLink.length == 0) {
Scott Main5a1123e2012-09-26 12:51:28 -0700316 isCrossingBoundary = true;
317 // no more topics in this section, jump to the first topic in the next section
smain@google.comabf34112014-06-23 11:39:02 -0700318 $nextLink = $selListItem.parents('li:eq(0)').next('li').find('a:eq(0)');
Scott Main5a1123e2012-09-26 12:51:28 -0700319 if (!$nextLink.length) { // Go up another layer to look for next page (lesson > class > course)
320 $nextLink = $selListItem.parents('li:eq(1)').next('li.nav-section').find('a:eq(0)');
Scott Main1a00f7f2013-10-29 11:11:19 -0700321 if ($nextLink.length == 0) {
322 // if that doesn't work, we're at the end of the list, so disable NEXT link
323 $('.next-page-link').attr('href','').addClass("disabled")
324 .click(function() { return false; });
smain@google.comc91ecb72014-06-23 10:22:23 -0700325 // and completely hide the one in the footer
326 $('.content-footer .next-page-link').hide();
Scott Main1a00f7f2013-10-29 11:11:19 -0700327 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700328 }
329 }
330 }
Scott Main5a1123e2012-09-26 12:51:28 -0700331
332 if (startClass) {
333 $('.start-class-link').attr('href', $nextLink.attr('href')).removeClass("hide");
334
Scott Main3b90aff2013-08-01 18:09:35 -0700335 // if there's no training bar (below the start button),
Scott Main5a1123e2012-09-26 12:51:28 -0700336 // then we need to add a bottom border to button
337 if (!$("#tb").length) {
338 $('.start-class-link').css({'border-bottom':'1px solid #DADADA'});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700339 }
Scott Main5a1123e2012-09-26 12:51:28 -0700340 } else if (isCrossingBoundary && !$('body.design').length) { // Design always crosses boundaries
341 $('.content-footer.next-class').show();
342 $('.next-page-link').attr('href','')
343 .removeClass("hide").addClass("disabled")
344 .click(function() { return false; });
smain@google.comc91ecb72014-06-23 10:22:23 -0700345 // and completely hide the one in the footer
346 $('.content-footer .next-page-link').hide();
Scott Main1a00f7f2013-10-29 11:11:19 -0700347 if ($nextLink.length) {
348 $('.next-class-link').attr('href',$nextLink.attr('href'))
smain@google.com5bc3a1a2014-06-17 20:02:53 -0700349 .removeClass("hide")
350 .append(": " + $nextLink.html());
Scott Main1a00f7f2013-10-29 11:11:19 -0700351 $('.next-class-link').find('.new').empty();
352 }
Scott Main5a1123e2012-09-26 12:51:28 -0700353 } else {
smain@google.com5bc3a1a2014-06-17 20:02:53 -0700354 $('.next-page-link').attr('href', $nextLink.attr('href'))
355 .removeClass("hide");
356 // for the footer link, also add the next page title
357 $('.content-footer .next-page-link').append(": " + $nextLink.html());
Scott Main5a1123e2012-09-26 12:51:28 -0700358 }
359
360 if (!startClass && $prevLink.length) {
361 var prevHref = $prevLink.attr('href');
362 if (prevHref == SITE_ROOT + 'index.html') {
363 // Don't show Previous when it leads to the homepage
364 } else {
365 $('.prev-page-link').attr('href', $prevLink.attr('href')).removeClass("hide");
366 }
Scott Main3b90aff2013-08-01 18:09:35 -0700367 }
Scott Main5a1123e2012-09-26 12:51:28 -0700368
Scott Maine4d8f1b2012-06-21 18:03:05 -0700369 }
Scott Main3b90aff2013-08-01 18:09:35 -0700370
371
372
Scott Main5a1123e2012-09-26 12:51:28 -0700373 // Set up the course landing pages for Training with class names and descriptions
374 if ($('body.trainingcourse').length) {
375 var $classLinks = $selListItem.find('ul li a').not('#nav .nav-section .nav-section ul a');
Scott Maine7d75352014-05-22 15:50:56 -0700376
377 // create an array for all the class descriptions
378 var $classDescriptions = new Array($classLinks.length);
379 var lang = getLangPref();
380 $classLinks.each(function(index) {
381 var langDescr = $(this).attr(lang + "-description");
382 if (typeof langDescr !== 'undefined' && langDescr !== false) {
383 // if there's a class description in the selected language, use that
384 $classDescriptions[index] = langDescr;
385 } else {
386 // otherwise, use the default english description
387 $classDescriptions[index] = $(this).attr("description");
388 }
389 });
Scott Main3b90aff2013-08-01 18:09:35 -0700390
Scott Main5a1123e2012-09-26 12:51:28 -0700391 var $olClasses = $('<ol class="class-list"></ol>');
392 var $liClass;
393 var $imgIcon;
394 var $h2Title;
395 var $pSummary;
396 var $olLessons;
397 var $liLesson;
398 $classLinks.each(function(index) {
399 $liClass = $('<li></li>');
400 $h2Title = $('<a class="title" href="'+$(this).attr('href')+'"><h2>' + $(this).html()+'</h2><span></span></a>');
Scott Maine7d75352014-05-22 15:50:56 -0700401 $pSummary = $('<p class="description">' + $classDescriptions[index] + '</p>');
Scott Main3b90aff2013-08-01 18:09:35 -0700402
Scott Main5a1123e2012-09-26 12:51:28 -0700403 $olLessons = $('<ol class="lesson-list"></ol>');
Scott Main3b90aff2013-08-01 18:09:35 -0700404
Scott Main5a1123e2012-09-26 12:51:28 -0700405 $lessons = $(this).closest('li').find('ul li a');
Scott Main3b90aff2013-08-01 18:09:35 -0700406
Scott Main5a1123e2012-09-26 12:51:28 -0700407 if ($lessons.length) {
Scott Main3b90aff2013-08-01 18:09:35 -0700408 $imgIcon = $('<img src="'+toRoot+'assets/images/resource-tutorial.png" '
409 + ' width="64" height="64" alt=""/>');
Scott Main5a1123e2012-09-26 12:51:28 -0700410 $lessons.each(function(index) {
411 $olLessons.append('<li><a href="'+$(this).attr('href')+'">' + $(this).html()+'</a></li>');
412 });
413 } else {
Scott Main3b90aff2013-08-01 18:09:35 -0700414 $imgIcon = $('<img src="'+toRoot+'assets/images/resource-article.png" '
415 + ' width="64" height="64" alt=""/>');
Scott Main5a1123e2012-09-26 12:51:28 -0700416 $pSummary.addClass('article');
417 }
418
419 $liClass.append($h2Title).append($imgIcon).append($pSummary).append($olLessons);
420 $olClasses.append($liClass);
421 });
422 $('.jd-descr').append($olClasses);
423 }
424
Scott Maine4d8f1b2012-06-21 18:03:05 -0700425 // Set up expand/collapse behavior
Scott Mainad08f072013-08-20 16:49:57 -0700426 initExpandableNavItems("#nav");
Scott Main3b90aff2013-08-01 18:09:35 -0700427
Scott Main3b90aff2013-08-01 18:09:35 -0700428
Scott Maine4d8f1b2012-06-21 18:03:05 -0700429 $(".scroll-pane").scroll(function(event) {
430 event.preventDefault();
431 return false;
432 });
433
434 /* Resize nav height when window height changes */
435 $(window).resize(function() {
436 if ($('#side-nav').length == 0) return;
437 var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
438 setNavBarLeftPos(); // do this even if sidenav isn't fixed because it could become fixed
439 // make sidenav behave when resizing the window and side-scolling is a concern
Scott Mainf5257812014-05-22 17:26:38 -0700440 if (sticky) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700441 if ((stylesheet.attr("disabled") == "disabled") || stylesheet.length == 0) {
442 updateSideNavPosition();
443 } else {
444 updateSidenavFullscreenWidth();
445 }
446 }
447 resizeNav();
448 });
449
450
Scott Maine4d8f1b2012-06-21 18:03:05 -0700451 var navBarLeftPos;
452 if ($('#devdoc-nav').length) {
453 setNavBarLeftPos();
454 }
455
456
Scott Maine4d8f1b2012-06-21 18:03:05 -0700457 // Set up play-on-hover <video> tags.
458 $('video.play-on-hover').bind('click', function(){
459 $(this).get(0).load(); // in case the video isn't seekable
460 $(this).get(0).play();
461 });
462
463 // Set up tooltips
464 var TOOLTIP_MARGIN = 10;
Scott Maindb3678b2012-10-23 14:13:41 -0700465 $('acronym,.tooltip-link').each(function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700466 var $target = $(this);
467 var $tooltip = $('<div>')
468 .addClass('tooltip-box')
Scott Maindb3678b2012-10-23 14:13:41 -0700469 .append($target.attr('title'))
Scott Maine4d8f1b2012-06-21 18:03:05 -0700470 .hide()
471 .appendTo('body');
472 $target.removeAttr('title');
473
474 $target.hover(function() {
475 // in
476 var targetRect = $target.offset();
477 targetRect.width = $target.width();
478 targetRect.height = $target.height();
479
480 $tooltip.css({
481 left: targetRect.left,
482 top: targetRect.top + targetRect.height + TOOLTIP_MARGIN
483 });
484 $tooltip.addClass('below');
485 $tooltip.show();
486 }, function() {
487 // out
488 $tooltip.hide();
489 });
490 });
491
492 // Set up <h2> deeplinks
493 $('h2').click(function() {
494 var id = $(this).attr('id');
495 if (id) {
496 document.location.hash = id;
497 }
498 });
499
500 //Loads the +1 button
501 var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
502 po.src = 'https://apis.google.com/js/plusone.js';
503 var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
504
505
Scott Main3b90aff2013-08-01 18:09:35 -0700506 // Revise the sidenav widths to make room for the scrollbar
Scott Maine4d8f1b2012-06-21 18:03:05 -0700507 // which avoids the visible width from changing each time the bar appears
508 var $sidenav = $("#side-nav");
509 var sidenav_width = parseInt($sidenav.innerWidth());
Scott Main3b90aff2013-08-01 18:09:35 -0700510
Scott Maine4d8f1b2012-06-21 18:03:05 -0700511 $("#devdoc-nav #nav").css("width", sidenav_width - 4 + "px"); // 4px is scrollbar width
512
513
514 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
Scott Main3b90aff2013-08-01 18:09:35 -0700515
Scott Maine4d8f1b2012-06-21 18:03:05 -0700516 if ($(".scroll-pane").length > 1) {
517 // Check if there's a user preference for the panel heights
518 var cookieHeight = readCookie("reference_height");
519 if (cookieHeight) {
520 restoreHeight(cookieHeight);
521 }
522 }
Scott Main3b90aff2013-08-01 18:09:35 -0700523
Scott Main06f3f2c2014-05-30 11:23:00 -0700524 // Resize once loading is finished
Scott Maine4d8f1b2012-06-21 18:03:05 -0700525 resizeNav();
Scott Main06f3f2c2014-05-30 11:23:00 -0700526 // Check if there's an anchor that we need to scroll into view.
527 // A delay is needed, because some browsers do not immediately scroll down to the anchor
528 window.setTimeout(offsetScrollForSticky, 100);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700529
Scott Main015d6162013-01-29 09:01:52 -0800530 /* init the language selector based on user cookie for lang */
531 loadLangPref();
532 changeNavLang(getLangPref());
533
534 /* setup event handlers to ensure the overflow menu is visible while picking lang */
535 $("#language select")
536 .mousedown(function() {
537 $("div.morehover").addClass("hover"); })
538 .blur(function() {
539 $("div.morehover").removeClass("hover"); });
540
541 /* some global variable setup */
542 resizePackagesNav = $("#resize-packages-nav");
543 classesNav = $("#classes-nav");
544 devdocNav = $("#devdoc-nav");
545
546 var cookiePath = "";
547 if (location.href.indexOf("/reference/") != -1) {
548 cookiePath = "reference_";
549 } else if (location.href.indexOf("/guide/") != -1) {
550 cookiePath = "guide_";
551 } else if (location.href.indexOf("/tools/") != -1) {
552 cookiePath = "tools_";
553 } else if (location.href.indexOf("/training/") != -1) {
554 cookiePath = "training_";
555 } else if (location.href.indexOf("/design/") != -1) {
556 cookiePath = "design_";
557 } else if (location.href.indexOf("/distribute/") != -1) {
558 cookiePath = "distribute_";
559 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700560
561});
Scott Main7e447ed2013-02-19 17:22:37 -0800562// END of the onload event
Scott Maine4d8f1b2012-06-21 18:03:05 -0700563
564
Scott Mainad08f072013-08-20 16:49:57 -0700565function initExpandableNavItems(rootTag) {
566 $(rootTag + ' li.nav-section .nav-section-header').click(function() {
567 var section = $(this).closest('li.nav-section');
568 if (section.hasClass('expanded')) {
Scott Mainf0093852013-08-22 11:37:11 -0700569 /* hide me and descendants */
570 section.find('ul').slideUp(250, function() {
571 // remove 'expanded' class from my section and any children
Scott Mainad08f072013-08-20 16:49:57 -0700572 section.closest('li').removeClass('expanded');
Scott Mainf0093852013-08-22 11:37:11 -0700573 $('li.nav-section', section).removeClass('expanded');
Scott Mainad08f072013-08-20 16:49:57 -0700574 resizeNav();
575 });
576 } else {
577 /* show me */
578 // first hide all other siblings
Scott Main70557ee2013-10-30 14:47:40 -0700579 var $others = $('li.nav-section.expanded', $(this).closest('ul')).not('.sticky');
Scott Mainad08f072013-08-20 16:49:57 -0700580 $others.removeClass('expanded').children('ul').slideUp(250);
581
582 // now expand me
583 section.closest('li').addClass('expanded');
584 section.children('ul').slideDown(250, function() {
585 resizeNav();
586 });
587 }
588 });
Scott Mainf0093852013-08-22 11:37:11 -0700589
590 // Stop expand/collapse behavior when clicking on nav section links
591 // (since we're navigating away from the page)
592 // This selector captures the first instance of <a>, but not those with "#" as the href.
593 $('.nav-section-header').find('a:eq(0)').not('a[href="#"]').click(function(evt) {
594 window.location.href = $(this).attr('href');
595 return false;
596 });
Scott Mainad08f072013-08-20 16:49:57 -0700597}
598
Dirk Doughertyc3921652014-05-13 16:55:26 -0700599
600/** Create the list of breadcrumb links in the sticky header */
601function buildBreadcrumbs() {
602 var $breadcrumbUl = $("#sticky-header ul.breadcrumb");
603 // Add the secondary horizontal nav item, if provided
604 var $selectedSecondNav = $("div#nav-x ul.nav-x a.selected").clone().removeClass("selected");
605 if ($selectedSecondNav.length) {
606 $breadcrumbUl.prepend($("<li>").append($selectedSecondNav))
607 }
608 // Add the primary horizontal nav
609 var $selectedFirstNav = $("div#header-wrap ul.nav-x a.selected").clone().removeClass("selected");
610 // If there's no header nav item, use the logo link and title from alt text
611 if ($selectedFirstNav.length < 1) {
612 $selectedFirstNav = $("<a>")
613 .attr('href', $("div#header .logo a").attr('href'))
614 .text($("div#header .logo img").attr('alt'));
615 }
616 $breadcrumbUl.prepend($("<li>").append($selectedFirstNav));
617}
618
619
620
Scott Maine624b3f2013-09-12 12:56:41 -0700621/** Highlight the current page in sidenav, expanding children as appropriate */
Scott Mainf6145542013-04-01 16:38:11 -0700622function highlightSidenav() {
Scott Maine624b3f2013-09-12 12:56:41 -0700623 // if something is already highlighted, undo it. This is for dynamic navigation (Samples index)
624 if ($("ul#nav li.selected").length) {
625 unHighlightSidenav();
626 }
627 // look for URL in sidenav, including the hash
628 var $selNavLink = $('#nav').find('a[href="' + mPagePath + location.hash + '"]');
629
630 // If the selNavLink is still empty, look for it without the hash
631 if ($selNavLink.length == 0) {
632 $selNavLink = $('#nav').find('a[href="' + mPagePath + '"]');
633 }
634
Scott Mainf6145542013-04-01 16:38:11 -0700635 var $selListItem;
636 if ($selNavLink.length) {
Scott Mainf6145542013-04-01 16:38:11 -0700637 // Find this page's <li> in sidenav and set selected
638 $selListItem = $selNavLink.closest('li');
639 $selListItem.addClass('selected');
Scott Main3b90aff2013-08-01 18:09:35 -0700640
Scott Mainf6145542013-04-01 16:38:11 -0700641 // Traverse up the tree and expand all parent nav-sections
642 $selNavLink.parents('li.nav-section').each(function() {
643 $(this).addClass('expanded');
644 $(this).children('ul').show();
645 });
646 }
647}
648
Scott Maine624b3f2013-09-12 12:56:41 -0700649function unHighlightSidenav() {
650 $("ul#nav li.selected").removeClass("selected");
651 $('ul#nav li.nav-section.expanded').removeClass('expanded').children('ul').hide();
652}
Scott Maine4d8f1b2012-06-21 18:03:05 -0700653
654function toggleFullscreen(enable) {
655 var delay = 20;
656 var enabled = true;
657 var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
658 if (enable) {
659 // Currently NOT USING fullscreen; enable fullscreen
660 stylesheet.removeAttr('disabled');
661 $('#nav-swap .fullscreen').removeClass('disabled');
662 $('#devdoc-nav').css({left:''});
663 setTimeout(updateSidenavFullscreenWidth,delay); // need to wait a moment for css to switch
664 enabled = true;
665 } else {
666 // Currently USING fullscreen; disable fullscreen
667 stylesheet.attr('disabled', 'disabled');
668 $('#nav-swap .fullscreen').addClass('disabled');
669 setTimeout(updateSidenavFixedWidth,delay); // need to wait a moment for css to switch
670 enabled = false;
671 }
smain@google.com6bdcb982014-11-14 11:53:07 -0800672 writeCookie("fullscreen", enabled, null);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700673 setNavBarLeftPos();
674 resizeNav(delay);
675 updateSideNavPosition();
676 setTimeout(initSidenavHeightResize,delay);
677}
678
679
680function setNavBarLeftPos() {
681 navBarLeftPos = $('#body-content').offset().left;
682}
683
684
685function updateSideNavPosition() {
686 var newLeft = $(window).scrollLeft() - navBarLeftPos;
687 $('#devdoc-nav').css({left: -newLeft});
688 $('#devdoc-nav .totop').css({left: -(newLeft - parseInt($('#side-nav').css('margin-left')))});
689}
Scott Main3b90aff2013-08-01 18:09:35 -0700690
Scott Maine4d8f1b2012-06-21 18:03:05 -0700691// TODO: use $(document).ready instead
692function addLoadEvent(newfun) {
693 var current = window.onload;
694 if (typeof window.onload != 'function') {
695 window.onload = newfun;
696 } else {
697 window.onload = function() {
698 current();
699 newfun();
700 }
701 }
702}
703
704var agent = navigator['userAgent'].toLowerCase();
705// If a mobile phone, set flag and do mobile setup
706if ((agent.indexOf("mobile") != -1) || // android, iphone, ipod
707 (agent.indexOf("blackberry") != -1) ||
708 (agent.indexOf("webos") != -1) ||
709 (agent.indexOf("mini") != -1)) { // opera mini browsers
710 isMobile = true;
711}
712
713
Scott Main498d7102013-08-21 15:47:38 -0700714$(document).ready(function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700715 $("pre:not(.no-pretty-print)").addClass("prettyprint");
716 prettyPrint();
Scott Main498d7102013-08-21 15:47:38 -0700717});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700718
Scott Maine4d8f1b2012-06-21 18:03:05 -0700719
720
721
722/* ######### RESIZE THE SIDENAV HEIGHT ########## */
723
724function resizeNav(delay) {
725 var $nav = $("#devdoc-nav");
726 var $window = $(window);
727 var navHeight;
Scott Main3b90aff2013-08-01 18:09:35 -0700728
Scott Maine4d8f1b2012-06-21 18:03:05 -0700729 // Get the height of entire window and the total header height.
730 // Then figure out based on scroll position whether the header is visible
731 var windowHeight = $window.height();
732 var scrollTop = $window.scrollTop();
Dirk Doughertyc3921652014-05-13 16:55:26 -0700733 var headerHeight = $('#header-wrapper').outerHeight();
734 var headerVisible = scrollTop < stickyTop;
Scott Main3b90aff2013-08-01 18:09:35 -0700735
736 // get the height of space between nav and top of window.
Scott Maine4d8f1b2012-06-21 18:03:05 -0700737 // Could be either margin or top position, depending on whether the nav is fixed.
Scott Main3b90aff2013-08-01 18:09:35 -0700738 var topMargin = (parseInt($nav.css('margin-top')) || parseInt($nav.css('top'))) + 1;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700739 // add 1 for the #side-nav bottom margin
Scott Main3b90aff2013-08-01 18:09:35 -0700740
Scott Maine4d8f1b2012-06-21 18:03:05 -0700741 // Depending on whether the header is visible, set the side nav's height.
742 if (headerVisible) {
743 // The sidenav height grows as the header goes off screen
Dirk Doughertyc3921652014-05-13 16:55:26 -0700744 navHeight = windowHeight - (headerHeight - scrollTop) - topMargin;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700745 } else {
746 // Once header is off screen, the nav height is almost full window height
747 navHeight = windowHeight - topMargin;
748 }
Scott Main3b90aff2013-08-01 18:09:35 -0700749
750
751
Scott Maine4d8f1b2012-06-21 18:03:05 -0700752 $scrollPanes = $(".scroll-pane");
753 if ($scrollPanes.length > 1) {
754 // subtract the height of the api level widget and nav swapper from the available nav height
755 navHeight -= ($('#api-nav-header').outerHeight(true) + $('#nav-swap').outerHeight(true));
Scott Main3b90aff2013-08-01 18:09:35 -0700756
Scott Maine4d8f1b2012-06-21 18:03:05 -0700757 $("#swapper").css({height:navHeight + "px"});
758 if ($("#nav-tree").is(":visible")) {
759 $("#nav-tree").css({height:navHeight});
760 }
Scott Main3b90aff2013-08-01 18:09:35 -0700761
762 var classesHeight = navHeight - parseInt($("#resize-packages-nav").css("height")) - 10 + "px";
Scott Maine4d8f1b2012-06-21 18:03:05 -0700763 //subtract 10px to account for drag bar
Scott Main3b90aff2013-08-01 18:09:35 -0700764
765 // if the window becomes small enough to make the class panel height 0,
Scott Maine4d8f1b2012-06-21 18:03:05 -0700766 // then the package panel should begin to shrink
767 if (parseInt(classesHeight) <= 0) {
768 $("#resize-packages-nav").css({height:navHeight - 10}); //subtract 10px for drag bar
769 $("#packages-nav").css({height:navHeight - 10});
770 }
Scott Main3b90aff2013-08-01 18:09:35 -0700771
Scott Maine4d8f1b2012-06-21 18:03:05 -0700772 $("#classes-nav").css({'height':classesHeight, 'margin-top':'10px'});
773 $("#classes-nav .jspContainer").css({height:classesHeight});
Scott Main3b90aff2013-08-01 18:09:35 -0700774
775
Scott Maine4d8f1b2012-06-21 18:03:05 -0700776 } else {
777 $nav.height(navHeight);
778 }
Scott Main3b90aff2013-08-01 18:09:35 -0700779
Scott Maine4d8f1b2012-06-21 18:03:05 -0700780 if (delay) {
781 updateFromResize = true;
782 delayedReInitScrollbars(delay);
783 } else {
784 reInitScrollbars();
785 }
Scott Main3b90aff2013-08-01 18:09:35 -0700786
Scott Maine4d8f1b2012-06-21 18:03:05 -0700787}
788
789var updateScrollbars = false;
790var updateFromResize = false;
791
792/* Re-initialize the scrollbars to account for changed nav size.
793 * This method postpones the actual update by a 1/4 second in order to optimize the
794 * scroll performance while the header is still visible, because re-initializing the
795 * scroll panes is an intensive process.
796 */
797function delayedReInitScrollbars(delay) {
798 // If we're scheduled for an update, but have received another resize request
799 // before the scheduled resize has occured, just ignore the new request
800 // (and wait for the scheduled one).
801 if (updateScrollbars && updateFromResize) {
802 updateFromResize = false;
803 return;
804 }
Scott Main3b90aff2013-08-01 18:09:35 -0700805
Scott Maine4d8f1b2012-06-21 18:03:05 -0700806 // We're scheduled for an update and the update request came from this method's setTimeout
807 if (updateScrollbars && !updateFromResize) {
808 reInitScrollbars();
809 updateScrollbars = false;
810 } else {
811 updateScrollbars = true;
812 updateFromResize = false;
813 setTimeout('delayedReInitScrollbars()',delay);
814 }
815}
816
817/* Re-initialize the scrollbars to account for changed nav size. */
818function reInitScrollbars() {
819 var pane = $(".scroll-pane").each(function(){
820 var api = $(this).data('jsp');
821 if (!api) { setTimeout(reInitScrollbars,300); return;}
822 api.reinitialise( {verticalGutter:0} );
Scott Main3b90aff2013-08-01 18:09:35 -0700823 });
Scott Maine4d8f1b2012-06-21 18:03:05 -0700824 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
825}
826
827
828/* Resize the height of the nav panels in the reference,
829 * and save the new size to a cookie */
830function saveNavPanels() {
831 var basePath = getBaseUri(location.pathname);
832 var section = basePath.substring(1,basePath.indexOf("/",1));
smain@google.com6bdcb982014-11-14 11:53:07 -0800833 writeCookie("height", resizePackagesNav.css("height"), section);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700834}
835
836
837
838function restoreHeight(packageHeight) {
839 $("#resize-packages-nav").height(packageHeight);
840 $("#packages-nav").height(packageHeight);
841 // var classesHeight = navHeight - packageHeight;
842 // $("#classes-nav").css({height:classesHeight});
843 // $("#classes-nav .jspContainer").css({height:classesHeight});
844}
845
846
847
848/* ######### END RESIZE THE SIDENAV HEIGHT ########## */
849
850
851
852
853
Scott Main3b90aff2013-08-01 18:09:35 -0700854/** Scroll the jScrollPane to make the currently selected item visible
Scott Maine4d8f1b2012-06-21 18:03:05 -0700855 This is called when the page finished loading. */
856function scrollIntoView(nav) {
857 var $nav = $("#"+nav);
858 var element = $nav.jScrollPane({/* ...settings... */});
859 var api = element.data('jsp');
860
861 if ($nav.is(':visible')) {
862 var $selected = $(".selected", $nav);
Scott Mainbc729572013-07-30 18:00:51 -0700863 if ($selected.length == 0) {
864 // If no selected item found, exit
865 return;
866 }
Scott Main52dd2062013-08-15 12:22:28 -0700867 // get the selected item's offset from its container nav by measuring the item's offset
868 // relative to the document then subtract the container nav's offset relative to the document
869 var selectedOffset = $selected.offset().top - $nav.offset().top;
870 if (selectedOffset > $nav.height() * .8) { // multiply nav height by .8 so we move up the item
871 // if it's more than 80% down the nav
872 // scroll the item up by an amount equal to 80% the container nav's height
873 api.scrollTo(0, selectedOffset - ($nav.height() * .8), false);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700874 }
875 }
876}
877
878
879
880
881
882
883/* Show popup dialogs */
884function showDialog(id) {
885 $dialog = $("#"+id);
886 $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>');
887 $dialog.wrapInner('<div/>');
888 $dialog.removeClass("hide");
889}
890
891
892
893
894
895/* ######### COOKIES! ########## */
896
897function readCookie(cookie) {
898 var myCookie = cookie_namespace+"_"+cookie+"=";
899 if (document.cookie) {
900 var index = document.cookie.indexOf(myCookie);
901 if (index != -1) {
902 var valStart = index + myCookie.length;
903 var valEnd = document.cookie.indexOf(";", valStart);
904 if (valEnd == -1) {
905 valEnd = document.cookie.length;
906 }
907 var val = document.cookie.substring(valStart, valEnd);
908 return val;
909 }
910 }
911 return 0;
912}
913
smain@google.com6bdcb982014-11-14 11:53:07 -0800914function writeCookie(cookie, val, section) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700915 if (val==undefined) return;
916 section = section == null ? "_" : "_"+section+"_";
smain@google.com6bdcb982014-11-14 11:53:07 -0800917 var age = 2*365*24*60*60; // set max-age to 2 years
Scott Main3b90aff2013-08-01 18:09:35 -0700918 var cookieValue = cookie_namespace + section + cookie + "=" + val
smain@google.com80e38f42014-11-03 10:47:12 -0800919 + "; max-age=" + age +"; path=/";
Scott Maine4d8f1b2012-06-21 18:03:05 -0700920 document.cookie = cookieValue;
921}
922
923/* ######### END COOKIES! ########## */
924
925
Dirk Doughertyca1230c2014-05-14 20:00:03 -0700926var sticky = false;
Dirk Doughertyc3921652014-05-13 16:55:26 -0700927var stickyTop;
Dirk Doughertyca1230c2014-05-14 20:00:03 -0700928var prevScrollLeft = 0; // used to compare current position to previous position of horiz scroll
Dirk Doughertyc3921652014-05-13 16:55:26 -0700929/* Sets the vertical scoll position at which the sticky bar should appear.
930 This method is called to reset the position when search results appear or hide */
931function setStickyTop() {
932 stickyTop = $('#header-wrapper').outerHeight() - $('#sticky-header').outerHeight();
933}
Scott Maine4d8f1b2012-06-21 18:03:05 -0700934
Dirk Doughertyca1230c2014-05-14 20:00:03 -0700935/*
Scott Mainb16376f2014-05-21 20:35:47 -0700936 * Displays sticky nav bar on pages when dac header scrolls out of view
Dirk Doughertyc3921652014-05-13 16:55:26 -0700937 */
Dirk Doughertyca1230c2014-05-14 20:00:03 -0700938$(window).scroll(function(event) {
939
940 setStickyTop();
941 var hiding = false;
942 var $stickyEl = $('#sticky-header');
943 var $menuEl = $('.menu-container');
944 // Exit if there's no sidenav
945 if ($('#side-nav').length == 0) return;
946 // Exit if the mouse target is a DIV, because that means the event is coming
947 // from a scrollable div and so there's no need to make adjustments to our layout
948 if ($(event.target).nodeName == "DIV") {
949 return;
950 }
951
952 var top = $(window).scrollTop();
953 // we set the navbar fixed when the scroll position is beyond the height of the site header...
954 var shouldBeSticky = top >= stickyTop;
955 // ... except if the document content is shorter than the sidenav height.
956 // (this is necessary to avoid crazy behavior on OSX Lion due to overscroll bouncing)
957 if ($("#doc-col").height() < $("#side-nav").height()) {
958 shouldBeSticky = false;
959 }
Scott Mainf5257812014-05-22 17:26:38 -0700960 // Account for horizontal scroll
961 var scrollLeft = $(window).scrollLeft();
962 // When the sidenav is fixed and user scrolls horizontally, reposition the sidenav to match
963 if (sticky && (scrollLeft != prevScrollLeft)) {
964 updateSideNavPosition();
965 prevScrollLeft = scrollLeft;
966 }
Dirk Doughertyca1230c2014-05-14 20:00:03 -0700967
968 // Don't continue if the header is sufficently far away
969 // (to avoid intensive resizing that slows scrolling)
970 if (sticky == shouldBeSticky) {
971 return;
972 }
Dirk Doughertyca1230c2014-05-14 20:00:03 -0700973
974 // If sticky header visible and position is now near top, hide sticky
975 if (sticky && !shouldBeSticky) {
976 sticky = false;
977 hiding = true;
978 // make the sidenav static again
979 $('#devdoc-nav')
980 .removeClass('fixed')
981 .css({'width':'auto','margin':''})
982 .prependTo('#side-nav');
983 // delay hide the sticky
984 $menuEl.removeClass('sticky-menu');
985 $stickyEl.fadeOut(250);
986 hiding = false;
987
988 // update the sidenaav position for side scrolling
989 updateSideNavPosition();
990 } else if (!sticky && shouldBeSticky) {
991 sticky = true;
992 $stickyEl.fadeIn(10);
993 $menuEl.addClass('sticky-menu');
994
995 // make the sidenav fixed
996 var width = $('#devdoc-nav').width();
997 $('#devdoc-nav')
998 .addClass('fixed')
999 .css({'width':width+'px'})
1000 .prependTo('#body-content');
1001
1002 // update the sidenaav position for side scrolling
1003 updateSideNavPosition();
1004
1005 } else if (hiding && top < 15) {
1006 $menuEl.removeClass('sticky-menu');
1007 $stickyEl.hide();
1008 hiding = false;
1009 }
1010 resizeNav(250); // pass true in order to delay the scrollbar re-initialization for performance
1011});
1012
1013/*
1014 * Manages secion card states and nav resize to conclude loading
1015 */
Dirk Doughertyc3921652014-05-13 16:55:26 -07001016(function() {
1017 $(document).ready(function() {
1018
Dirk Doughertyc3921652014-05-13 16:55:26 -07001019 // Stack hover states
1020 $('.section-card-menu').each(function(index, el) {
1021 var height = $(el).height();
1022 $(el).css({height:height+'px', position:'relative'});
1023 var $cardInfo = $(el).find('.card-info');
1024
1025 $cardInfo.css({position: 'absolute', bottom:'0px', left:'0px', right:'0px', overflow:'visible'});
1026 });
1027
Dirk Doughertyc3921652014-05-13 16:55:26 -07001028 });
1029
1030})();
1031
Scott Maine4d8f1b2012-06-21 18:03:05 -07001032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
Scott Maind7026f72013-06-17 15:08:49 -07001045/* MISC LIBRARY FUNCTIONS */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001046
1047
1048
1049
1050
1051function toggle(obj, slide) {
1052 var ul = $("ul:first", obj);
1053 var li = ul.parent();
1054 if (li.hasClass("closed")) {
1055 if (slide) {
1056 ul.slideDown("fast");
1057 } else {
1058 ul.show();
1059 }
1060 li.removeClass("closed");
1061 li.addClass("open");
1062 $(".toggle-img", li).attr("title", "hide pages");
1063 } else {
1064 ul.slideUp("fast");
1065 li.removeClass("open");
1066 li.addClass("closed");
1067 $(".toggle-img", li).attr("title", "show pages");
1068 }
1069}
1070
1071
Scott Maine4d8f1b2012-06-21 18:03:05 -07001072function buildToggleLists() {
1073 $(".toggle-list").each(
1074 function(i) {
1075 $("div:first", this).append("<a class='toggle-img' href='#' title='show pages' onClick='toggle(this.parentNode.parentNode, true); return false;'></a>");
1076 $(this).addClass("closed");
1077 });
1078}
1079
1080
1081
Scott Maind7026f72013-06-17 15:08:49 -07001082function hideNestedItems(list, toggle) {
1083 $list = $(list);
1084 // hide nested lists
1085 if($list.hasClass('showing')) {
1086 $("li ol", $list).hide('fast');
1087 $list.removeClass('showing');
1088 // show nested lists
1089 } else {
1090 $("li ol", $list).show('fast');
1091 $list.addClass('showing');
1092 }
1093 $(".more,.less",$(toggle)).toggle();
1094}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001095
1096
smain@google.com95948b82014-06-16 19:24:25 -07001097/* Call this to add listeners to a <select> element for Studio/Eclipse/Other docs */
1098function setupIdeDocToggle() {
1099 $( "select.ide" ).change(function() {
1100 var selected = $(this).find("option:selected").attr("value");
1101 $(".select-ide").hide();
1102 $(".select-ide."+selected).show();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001103
smain@google.com95948b82014-06-16 19:24:25 -07001104 $("select.ide").val(selected);
1105 });
1106}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131/* REFERENCE NAV SWAP */
1132
1133
1134function getNavPref() {
1135 var v = readCookie('reference_nav');
1136 if (v != NAV_PREF_TREE) {
1137 v = NAV_PREF_PANELS;
1138 }
1139 return v;
1140}
1141
1142function chooseDefaultNav() {
1143 nav_pref = getNavPref();
1144 if (nav_pref == NAV_PREF_TREE) {
1145 $("#nav-panels").toggle();
1146 $("#panel-link").toggle();
1147 $("#nav-tree").toggle();
1148 $("#tree-link").toggle();
1149 }
1150}
1151
1152function swapNav() {
1153 if (nav_pref == NAV_PREF_TREE) {
1154 nav_pref = NAV_PREF_PANELS;
1155 } else {
1156 nav_pref = NAV_PREF_TREE;
1157 init_default_navtree(toRoot);
1158 }
smain@google.com6bdcb982014-11-14 11:53:07 -08001159 writeCookie("nav", nav_pref, "reference");
Scott Maine4d8f1b2012-06-21 18:03:05 -07001160
1161 $("#nav-panels").toggle();
1162 $("#panel-link").toggle();
1163 $("#nav-tree").toggle();
1164 $("#tree-link").toggle();
Scott Main3b90aff2013-08-01 18:09:35 -07001165
Scott Maine4d8f1b2012-06-21 18:03:05 -07001166 resizeNav();
1167
1168 // Gross nasty hack to make tree view show up upon first swap by setting height manually
1169 $("#nav-tree .jspContainer:visible")
1170 .css({'height':$("#nav-tree .jspContainer .jspPane").height() +'px'});
1171 // Another nasty hack to make the scrollbar appear now that we have height
1172 resizeNav();
Scott Main3b90aff2013-08-01 18:09:35 -07001173
Scott Maine4d8f1b2012-06-21 18:03:05 -07001174 if ($("#nav-tree").is(':visible')) {
1175 scrollIntoView("nav-tree");
1176 } else {
1177 scrollIntoView("packages-nav");
1178 scrollIntoView("classes-nav");
1179 }
1180}
1181
1182
1183
Scott Mainf5089842012-08-14 16:31:07 -07001184/* ############################################ */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001185/* ########## LOCALIZATION ############ */
Scott Mainf5089842012-08-14 16:31:07 -07001186/* ############################################ */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001187
1188function getBaseUri(uri) {
1189 var intlUrl = (uri.substring(0,6) == "/intl/");
1190 if (intlUrl) {
1191 base = uri.substring(uri.indexOf('intl/')+5,uri.length);
1192 base = base.substring(base.indexOf('/')+1, base.length);
1193 //alert("intl, returning base url: /" + base);
1194 return ("/" + base);
1195 } else {
1196 //alert("not intl, returning uri as found.");
1197 return uri;
1198 }
1199}
1200
1201function requestAppendHL(uri) {
1202//append "?hl=<lang> to an outgoing request (such as to blog)
1203 var lang = getLangPref();
1204 if (lang) {
1205 var q = 'hl=' + lang;
1206 uri += '?' + q;
1207 window.location = uri;
1208 return false;
1209 } else {
1210 return true;
1211 }
1212}
1213
1214
Scott Maine4d8f1b2012-06-21 18:03:05 -07001215function changeNavLang(lang) {
Scott Main6eb95f12012-10-02 17:12:23 -07001216 var $links = $("#devdoc-nav,#header,#nav-x,.training-nav-top,.content-footer").find("a["+lang+"-lang]");
1217 $links.each(function(i){ // for each link with a translation
1218 var $link = $(this);
1219 if (lang != "en") { // No need to worry about English, because a language change invokes new request
1220 // put the desired language from the attribute as the text
1221 $link.text($link.attr(lang+"-lang"))
Scott Maine4d8f1b2012-06-21 18:03:05 -07001222 }
Scott Main6eb95f12012-10-02 17:12:23 -07001223 });
Scott Maine4d8f1b2012-06-21 18:03:05 -07001224}
1225
Scott Main015d6162013-01-29 09:01:52 -08001226function changeLangPref(lang, submit) {
smain@google.com6bdcb982014-11-14 11:53:07 -08001227 writeCookie("pref_lang", lang, null);
Scott Main015d6162013-01-29 09:01:52 -08001228
1229 // ####### TODO: Remove this condition once we're stable on devsite #######
1230 // This condition is only needed if we still need to support legacy GAE server
1231 if (devsite) {
1232 // Switch language when on Devsite server
1233 if (submit) {
1234 $("#setlang").submit();
1235 }
1236 } else {
1237 // Switch language when on legacy GAE server
Scott Main015d6162013-01-29 09:01:52 -08001238 if (submit) {
1239 window.location = getBaseUri(location.pathname);
1240 }
Scott Maine4d8f1b2012-06-21 18:03:05 -07001241 }
1242}
1243
1244function loadLangPref() {
1245 var lang = readCookie("pref_lang");
1246 if (lang != 0) {
1247 $("#language").find("option[value='"+lang+"']").attr("selected",true);
1248 }
1249}
1250
1251function getLangPref() {
1252 var lang = $("#language").find(":selected").attr("value");
1253 if (!lang) {
1254 lang = readCookie("pref_lang");
1255 }
1256 return (lang != 0) ? lang : 'en';
1257}
1258
1259/* ########## END LOCALIZATION ############ */
1260
1261
1262
1263
1264
1265
1266/* Used to hide and reveal supplemental content, such as long code samples.
1267 See the companion CSS in android-developer-docs.css */
1268function toggleContent(obj) {
Scott Maindc63dda2013-08-22 16:03:21 -07001269 var div = $(obj).closest(".toggle-content");
1270 var toggleMe = $(".toggle-content-toggleme:eq(0)",div);
Scott Maine4d8f1b2012-06-21 18:03:05 -07001271 if (div.hasClass("closed")) { // if it's closed, open it
1272 toggleMe.slideDown();
Scott Maindc63dda2013-08-22 16:03:21 -07001273 $(".toggle-content-text:eq(0)", obj).toggle();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001274 div.removeClass("closed").addClass("open");
Scott Maindc63dda2013-08-22 16:03:21 -07001275 $(".toggle-content-img:eq(0)", div).attr("title", "hide").attr("src", toRoot
Scott Maine4d8f1b2012-06-21 18:03:05 -07001276 + "assets/images/triangle-opened.png");
1277 } else { // if it's open, close it
1278 toggleMe.slideUp('fast', function() { // Wait until the animation is done before closing arrow
Scott Maindc63dda2013-08-22 16:03:21 -07001279 $(".toggle-content-text:eq(0)", obj).toggle();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001280 div.removeClass("open").addClass("closed");
Scott Maindc63dda2013-08-22 16:03:21 -07001281 div.find(".toggle-content").removeClass("open").addClass("closed")
1282 .find(".toggle-content-toggleme").hide();
Scott Main3b90aff2013-08-01 18:09:35 -07001283 $(".toggle-content-img", div).attr("title", "show").attr("src", toRoot
Scott Maine4d8f1b2012-06-21 18:03:05 -07001284 + "assets/images/triangle-closed.png");
1285 });
1286 }
1287 return false;
1288}
Scott Mainf5089842012-08-14 16:31:07 -07001289
1290
Scott Maindb3678b2012-10-23 14:13:41 -07001291/* New version of expandable content */
1292function toggleExpandable(link,id) {
1293 if($(id).is(':visible')) {
1294 $(id).slideUp();
1295 $(link).removeClass('expanded');
1296 } else {
1297 $(id).slideDown();
1298 $(link).addClass('expanded');
1299 }
1300}
1301
1302function hideExpandable(ids) {
1303 $(ids).slideUp();
Scott Main55d99832012-11-12 23:03:59 -08001304 $(ids).prev('h4').find('a.expandable').removeClass('expanded');
Scott Maindb3678b2012-10-23 14:13:41 -07001305}
1306
Scott Mainf5089842012-08-14 16:31:07 -07001307
1308
1309
1310
Scott Main3b90aff2013-08-01 18:09:35 -07001311/*
Scott Mainf5089842012-08-14 16:31:07 -07001312 * Slideshow 1.0
1313 * Used on /index.html and /develop/index.html for carousel
1314 *
1315 * Sample usage:
1316 * HTML -
1317 * <div class="slideshow-container">
1318 * <a href="" class="slideshow-prev">Prev</a>
1319 * <a href="" class="slideshow-next">Next</a>
1320 * <ul>
1321 * <li class="item"><img src="images/marquee1.jpg"></li>
1322 * <li class="item"><img src="images/marquee2.jpg"></li>
1323 * <li class="item"><img src="images/marquee3.jpg"></li>
1324 * <li class="item"><img src="images/marquee4.jpg"></li>
1325 * </ul>
1326 * </div>
1327 *
1328 * <script type="text/javascript">
1329 * $('.slideshow-container').dacSlideshow({
1330 * auto: true,
1331 * btnPrev: '.slideshow-prev',
1332 * btnNext: '.slideshow-next'
1333 * });
1334 * </script>
1335 *
1336 * Options:
1337 * btnPrev: optional identifier for previous button
1338 * btnNext: optional identifier for next button
Scott Maineb410352013-01-14 19:03:40 -08001339 * btnPause: optional identifier for pause button
Scott Mainf5089842012-08-14 16:31:07 -07001340 * auto: whether or not to auto-proceed
1341 * speed: animation speed
1342 * autoTime: time between auto-rotation
1343 * easing: easing function for transition
1344 * start: item to select by default
1345 * scroll: direction to scroll in
1346 * pagination: whether or not to include dotted pagination
1347 *
1348 */
1349
1350 (function($) {
1351 $.fn.dacSlideshow = function(o) {
Scott Main3b90aff2013-08-01 18:09:35 -07001352
Scott Mainf5089842012-08-14 16:31:07 -07001353 //Options - see above
1354 o = $.extend({
1355 btnPrev: null,
1356 btnNext: null,
Scott Maineb410352013-01-14 19:03:40 -08001357 btnPause: null,
Scott Mainf5089842012-08-14 16:31:07 -07001358 auto: true,
1359 speed: 500,
1360 autoTime: 12000,
1361 easing: null,
1362 start: 0,
1363 scroll: 1,
1364 pagination: true
1365
1366 }, o || {});
Scott Main3b90aff2013-08-01 18:09:35 -07001367
1368 //Set up a carousel for each
Scott Mainf5089842012-08-14 16:31:07 -07001369 return this.each(function() {
1370
1371 var running = false;
1372 var animCss = o.vertical ? "top" : "left";
1373 var sizeCss = o.vertical ? "height" : "width";
1374 var div = $(this);
1375 var ul = $("ul", div);
1376 var tLi = $("li", ul);
Scott Main3b90aff2013-08-01 18:09:35 -07001377 var tl = tLi.size();
Scott Mainf5089842012-08-14 16:31:07 -07001378 var timer = null;
1379
1380 var li = $("li", ul);
1381 var itemLength = li.size();
1382 var curr = o.start;
1383
1384 li.css({float: o.vertical ? "none" : "left"});
1385 ul.css({margin: "0", padding: "0", position: "relative", "list-style-type": "none", "z-index": "1"});
1386 div.css({position: "relative", "z-index": "2", left: "0px"});
1387
1388 var liSize = o.vertical ? height(li) : width(li);
1389 var ulSize = liSize * itemLength;
1390 var divSize = liSize;
1391
1392 li.css({width: li.width(), height: li.height()});
1393 ul.css(sizeCss, ulSize+"px").css(animCss, -(curr*liSize));
1394
1395 div.css(sizeCss, divSize+"px");
Scott Main3b90aff2013-08-01 18:09:35 -07001396
Scott Mainf5089842012-08-14 16:31:07 -07001397 //Pagination
1398 if (o.pagination) {
1399 var pagination = $("<div class='pagination'></div>");
1400 var pag_ul = $("<ul></ul>");
1401 if (tl > 1) {
1402 for (var i=0;i<tl;i++) {
1403 var li = $("<li>"+i+"</li>");
1404 pag_ul.append(li);
1405 if (i==o.start) li.addClass('active');
1406 li.click(function() {
1407 go(parseInt($(this).text()));
1408 })
1409 }
1410 pagination.append(pag_ul);
1411 div.append(pagination);
1412 }
1413 }
Scott Main3b90aff2013-08-01 18:09:35 -07001414
Scott Mainf5089842012-08-14 16:31:07 -07001415 //Previous button
1416 if(o.btnPrev)
1417 $(o.btnPrev).click(function(e) {
1418 e.preventDefault();
1419 return go(curr-o.scroll);
1420 });
1421
1422 //Next button
1423 if(o.btnNext)
1424 $(o.btnNext).click(function(e) {
1425 e.preventDefault();
1426 return go(curr+o.scroll);
1427 });
Scott Maineb410352013-01-14 19:03:40 -08001428
1429 //Pause button
1430 if(o.btnPause)
1431 $(o.btnPause).click(function(e) {
1432 e.preventDefault();
1433 if ($(this).hasClass('paused')) {
1434 startRotateTimer();
1435 } else {
1436 pauseRotateTimer();
1437 }
1438 });
Scott Main3b90aff2013-08-01 18:09:35 -07001439
Scott Mainf5089842012-08-14 16:31:07 -07001440 //Auto rotation
1441 if(o.auto) startRotateTimer();
Scott Main3b90aff2013-08-01 18:09:35 -07001442
Scott Mainf5089842012-08-14 16:31:07 -07001443 function startRotateTimer() {
1444 clearInterval(timer);
1445 timer = setInterval(function() {
1446 if (curr == tl-1) {
1447 go(0);
1448 } else {
Scott Main3b90aff2013-08-01 18:09:35 -07001449 go(curr+o.scroll);
1450 }
Scott Mainf5089842012-08-14 16:31:07 -07001451 }, o.autoTime);
Scott Maineb410352013-01-14 19:03:40 -08001452 $(o.btnPause).removeClass('paused');
1453 }
1454
1455 function pauseRotateTimer() {
1456 clearInterval(timer);
1457 $(o.btnPause).addClass('paused');
Scott Mainf5089842012-08-14 16:31:07 -07001458 }
1459
1460 //Go to an item
1461 function go(to) {
1462 if(!running) {
1463
1464 if(to<0) {
1465 to = itemLength-1;
1466 } else if (to>itemLength-1) {
1467 to = 0;
1468 }
1469 curr = to;
1470
1471 running = true;
1472
1473 ul.animate(
1474 animCss == "left" ? { left: -(curr*liSize) } : { top: -(curr*liSize) } , o.speed, o.easing,
1475 function() {
1476 running = false;
1477 }
1478 );
1479
1480 $(o.btnPrev + "," + o.btnNext).removeClass("disabled");
1481 $( (curr-o.scroll<0 && o.btnPrev)
1482 ||
1483 (curr+o.scroll > itemLength && o.btnNext)
1484 ||
1485 []
1486 ).addClass("disabled");
1487
Scott Main3b90aff2013-08-01 18:09:35 -07001488
Scott Mainf5089842012-08-14 16:31:07 -07001489 var nav_items = $('li', pagination);
1490 nav_items.removeClass('active');
1491 nav_items.eq(to).addClass('active');
Scott Main3b90aff2013-08-01 18:09:35 -07001492
Scott Mainf5089842012-08-14 16:31:07 -07001493
1494 }
1495 if(o.auto) startRotateTimer();
1496 return false;
1497 };
1498 });
1499 };
1500
1501 function css(el, prop) {
1502 return parseInt($.css(el[0], prop)) || 0;
1503 };
1504 function width(el) {
1505 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1506 };
1507 function height(el) {
1508 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1509 };
1510
1511 })(jQuery);
1512
1513
Scott Main3b90aff2013-08-01 18:09:35 -07001514/*
Scott Mainf5089842012-08-14 16:31:07 -07001515 * dacSlideshow 1.0
1516 * Used on develop/index.html for side-sliding tabs
1517 *
1518 * Sample usage:
1519 * HTML -
1520 * <div class="slideshow-container">
1521 * <a href="" class="slideshow-prev">Prev</a>
1522 * <a href="" class="slideshow-next">Next</a>
1523 * <ul>
1524 * <li class="item"><img src="images/marquee1.jpg"></li>
1525 * <li class="item"><img src="images/marquee2.jpg"></li>
1526 * <li class="item"><img src="images/marquee3.jpg"></li>
1527 * <li class="item"><img src="images/marquee4.jpg"></li>
1528 * </ul>
1529 * </div>
1530 *
1531 * <script type="text/javascript">
1532 * $('.slideshow-container').dacSlideshow({
1533 * auto: true,
1534 * btnPrev: '.slideshow-prev',
1535 * btnNext: '.slideshow-next'
1536 * });
1537 * </script>
1538 *
1539 * Options:
1540 * btnPrev: optional identifier for previous button
1541 * btnNext: optional identifier for next button
1542 * auto: whether or not to auto-proceed
1543 * speed: animation speed
1544 * autoTime: time between auto-rotation
1545 * easing: easing function for transition
1546 * start: item to select by default
1547 * scroll: direction to scroll in
1548 * pagination: whether or not to include dotted pagination
1549 *
1550 */
1551 (function($) {
1552 $.fn.dacTabbedList = function(o) {
Scott Main3b90aff2013-08-01 18:09:35 -07001553
Scott Mainf5089842012-08-14 16:31:07 -07001554 //Options - see above
1555 o = $.extend({
1556 speed : 250,
1557 easing: null,
1558 nav_id: null,
1559 frame_id: null
1560 }, o || {});
Scott Main3b90aff2013-08-01 18:09:35 -07001561
1562 //Set up a carousel for each
Scott Mainf5089842012-08-14 16:31:07 -07001563 return this.each(function() {
1564
1565 var curr = 0;
1566 var running = false;
1567 var animCss = "margin-left";
1568 var sizeCss = "width";
1569 var div = $(this);
Scott Main3b90aff2013-08-01 18:09:35 -07001570
Scott Mainf5089842012-08-14 16:31:07 -07001571 var nav = $(o.nav_id, div);
1572 var nav_li = $("li", nav);
Scott Main3b90aff2013-08-01 18:09:35 -07001573 var nav_size = nav_li.size();
Scott Mainf5089842012-08-14 16:31:07 -07001574 var frame = div.find(o.frame_id);
1575 var content_width = $(frame).find('ul').width();
1576 //Buttons
1577 $(nav_li).click(function(e) {
1578 go($(nav_li).index($(this)));
1579 })
Scott Main3b90aff2013-08-01 18:09:35 -07001580
Scott Mainf5089842012-08-14 16:31:07 -07001581 //Go to an item
1582 function go(to) {
1583 if(!running) {
1584 curr = to;
1585 running = true;
1586
1587 frame.animate({ 'margin-left' : -(curr*content_width) }, o.speed, o.easing,
1588 function() {
1589 running = false;
1590 }
1591 );
1592
Scott Main3b90aff2013-08-01 18:09:35 -07001593
Scott Mainf5089842012-08-14 16:31:07 -07001594 nav_li.removeClass('active');
1595 nav_li.eq(to).addClass('active');
Scott Main3b90aff2013-08-01 18:09:35 -07001596
Scott Mainf5089842012-08-14 16:31:07 -07001597
1598 }
1599 return false;
1600 };
1601 });
1602 };
1603
1604 function css(el, prop) {
1605 return parseInt($.css(el[0], prop)) || 0;
1606 };
1607 function width(el) {
1608 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1609 };
1610 function height(el) {
1611 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1612 };
1613
1614 })(jQuery);
1615
1616
1617
1618
1619
1620/* ######################################################## */
1621/* ################ SEARCH SUGGESTIONS ################## */
1622/* ######################################################## */
1623
1624
Scott Main7e447ed2013-02-19 17:22:37 -08001625
Scott Main0e76e7e2013-03-12 10:24:07 -07001626var gSelectedIndex = -1; // the index position of currently highlighted suggestion
1627var gSelectedColumn = -1; // which column of suggestion lists is currently focused
1628
Scott Mainf5089842012-08-14 16:31:07 -07001629var gMatches = new Array();
1630var gLastText = "";
Scott Mainf5089842012-08-14 16:31:07 -07001631var gInitialized = false;
Scott Main7e447ed2013-02-19 17:22:37 -08001632var ROW_COUNT_FRAMEWORK = 20; // max number of results in list
1633var gListLength = 0;
1634
1635
1636var gGoogleMatches = new Array();
1637var ROW_COUNT_GOOGLE = 15; // max number of results in list
1638var gGoogleListLength = 0;
Scott Mainf5089842012-08-14 16:31:07 -07001639
Scott Main0e76e7e2013-03-12 10:24:07 -07001640var gDocsMatches = new Array();
1641var ROW_COUNT_DOCS = 100; // max number of results in list
1642var gDocsListLength = 0;
1643
Scott Mainde295272013-03-25 15:48:35 -07001644function onSuggestionClick(link) {
1645 // When user clicks a suggested document, track it
smain@google.com633f3222014-10-03 15:49:45 -07001646 ga('send', 'event', 'Suggestion Click', 'clicked: ' + $(link).text(),
1647 'from: ' + $("#search_autocomplete").val());
Scott Mainde295272013-03-25 15:48:35 -07001648}
1649
Scott Mainf5089842012-08-14 16:31:07 -07001650function set_item_selected($li, selected)
1651{
1652 if (selected) {
1653 $li.attr('class','jd-autocomplete jd-selected');
1654 } else {
1655 $li.attr('class','jd-autocomplete');
1656 }
1657}
1658
1659function set_item_values(toroot, $li, match)
1660{
1661 var $link = $('a',$li);
1662 $link.html(match.__hilabel || match.label);
1663 $link.attr('href',toroot + match.link);
1664}
1665
Scott Main719acb42013-12-05 16:05:09 -08001666function set_item_values_jd(toroot, $li, match)
1667{
1668 var $link = $('a',$li);
1669 $link.html(match.title);
1670 $link.attr('href',toroot + match.url);
1671}
1672
Scott Main0e76e7e2013-03-12 10:24:07 -07001673function new_suggestion($list) {
Scott Main7e447ed2013-02-19 17:22:37 -08001674 var $li = $("<li class='jd-autocomplete'></li>");
1675 $list.append($li);
1676
1677 $li.mousedown(function() {
1678 window.location = this.firstChild.getAttribute("href");
1679 });
1680 $li.mouseover(function() {
Scott Main0e76e7e2013-03-12 10:24:07 -07001681 $('.search_filtered_wrapper li').removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001682 $(this).addClass('jd-selected');
Scott Main0e76e7e2013-03-12 10:24:07 -07001683 gSelectedColumn = $(".search_filtered:visible").index($(this).closest('.search_filtered'));
1684 gSelectedIndex = $("li", $(".search_filtered:visible")[gSelectedColumn]).index(this);
Scott Main7e447ed2013-02-19 17:22:37 -08001685 });
Scott Mainde295272013-03-25 15:48:35 -07001686 $li.append("<a onclick='onSuggestionClick(this)'></a>");
Scott Main7e447ed2013-02-19 17:22:37 -08001687 $li.attr('class','show-item');
1688 return $li;
1689}
1690
Scott Mainf5089842012-08-14 16:31:07 -07001691function sync_selection_table(toroot)
1692{
Scott Mainf5089842012-08-14 16:31:07 -07001693 var $li; //list item jquery object
1694 var i; //list item iterator
Scott Main7e447ed2013-02-19 17:22:37 -08001695
Scott Main0e76e7e2013-03-12 10:24:07 -07001696 // if there are NO results at all, hide all columns
1697 if (!(gMatches.length > 0) && !(gGoogleMatches.length > 0) && !(gDocsMatches.length > 0)) {
1698 $('.suggest-card').hide(300);
1699 return;
1700 }
1701
1702 // if there are api results
Scott Main7e447ed2013-02-19 17:22:37 -08001703 if ((gMatches.length > 0) || (gGoogleMatches.length > 0)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001704 // reveal suggestion list
1705 $('.suggest-card.dummy').show();
1706 $('.suggest-card.reference').show();
1707 var listIndex = 0; // list index position
Scott Main7e447ed2013-02-19 17:22:37 -08001708
Scott Main0e76e7e2013-03-12 10:24:07 -07001709 // reset the lists
1710 $(".search_filtered_wrapper.reference li").remove();
Scott Main7e447ed2013-02-19 17:22:37 -08001711
Scott Main0e76e7e2013-03-12 10:24:07 -07001712 // ########### ANDROID RESULTS #############
1713 if (gMatches.length > 0) {
Scott Main7e447ed2013-02-19 17:22:37 -08001714
Scott Main0e76e7e2013-03-12 10:24:07 -07001715 // determine android results to show
1716 gListLength = gMatches.length < ROW_COUNT_FRAMEWORK ?
1717 gMatches.length : ROW_COUNT_FRAMEWORK;
1718 for (i=0; i<gListLength; i++) {
1719 var $li = new_suggestion($(".suggest-card.reference ul"));
1720 set_item_values(toroot, $li, gMatches[i]);
1721 set_item_selected($li, i == gSelectedIndex);
1722 }
1723 }
Scott Main7e447ed2013-02-19 17:22:37 -08001724
Scott Main0e76e7e2013-03-12 10:24:07 -07001725 // ########### GOOGLE RESULTS #############
1726 if (gGoogleMatches.length > 0) {
1727 // show header for list
1728 $(".suggest-card.reference ul").append("<li class='header'>in Google Services:</li>");
Scott Main7e447ed2013-02-19 17:22:37 -08001729
Scott Main0e76e7e2013-03-12 10:24:07 -07001730 // determine google results to show
1731 gGoogleListLength = gGoogleMatches.length < ROW_COUNT_GOOGLE ? gGoogleMatches.length : ROW_COUNT_GOOGLE;
1732 for (i=0; i<gGoogleListLength; i++) {
1733 var $li = new_suggestion($(".suggest-card.reference ul"));
1734 set_item_values(toroot, $li, gGoogleMatches[i]);
1735 set_item_selected($li, i == gSelectedIndex);
1736 }
1737 }
Scott Mainf5089842012-08-14 16:31:07 -07001738 } else {
Scott Main0e76e7e2013-03-12 10:24:07 -07001739 $('.suggest-card.reference').hide();
1740 $('.suggest-card.dummy').hide();
1741 }
1742
1743 // ########### JD DOC RESULTS #############
1744 if (gDocsMatches.length > 0) {
1745 // reset the lists
1746 $(".search_filtered_wrapper.docs li").remove();
1747
1748 // determine google results to show
Scott Main719acb42013-12-05 16:05:09 -08001749 // NOTE: The order of the conditions below for the sugg.type MUST BE SPECIFIC:
1750 // The order must match the reverse order that each section appears as a card in
1751 // the suggestion UI... this may be only for the "develop" grouped items though.
Scott Main0e76e7e2013-03-12 10:24:07 -07001752 gDocsListLength = gDocsMatches.length < ROW_COUNT_DOCS ? gDocsMatches.length : ROW_COUNT_DOCS;
1753 for (i=0; i<gDocsListLength; i++) {
1754 var sugg = gDocsMatches[i];
1755 var $li;
1756 if (sugg.type == "design") {
1757 $li = new_suggestion($(".suggest-card.design ul"));
1758 } else
1759 if (sugg.type == "distribute") {
1760 $li = new_suggestion($(".suggest-card.distribute ul"));
1761 } else
Scott Main719acb42013-12-05 16:05:09 -08001762 if (sugg.type == "samples") {
1763 $li = new_suggestion($(".suggest-card.develop .child-card.samples"));
1764 } else
Scott Main0e76e7e2013-03-12 10:24:07 -07001765 if (sugg.type == "training") {
1766 $li = new_suggestion($(".suggest-card.develop .child-card.training"));
1767 } else
Scott Main719acb42013-12-05 16:05:09 -08001768 if (sugg.type == "about"||"guide"||"tools"||"google") {
Scott Main0e76e7e2013-03-12 10:24:07 -07001769 $li = new_suggestion($(".suggest-card.develop .child-card.guides"));
1770 } else {
1771 continue;
1772 }
1773
Scott Main719acb42013-12-05 16:05:09 -08001774 set_item_values_jd(toroot, $li, sugg);
Scott Main0e76e7e2013-03-12 10:24:07 -07001775 set_item_selected($li, i == gSelectedIndex);
1776 }
1777
1778 // add heading and show or hide card
1779 if ($(".suggest-card.design li").length > 0) {
1780 $(".suggest-card.design ul").prepend("<li class='header'>Design:</li>");
1781 $(".suggest-card.design").show(300);
1782 } else {
1783 $('.suggest-card.design').hide(300);
1784 }
1785 if ($(".suggest-card.distribute li").length > 0) {
1786 $(".suggest-card.distribute ul").prepend("<li class='header'>Distribute:</li>");
1787 $(".suggest-card.distribute").show(300);
1788 } else {
1789 $('.suggest-card.distribute').hide(300);
1790 }
1791 if ($(".child-card.guides li").length > 0) {
1792 $(".child-card.guides").prepend("<li class='header'>Guides:</li>");
1793 $(".child-card.guides li").appendTo(".suggest-card.develop ul");
1794 }
1795 if ($(".child-card.training li").length > 0) {
1796 $(".child-card.training").prepend("<li class='header'>Training:</li>");
1797 $(".child-card.training li").appendTo(".suggest-card.develop ul");
1798 }
Scott Main719acb42013-12-05 16:05:09 -08001799 if ($(".child-card.samples li").length > 0) {
1800 $(".child-card.samples").prepend("<li class='header'>Samples:</li>");
1801 $(".child-card.samples li").appendTo(".suggest-card.develop ul");
1802 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001803
1804 if ($(".suggest-card.develop li").length > 0) {
1805 $(".suggest-card.develop").show(300);
1806 } else {
1807 $('.suggest-card.develop').hide(300);
1808 }
1809
1810 } else {
1811 $('.search_filtered_wrapper.docs .suggest-card:not(.dummy)').hide(300);
Scott Mainf5089842012-08-14 16:31:07 -07001812 }
1813}
1814
Scott Main0e76e7e2013-03-12 10:24:07 -07001815/** Called by the search input's onkeydown and onkeyup events.
1816 * Handles navigation with keyboard arrows, Enter key to invoke search,
1817 * otherwise invokes search suggestions on key-up event.
1818 * @param e The JS event
1819 * @param kd True if the event is key-down
Scott Main3b90aff2013-08-01 18:09:35 -07001820 * @param toroot A string for the site's root path
Scott Main0e76e7e2013-03-12 10:24:07 -07001821 * @returns True if the event should bubble up
1822 */
Scott Mainf5089842012-08-14 16:31:07 -07001823function search_changed(e, kd, toroot)
1824{
Scott Main719acb42013-12-05 16:05:09 -08001825 var currentLang = getLangPref();
Scott Mainf5089842012-08-14 16:31:07 -07001826 var search = document.getElementById("search_autocomplete");
1827 var text = search.value.replace(/(^ +)|( +$)/g, '');
Scott Main0e76e7e2013-03-12 10:24:07 -07001828 // get the ul hosting the currently selected item
1829 gSelectedColumn = gSelectedColumn >= 0 ? gSelectedColumn : 0;
1830 var $columns = $(".search_filtered_wrapper").find(".search_filtered:visible");
1831 var $selectedUl = $columns[gSelectedColumn];
1832
Scott Mainf5089842012-08-14 16:31:07 -07001833 // show/hide the close button
1834 if (text != '') {
1835 $(".search .close").removeClass("hide");
1836 } else {
1837 $(".search .close").addClass("hide");
1838 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001839 // 27 = esc
1840 if (e.keyCode == 27) {
1841 // close all search results
1842 if (kd) $('.search .close').trigger('click');
1843 return true;
1844 }
Scott Mainf5089842012-08-14 16:31:07 -07001845 // 13 = enter
Scott Main0e76e7e2013-03-12 10:24:07 -07001846 else if (e.keyCode == 13) {
1847 if (gSelectedIndex < 0) {
1848 $('.suggest-card').hide();
Scott Main7e447ed2013-02-19 17:22:37 -08001849 if ($("#searchResults").is(":hidden") && (search.value != "")) {
1850 // if results aren't showing (and text not empty), return true to allow search to execute
Dirk Doughertyc3921652014-05-13 16:55:26 -07001851 $('body,html').animate({scrollTop:0}, '500', 'swing');
Scott Mainf5089842012-08-14 16:31:07 -07001852 return true;
1853 } else {
1854 // otherwise, results are already showing, so allow ajax to auto refresh the results
1855 // and ignore this Enter press to avoid the reload.
1856 return false;
1857 }
1858 } else if (kd && gSelectedIndex >= 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001859 // click the link corresponding to selected item
1860 $("a",$("li",$selectedUl)[gSelectedIndex]).get()[0].click();
Scott Mainf5089842012-08-14 16:31:07 -07001861 return false;
1862 }
1863 }
Scott Mainb16376f2014-05-21 20:35:47 -07001864 // If Google results are showing, return true to allow ajax search to execute
Scott Main0e76e7e2013-03-12 10:24:07 -07001865 else if ($("#searchResults").is(":visible")) {
Scott Mainb16376f2014-05-21 20:35:47 -07001866 // Also, if search_results is scrolled out of view, scroll to top to make results visible
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001867 if ((sticky ) && (search.value != "")) {
1868 $('body,html').animate({scrollTop:0}, '500', 'swing');
1869 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001870 return true;
1871 }
1872 // 38 UP ARROW
Scott Mainf5089842012-08-14 16:31:07 -07001873 else if (kd && (e.keyCode == 38)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001874 // if the next item is a header, skip it
1875 if ($($("li", $selectedUl)[gSelectedIndex-1]).hasClass("header")) {
Scott Mainf5089842012-08-14 16:31:07 -07001876 gSelectedIndex--;
Scott Main7e447ed2013-02-19 17:22:37 -08001877 }
1878 if (gSelectedIndex >= 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001879 $('li', $selectedUl).removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001880 gSelectedIndex--;
Scott Main0e76e7e2013-03-12 10:24:07 -07001881 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
1882 // If user reaches top, reset selected column
1883 if (gSelectedIndex < 0) {
1884 gSelectedColumn = -1;
1885 }
Scott Mainf5089842012-08-14 16:31:07 -07001886 }
1887 return false;
1888 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001889 // 40 DOWN ARROW
Scott Mainf5089842012-08-14 16:31:07 -07001890 else if (kd && (e.keyCode == 40)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001891 // if the next item is a header, skip it
1892 if ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header")) {
Scott Mainf5089842012-08-14 16:31:07 -07001893 gSelectedIndex++;
Scott Main7e447ed2013-02-19 17:22:37 -08001894 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001895 if ((gSelectedIndex < $("li", $selectedUl).length-1) ||
1896 ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header"))) {
1897 $('li', $selectedUl).removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001898 gSelectedIndex++;
Scott Main0e76e7e2013-03-12 10:24:07 -07001899 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
Scott Mainf5089842012-08-14 16:31:07 -07001900 }
1901 return false;
1902 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001903 // Consider left/right arrow navigation
1904 // NOTE: Order of suggest columns are reverse order (index position 0 is on right)
1905 else if (kd && $columns.length > 1 && gSelectedColumn >= 0) {
1906 // 37 LEFT ARROW
1907 // go left only if current column is not left-most column (last column)
1908 if (e.keyCode == 37 && gSelectedColumn < $columns.length - 1) {
1909 $('li', $selectedUl).removeClass('jd-selected');
1910 gSelectedColumn++;
1911 $selectedUl = $columns[gSelectedColumn];
1912 // keep or reset the selected item to last item as appropriate
1913 gSelectedIndex = gSelectedIndex >
1914 $("li", $selectedUl).length-1 ?
1915 $("li", $selectedUl).length-1 : gSelectedIndex;
1916 // if the corresponding item is a header, move down
1917 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
1918 gSelectedIndex++;
1919 }
Scott Main3b90aff2013-08-01 18:09:35 -07001920 // set item selected
Scott Main0e76e7e2013-03-12 10:24:07 -07001921 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
1922 return false;
1923 }
1924 // 39 RIGHT ARROW
1925 // go right only if current column is not the right-most column (first column)
1926 else if (e.keyCode == 39 && gSelectedColumn > 0) {
1927 $('li', $selectedUl).removeClass('jd-selected');
1928 gSelectedColumn--;
1929 $selectedUl = $columns[gSelectedColumn];
1930 // keep or reset the selected item to last item as appropriate
1931 gSelectedIndex = gSelectedIndex >
1932 $("li", $selectedUl).length-1 ?
1933 $("li", $selectedUl).length-1 : gSelectedIndex;
1934 // if the corresponding item is a header, move down
1935 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
1936 gSelectedIndex++;
1937 }
Scott Main3b90aff2013-08-01 18:09:35 -07001938 // set item selected
Scott Main0e76e7e2013-03-12 10:24:07 -07001939 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
1940 return false;
1941 }
1942 }
1943
Scott Main719acb42013-12-05 16:05:09 -08001944 // if key-up event and not arrow down/up/left/right,
1945 // read the search query and add suggestions to gMatches
Scott Main0e76e7e2013-03-12 10:24:07 -07001946 else if (!kd && (e.keyCode != 40)
1947 && (e.keyCode != 38)
1948 && (e.keyCode != 37)
1949 && (e.keyCode != 39)) {
1950 gSelectedIndex = -1;
Scott Mainf5089842012-08-14 16:31:07 -07001951 gMatches = new Array();
1952 matchedCount = 0;
Scott Main7e447ed2013-02-19 17:22:37 -08001953 gGoogleMatches = new Array();
1954 matchedCountGoogle = 0;
Scott Main0e76e7e2013-03-12 10:24:07 -07001955 gDocsMatches = new Array();
1956 matchedCountDocs = 0;
Scott Main7e447ed2013-02-19 17:22:37 -08001957
1958 // Search for Android matches
Scott Mainf5089842012-08-14 16:31:07 -07001959 for (var i=0; i<DATA.length; i++) {
1960 var s = DATA[i];
1961 if (text.length != 0 &&
1962 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
1963 gMatches[matchedCount] = s;
1964 matchedCount++;
1965 }
1966 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001967 rank_autocomplete_api_results(text, gMatches);
Scott Mainf5089842012-08-14 16:31:07 -07001968 for (var i=0; i<gMatches.length; i++) {
1969 var s = gMatches[i];
Scott Main7e447ed2013-02-19 17:22:37 -08001970 }
1971
1972
1973 // Search for Google matches
1974 for (var i=0; i<GOOGLE_DATA.length; i++) {
1975 var s = GOOGLE_DATA[i];
1976 if (text.length != 0 &&
1977 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
1978 gGoogleMatches[matchedCountGoogle] = s;
1979 matchedCountGoogle++;
Scott Mainf5089842012-08-14 16:31:07 -07001980 }
1981 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001982 rank_autocomplete_api_results(text, gGoogleMatches);
Scott Main7e447ed2013-02-19 17:22:37 -08001983 for (var i=0; i<gGoogleMatches.length; i++) {
1984 var s = gGoogleMatches[i];
1985 }
1986
Scott Mainf5089842012-08-14 16:31:07 -07001987 highlight_autocomplete_result_labels(text);
Scott Main0e76e7e2013-03-12 10:24:07 -07001988
1989
1990
Scott Main719acb42013-12-05 16:05:09 -08001991 // Search for matching JD docs
Dirk Dougherty9b7f8f22014-11-01 17:08:56 -07001992 if (text.length >= 2) {
Scott Main719acb42013-12-05 16:05:09 -08001993 // Regex to match only the beginning of a word
1994 var textRegex = new RegExp("\\b" + text.toLowerCase(), "g");
1995
1996
1997 // Search for Training classes
1998 for (var i=0; i<TRAINING_RESOURCES.length; i++) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001999 // current search comparison, with counters for tag and title,
2000 // used later to improve ranking
Scott Main719acb42013-12-05 16:05:09 -08002001 var s = TRAINING_RESOURCES[i];
Scott Main0e76e7e2013-03-12 10:24:07 -07002002 s.matched_tag = 0;
2003 s.matched_title = 0;
2004 var matched = false;
2005
2006 // Check if query matches any tags; work backwards toward 1 to assist ranking
Scott Main719acb42013-12-05 16:05:09 -08002007 for (var j = s.keywords.length - 1; j >= 0; j--) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002008 // it matches a tag
Scott Main719acb42013-12-05 16:05:09 -08002009 if (s.keywords[j].toLowerCase().match(textRegex)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002010 matched = true;
2011 s.matched_tag = j + 1; // add 1 to index position
2012 }
2013 }
Scott Main719acb42013-12-05 16:05:09 -08002014 // Don't consider doc title for lessons (only for class landing pages),
2015 // unless the lesson has a tag that already matches
2016 if ((s.lang == currentLang) &&
2017 (!(s.type == "training" && s.url.indexOf("index.html") == -1) || matched)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002018 // it matches the doc title
Scott Main719acb42013-12-05 16:05:09 -08002019 if (s.title.toLowerCase().match(textRegex)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002020 matched = true;
2021 s.matched_title = 1;
2022 }
2023 }
2024 if (matched) {
2025 gDocsMatches[matchedCountDocs] = s;
2026 matchedCountDocs++;
2027 }
2028 }
Scott Main719acb42013-12-05 16:05:09 -08002029
2030
2031 // Search for API Guides
2032 for (var i=0; i<GUIDE_RESOURCES.length; i++) {
2033 // current search comparison, with counters for tag and title,
2034 // used later to improve ranking
2035 var s = GUIDE_RESOURCES[i];
2036 s.matched_tag = 0;
2037 s.matched_title = 0;
2038 var matched = false;
2039
2040 // Check if query matches any tags; work backwards toward 1 to assist ranking
2041 for (var j = s.keywords.length - 1; j >= 0; j--) {
2042 // it matches a tag
2043 if (s.keywords[j].toLowerCase().match(textRegex)) {
2044 matched = true;
2045 s.matched_tag = j + 1; // add 1 to index position
2046 }
2047 }
2048 // Check if query matches the doc title, but only for current language
2049 if (s.lang == currentLang) {
2050 // if query matches the doc title
2051 if (s.title.toLowerCase().match(textRegex)) {
2052 matched = true;
2053 s.matched_title = 1;
2054 }
2055 }
2056 if (matched) {
2057 gDocsMatches[matchedCountDocs] = s;
2058 matchedCountDocs++;
2059 }
2060 }
2061
2062
2063 // Search for Tools Guides
2064 for (var i=0; i<TOOLS_RESOURCES.length; i++) {
2065 // current search comparison, with counters for tag and title,
2066 // used later to improve ranking
2067 var s = TOOLS_RESOURCES[i];
2068 s.matched_tag = 0;
2069 s.matched_title = 0;
2070 var matched = false;
2071
2072 // Check if query matches any tags; work backwards toward 1 to assist ranking
2073 for (var j = s.keywords.length - 1; j >= 0; j--) {
2074 // it matches a tag
2075 if (s.keywords[j].toLowerCase().match(textRegex)) {
2076 matched = true;
2077 s.matched_tag = j + 1; // add 1 to index position
2078 }
2079 }
2080 // Check if query matches the doc title, but only for current language
2081 if (s.lang == currentLang) {
2082 // if query matches the doc title
2083 if (s.title.toLowerCase().match(textRegex)) {
2084 matched = true;
2085 s.matched_title = 1;
2086 }
2087 }
2088 if (matched) {
2089 gDocsMatches[matchedCountDocs] = s;
2090 matchedCountDocs++;
2091 }
2092 }
2093
2094
2095 // Search for About docs
2096 for (var i=0; i<ABOUT_RESOURCES.length; i++) {
2097 // current search comparison, with counters for tag and title,
2098 // used later to improve ranking
2099 var s = ABOUT_RESOURCES[i];
2100 s.matched_tag = 0;
2101 s.matched_title = 0;
2102 var matched = false;
2103
2104 // Check if query matches any tags; work backwards toward 1 to assist ranking
2105 for (var j = s.keywords.length - 1; j >= 0; j--) {
2106 // it matches a tag
2107 if (s.keywords[j].toLowerCase().match(textRegex)) {
2108 matched = true;
2109 s.matched_tag = j + 1; // add 1 to index position
2110 }
2111 }
2112 // Check if query matches the doc title, but only for current language
2113 if (s.lang == currentLang) {
2114 // if query matches the doc title
2115 if (s.title.toLowerCase().match(textRegex)) {
2116 matched = true;
2117 s.matched_title = 1;
2118 }
2119 }
2120 if (matched) {
2121 gDocsMatches[matchedCountDocs] = s;
2122 matchedCountDocs++;
2123 }
2124 }
2125
2126
2127 // Search for Design guides
2128 for (var i=0; i<DESIGN_RESOURCES.length; i++) {
2129 // current search comparison, with counters for tag and title,
2130 // used later to improve ranking
2131 var s = DESIGN_RESOURCES[i];
2132 s.matched_tag = 0;
2133 s.matched_title = 0;
2134 var matched = false;
2135
2136 // Check if query matches any tags; work backwards toward 1 to assist ranking
2137 for (var j = s.keywords.length - 1; j >= 0; j--) {
2138 // it matches a tag
2139 if (s.keywords[j].toLowerCase().match(textRegex)) {
2140 matched = true;
2141 s.matched_tag = j + 1; // add 1 to index position
2142 }
2143 }
2144 // Check if query matches the doc title, but only for current language
2145 if (s.lang == currentLang) {
2146 // if query matches the doc title
2147 if (s.title.toLowerCase().match(textRegex)) {
2148 matched = true;
2149 s.matched_title = 1;
2150 }
2151 }
2152 if (matched) {
2153 gDocsMatches[matchedCountDocs] = s;
2154 matchedCountDocs++;
2155 }
2156 }
2157
2158
2159 // Search for Distribute guides
2160 for (var i=0; i<DISTRIBUTE_RESOURCES.length; i++) {
2161 // current search comparison, with counters for tag and title,
2162 // used later to improve ranking
2163 var s = DISTRIBUTE_RESOURCES[i];
2164 s.matched_tag = 0;
2165 s.matched_title = 0;
2166 var matched = false;
2167
2168 // Check if query matches any tags; work backwards toward 1 to assist ranking
2169 for (var j = s.keywords.length - 1; j >= 0; j--) {
2170 // it matches a tag
2171 if (s.keywords[j].toLowerCase().match(textRegex)) {
2172 matched = true;
2173 s.matched_tag = j + 1; // add 1 to index position
2174 }
2175 }
2176 // Check if query matches the doc title, but only for current language
2177 if (s.lang == currentLang) {
2178 // if query matches the doc title
2179 if (s.title.toLowerCase().match(textRegex)) {
2180 matched = true;
2181 s.matched_title = 1;
2182 }
2183 }
2184 if (matched) {
2185 gDocsMatches[matchedCountDocs] = s;
2186 matchedCountDocs++;
2187 }
2188 }
2189
2190
2191 // Search for Google guides
2192 for (var i=0; i<GOOGLE_RESOURCES.length; i++) {
2193 // current search comparison, with counters for tag and title,
2194 // used later to improve ranking
2195 var s = GOOGLE_RESOURCES[i];
2196 s.matched_tag = 0;
2197 s.matched_title = 0;
2198 var matched = false;
2199
2200 // Check if query matches any tags; work backwards toward 1 to assist ranking
2201 for (var j = s.keywords.length - 1; j >= 0; j--) {
2202 // it matches a tag
2203 if (s.keywords[j].toLowerCase().match(textRegex)) {
2204 matched = true;
2205 s.matched_tag = j + 1; // add 1 to index position
2206 }
2207 }
2208 // Check if query matches the doc title, but only for current language
2209 if (s.lang == currentLang) {
2210 // if query matches the doc title
2211 if (s.title.toLowerCase().match(textRegex)) {
2212 matched = true;
2213 s.matched_title = 1;
2214 }
2215 }
2216 if (matched) {
2217 gDocsMatches[matchedCountDocs] = s;
2218 matchedCountDocs++;
2219 }
2220 }
2221
2222
2223 // Search for Samples
2224 for (var i=0; i<SAMPLES_RESOURCES.length; i++) {
2225 // current search comparison, with counters for tag and title,
2226 // used later to improve ranking
2227 var s = SAMPLES_RESOURCES[i];
2228 s.matched_tag = 0;
2229 s.matched_title = 0;
2230 var matched = false;
2231 // Check if query matches any tags; work backwards toward 1 to assist ranking
2232 for (var j = s.keywords.length - 1; j >= 0; j--) {
2233 // it matches a tag
2234 if (s.keywords[j].toLowerCase().match(textRegex)) {
2235 matched = true;
2236 s.matched_tag = j + 1; // add 1 to index position
2237 }
2238 }
2239 // Check if query matches the doc title, but only for current language
2240 if (s.lang == currentLang) {
2241 // if query matches the doc title.t
2242 if (s.title.toLowerCase().match(textRegex)) {
2243 matched = true;
2244 s.matched_title = 1;
2245 }
2246 }
2247 if (matched) {
2248 gDocsMatches[matchedCountDocs] = s;
2249 matchedCountDocs++;
2250 }
2251 }
2252
2253 // Rank/sort all the matched pages
Scott Main0e76e7e2013-03-12 10:24:07 -07002254 rank_autocomplete_doc_results(text, gDocsMatches);
2255 }
2256
2257 // draw the suggestions
Scott Mainf5089842012-08-14 16:31:07 -07002258 sync_selection_table(toroot);
2259 return true; // allow the event to bubble up to the search api
2260 }
2261}
2262
Scott Main0e76e7e2013-03-12 10:24:07 -07002263/* Order the jd doc result list based on match quality */
2264function rank_autocomplete_doc_results(query, matches) {
2265 query = query || '';
2266 if (!matches || !matches.length)
2267 return;
2268
2269 var _resultScoreFn = function(match) {
2270 var score = 1.0;
2271
2272 // if the query matched a tag
2273 if (match.matched_tag > 0) {
2274 // multiply score by factor relative to position in tags list (max of 3)
2275 score *= 3 / match.matched_tag;
2276
2277 // if it also matched the title
2278 if (match.matched_title > 0) {
2279 score *= 2;
2280 }
2281 } else if (match.matched_title > 0) {
2282 score *= 3;
2283 }
2284
2285 return score;
2286 };
2287
2288 for (var i=0; i<matches.length; i++) {
2289 matches[i].__resultScore = _resultScoreFn(matches[i]);
2290 }
2291
2292 matches.sort(function(a,b){
2293 var n = b.__resultScore - a.__resultScore;
2294 if (n == 0) // lexicographical sort if scores are the same
2295 n = (a.label < b.label) ? -1 : 1;
2296 return n;
2297 });
2298}
2299
Scott Main7e447ed2013-02-19 17:22:37 -08002300/* Order the result list based on match quality */
Scott Main0e76e7e2013-03-12 10:24:07 -07002301function rank_autocomplete_api_results(query, matches) {
Scott Mainf5089842012-08-14 16:31:07 -07002302 query = query || '';
Scott Main7e447ed2013-02-19 17:22:37 -08002303 if (!matches || !matches.length)
Scott Mainf5089842012-08-14 16:31:07 -07002304 return;
2305
2306 // helper function that gets the last occurence index of the given regex
2307 // in the given string, or -1 if not found
2308 var _lastSearch = function(s, re) {
2309 if (s == '')
2310 return -1;
2311 var l = -1;
2312 var tmp;
2313 while ((tmp = s.search(re)) >= 0) {
2314 if (l < 0) l = 0;
2315 l += tmp;
2316 s = s.substr(tmp + 1);
2317 }
2318 return l;
2319 };
2320
2321 // helper function that counts the occurrences of a given character in
2322 // a given string
2323 var _countChar = function(s, c) {
2324 var n = 0;
2325 for (var i=0; i<s.length; i++)
2326 if (s.charAt(i) == c) ++n;
2327 return n;
2328 };
2329
2330 var queryLower = query.toLowerCase();
2331 var queryAlnum = (queryLower.match(/\w+/) || [''])[0];
2332 var partPrefixAlnumRE = new RegExp('\\b' + queryAlnum);
2333 var partExactAlnumRE = new RegExp('\\b' + queryAlnum + '\\b');
2334
2335 var _resultScoreFn = function(result) {
2336 // scores are calculated based on exact and prefix matches,
2337 // and then number of path separators (dots) from the last
2338 // match (i.e. favoring classes and deep package names)
2339 var score = 1.0;
2340 var labelLower = result.label.toLowerCase();
2341 var t;
2342 t = _lastSearch(labelLower, partExactAlnumRE);
2343 if (t >= 0) {
2344 // exact part match
2345 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2346 score *= 200 / (partsAfter + 1);
2347 } else {
2348 t = _lastSearch(labelLower, partPrefixAlnumRE);
2349 if (t >= 0) {
2350 // part prefix match
2351 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2352 score *= 20 / (partsAfter + 1);
2353 }
2354 }
2355
2356 return score;
2357 };
2358
Scott Main7e447ed2013-02-19 17:22:37 -08002359 for (var i=0; i<matches.length; i++) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002360 // if the API is deprecated, default score is 0; otherwise, perform scoring
2361 if (matches[i].deprecated == "true") {
2362 matches[i].__resultScore = 0;
2363 } else {
2364 matches[i].__resultScore = _resultScoreFn(matches[i]);
2365 }
Scott Mainf5089842012-08-14 16:31:07 -07002366 }
2367
Scott Main7e447ed2013-02-19 17:22:37 -08002368 matches.sort(function(a,b){
Scott Mainf5089842012-08-14 16:31:07 -07002369 var n = b.__resultScore - a.__resultScore;
2370 if (n == 0) // lexicographical sort if scores are the same
2371 n = (a.label < b.label) ? -1 : 1;
2372 return n;
2373 });
2374}
2375
Scott Main7e447ed2013-02-19 17:22:37 -08002376/* Add emphasis to part of string that matches query */
Scott Mainf5089842012-08-14 16:31:07 -07002377function highlight_autocomplete_result_labels(query) {
2378 query = query || '';
Scott Main7e447ed2013-02-19 17:22:37 -08002379 if ((!gMatches || !gMatches.length) && (!gGoogleMatches || !gGoogleMatches.length))
Scott Mainf5089842012-08-14 16:31:07 -07002380 return;
2381
2382 var queryLower = query.toLowerCase();
2383 var queryAlnumDot = (queryLower.match(/[\w\.]+/) || [''])[0];
2384 var queryRE = new RegExp(
2385 '(' + queryAlnumDot.replace(/\./g, '\\.') + ')', 'ig');
2386 for (var i=0; i<gMatches.length; i++) {
2387 gMatches[i].__hilabel = gMatches[i].label.replace(
2388 queryRE, '<b>$1</b>');
2389 }
Scott Main7e447ed2013-02-19 17:22:37 -08002390 for (var i=0; i<gGoogleMatches.length; i++) {
2391 gGoogleMatches[i].__hilabel = gGoogleMatches[i].label.replace(
2392 queryRE, '<b>$1</b>');
2393 }
Scott Mainf5089842012-08-14 16:31:07 -07002394}
2395
2396function search_focus_changed(obj, focused)
2397{
Scott Main3b90aff2013-08-01 18:09:35 -07002398 if (!focused) {
Scott Mainf5089842012-08-14 16:31:07 -07002399 if(obj.value == ""){
2400 $(".search .close").addClass("hide");
2401 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002402 $(".suggest-card").hide();
Scott Mainf5089842012-08-14 16:31:07 -07002403 }
2404}
2405
2406function submit_search() {
2407 var query = document.getElementById('search_autocomplete').value;
2408 location.hash = 'q=' + query;
2409 loadSearchResults();
Dirk Doughertyc3921652014-05-13 16:55:26 -07002410 $("#searchResults").slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002411 return false;
2412}
2413
2414
2415function hideResults() {
Dirk Doughertyc3921652014-05-13 16:55:26 -07002416 $("#searchResults").slideUp('fast', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002417 $(".search .close").addClass("hide");
2418 location.hash = '';
Scott Main3b90aff2013-08-01 18:09:35 -07002419
Scott Mainf5089842012-08-14 16:31:07 -07002420 $("#search_autocomplete").val("").blur();
Scott Main3b90aff2013-08-01 18:09:35 -07002421
Scott Mainf5089842012-08-14 16:31:07 -07002422 // reset the ajax search callback to nothing, so results don't appear unless ENTER
2423 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {});
Scott Main0e76e7e2013-03-12 10:24:07 -07002424
2425 // forcefully regain key-up event control (previously jacked by search api)
2426 $("#search_autocomplete").keyup(function(event) {
2427 return search_changed(event, false, toRoot);
2428 });
2429
Scott Mainf5089842012-08-14 16:31:07 -07002430 return false;
2431}
2432
2433
2434
2435/* ########################################################## */
2436/* ################ CUSTOM SEARCH ENGINE ################## */
2437/* ########################################################## */
2438
Scott Mainf5089842012-08-14 16:31:07 -07002439var searchControl;
Scott Main0e76e7e2013-03-12 10:24:07 -07002440google.load('search', '1', {"callback" : function() {
2441 searchControl = new google.search.SearchControl();
2442 } });
Scott Mainf5089842012-08-14 16:31:07 -07002443
2444function loadSearchResults() {
2445 document.getElementById("search_autocomplete").style.color = "#000";
2446
Scott Mainf5089842012-08-14 16:31:07 -07002447 searchControl = new google.search.SearchControl();
2448
2449 // use our existing search form and use tabs when multiple searchers are used
2450 drawOptions = new google.search.DrawOptions();
2451 drawOptions.setDrawMode(google.search.SearchControl.DRAW_MODE_TABBED);
2452 drawOptions.setInput(document.getElementById("search_autocomplete"));
2453
2454 // configure search result options
2455 searchOptions = new google.search.SearcherOptions();
2456 searchOptions.setExpandMode(GSearchControl.EXPAND_MODE_OPEN);
2457
2458 // configure each of the searchers, for each tab
2459 devSiteSearcher = new google.search.WebSearch();
2460 devSiteSearcher.setUserDefinedLabel("All");
2461 devSiteSearcher.setSiteRestriction("001482626316274216503:zu90b7s047u");
2462
2463 designSearcher = new google.search.WebSearch();
2464 designSearcher.setUserDefinedLabel("Design");
2465 designSearcher.setSiteRestriction("http://developer.android.com/design/");
2466
2467 trainingSearcher = new google.search.WebSearch();
2468 trainingSearcher.setUserDefinedLabel("Training");
2469 trainingSearcher.setSiteRestriction("http://developer.android.com/training/");
2470
2471 guidesSearcher = new google.search.WebSearch();
2472 guidesSearcher.setUserDefinedLabel("Guides");
2473 guidesSearcher.setSiteRestriction("http://developer.android.com/guide/");
2474
2475 referenceSearcher = new google.search.WebSearch();
2476 referenceSearcher.setUserDefinedLabel("Reference");
2477 referenceSearcher.setSiteRestriction("http://developer.android.com/reference/");
2478
Scott Maindf08ada2012-12-03 08:54:37 -08002479 googleSearcher = new google.search.WebSearch();
2480 googleSearcher.setUserDefinedLabel("Google Services");
2481 googleSearcher.setSiteRestriction("http://developer.android.com/google/");
2482
Scott Mainf5089842012-08-14 16:31:07 -07002483 blogSearcher = new google.search.WebSearch();
2484 blogSearcher.setUserDefinedLabel("Blog");
2485 blogSearcher.setSiteRestriction("http://android-developers.blogspot.com");
2486
2487 // add each searcher to the search control
2488 searchControl.addSearcher(devSiteSearcher, searchOptions);
2489 searchControl.addSearcher(designSearcher, searchOptions);
2490 searchControl.addSearcher(trainingSearcher, searchOptions);
2491 searchControl.addSearcher(guidesSearcher, searchOptions);
2492 searchControl.addSearcher(referenceSearcher, searchOptions);
Scott Maindf08ada2012-12-03 08:54:37 -08002493 searchControl.addSearcher(googleSearcher, searchOptions);
Scott Mainf5089842012-08-14 16:31:07 -07002494 searchControl.addSearcher(blogSearcher, searchOptions);
2495
2496 // configure result options
2497 searchControl.setResultSetSize(google.search.Search.LARGE_RESULTSET);
2498 searchControl.setLinkTarget(google.search.Search.LINK_TARGET_SELF);
2499 searchControl.setTimeoutInterval(google.search.SearchControl.TIMEOUT_SHORT);
2500 searchControl.setNoResultsString(google.search.SearchControl.NO_RESULTS_DEFAULT_STRING);
2501
2502 // upon ajax search, refresh the url and search title
2503 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {
2504 updateResultTitle(query);
2505 var query = document.getElementById('search_autocomplete').value;
2506 location.hash = 'q=' + query;
2507 });
2508
Scott Mainde295272013-03-25 15:48:35 -07002509 // once search results load, set up click listeners
2510 searchControl.setSearchCompleteCallback(this, function(control, searcher, query) {
2511 addResultClickListeners();
2512 });
2513
Scott Mainf5089842012-08-14 16:31:07 -07002514 // draw the search results box
2515 searchControl.draw(document.getElementById("leftSearchControl"), drawOptions);
2516
2517 // get query and execute the search
2518 searchControl.execute(decodeURI(getQuery(location.hash)));
2519
2520 document.getElementById("search_autocomplete").focus();
2521 addTabListeners();
2522}
2523// End of loadSearchResults
2524
2525
2526google.setOnLoadCallback(function(){
2527 if (location.hash.indexOf("q=") == -1) {
2528 // if there's no query in the url, don't search and make sure results are hidden
2529 $('#searchResults').hide();
2530 return;
2531 } else {
2532 // first time loading search results for this page
Dirk Doughertyc3921652014-05-13 16:55:26 -07002533 $('#searchResults').slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002534 $(".search .close").removeClass("hide");
2535 loadSearchResults();
2536 }
2537}, true);
2538
smain@google.com9a818f52014-10-03 09:25:59 -07002539/* Adjust the scroll position to account for sticky header, only if the hash matches an id.
2540 This does not handle <a name=""> tags. Some CSS fixes those, but only for reference docs. */
Scott Mainb16376f2014-05-21 20:35:47 -07002541function offsetScrollForSticky() {
smain@google.com11fc0092014-10-16 22:10:00 -07002542 // Ignore if there's no search bar (some special pages have no header)
2543 if ($("#search-container").length < 1) return;
2544
smain@google.com3b77ab52014-06-17 11:57:27 -07002545 var hash = escape(location.hash.substr(1));
2546 var $matchingElement = $("#"+hash);
smain@google.com08f336ea2014-10-03 17:40:00 -07002547 // Sanity check that there's an element with that ID on the page
2548 if ($matchingElement.length) {
Scott Mainb16376f2014-05-21 20:35:47 -07002549 // If the position of the target element is near the top of the page (<20px, where we expect it
2550 // to be because we need to move it down 60px to become in view), then move it down 60px
2551 if (Math.abs($matchingElement.offset().top - $(window).scrollTop()) < 20) {
2552 $(window).scrollTop($(window).scrollTop() - 60);
Scott Mainb16376f2014-05-21 20:35:47 -07002553 }
2554 }
2555}
2556
Scott Mainf5089842012-08-14 16:31:07 -07002557// when an event on the browser history occurs (back, forward, load) requery hash and do search
2558$(window).hashchange( function(){
smain@google.com2f077192014-10-09 18:04:10 -07002559 // Ignore if there's no search bar (some special pages have no header)
2560 if ($("#search-container").length < 1) return;
2561
Dirk Doughertyc3921652014-05-13 16:55:26 -07002562 // If the hash isn't a search query or there's an error in the query,
2563 // then adjust the scroll position to account for sticky header, then exit.
Scott Mainf5089842012-08-14 16:31:07 -07002564 if ((location.hash.indexOf("q=") == -1) || (query == "undefined")) {
2565 // If the results pane is open, close it.
2566 if (!$("#searchResults").is(":hidden")) {
2567 hideResults();
2568 }
Scott Mainb16376f2014-05-21 20:35:47 -07002569 offsetScrollForSticky();
Scott Mainf5089842012-08-14 16:31:07 -07002570 return;
2571 }
2572
2573 // Otherwise, we have a search to do
2574 var query = decodeURI(getQuery(location.hash));
2575 searchControl.execute(query);
Dirk Doughertyc3921652014-05-13 16:55:26 -07002576 $('#searchResults').slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002577 $("#search_autocomplete").focus();
2578 $(".search .close").removeClass("hide");
2579
2580 updateResultTitle(query);
2581});
2582
2583function updateResultTitle(query) {
2584 $("#searchTitle").html("Results for <em>" + escapeHTML(query) + "</em>");
2585}
2586
2587// forcefully regain key-up event control (previously jacked by search api)
2588$("#search_autocomplete").keyup(function(event) {
2589 return search_changed(event, false, toRoot);
2590});
2591
2592// add event listeners to each tab so we can track the browser history
2593function addTabListeners() {
2594 var tabHeaders = $(".gsc-tabHeader");
2595 for (var i = 0; i < tabHeaders.length; i++) {
2596 $(tabHeaders[i]).attr("id",i).click(function() {
2597 /*
2598 // make a copy of the page numbers for the search left pane
2599 setTimeout(function() {
2600 // remove any residual page numbers
2601 $('#searchResults .gsc-tabsArea .gsc-cursor-box.gs-bidi-start-align').remove();
Scott Main3b90aff2013-08-01 18:09:35 -07002602 // move the page numbers to the left position; make a clone,
Scott Mainf5089842012-08-14 16:31:07 -07002603 // because the element is drawn to the DOM only once
Scott Main3b90aff2013-08-01 18:09:35 -07002604 // and because we're going to remove it (previous line),
2605 // we need it to be available to move again as the user navigates
Scott Mainf5089842012-08-14 16:31:07 -07002606 $('#searchResults .gsc-webResult .gsc-cursor-box.gs-bidi-start-align:visible')
2607 .clone().appendTo('#searchResults .gsc-tabsArea');
2608 }, 200);
2609 */
2610 });
2611 }
2612 setTimeout(function(){$(tabHeaders[0]).click()},200);
2613}
2614
Scott Mainde295272013-03-25 15:48:35 -07002615// add analytics tracking events to each result link
2616function addResultClickListeners() {
2617 $("#searchResults a.gs-title").each(function(index, link) {
2618 // When user clicks enter for Google search results, track it
2619 $(link).click(function() {
smain@google.com633f3222014-10-03 15:49:45 -07002620 ga('send', 'event', 'Google Click', 'clicked: ' + $(this).text(),
2621 'from: ' + $("#search_autocomplete").val());
Scott Mainde295272013-03-25 15:48:35 -07002622 });
2623 });
2624}
2625
Scott Mainf5089842012-08-14 16:31:07 -07002626
2627function getQuery(hash) {
2628 var queryParts = hash.split('=');
2629 return queryParts[1];
2630}
2631
2632/* returns the given string with all HTML brackets converted to entities
2633 TODO: move this to the site's JS library */
2634function escapeHTML(string) {
2635 return string.replace(/</g,"&lt;")
2636 .replace(/>/g,"&gt;");
2637}
2638
2639
2640
2641
2642
2643
2644
2645/* ######################################################## */
2646/* ################# JAVADOC REFERENCE ################### */
2647/* ######################################################## */
2648
Scott Main65511c02012-09-07 15:51:32 -07002649/* Initialize some droiddoc stuff, but only if we're in the reference */
Scott Main52dd2062013-08-15 12:22:28 -07002650if (location.pathname.indexOf("/reference") == 0) {
2651 if(!(location.pathname.indexOf("/reference-gms/packages.html") == 0)
2652 && !(location.pathname.indexOf("/reference-gcm/packages.html") == 0)
2653 && !(location.pathname.indexOf("/reference/com/google") == 0)) {
Robert Ly67d75f12012-12-03 12:53:42 -08002654 $(document).ready(function() {
2655 // init available apis based on user pref
2656 changeApiLevel();
2657 initSidenavHeightResize()
2658 });
2659 }
Scott Main65511c02012-09-07 15:51:32 -07002660}
Scott Mainf5089842012-08-14 16:31:07 -07002661
2662var API_LEVEL_COOKIE = "api_level";
2663var minLevel = 1;
2664var maxLevel = 1;
2665
2666/******* SIDENAV DIMENSIONS ************/
Scott Main3b90aff2013-08-01 18:09:35 -07002667
Scott Mainf5089842012-08-14 16:31:07 -07002668 function initSidenavHeightResize() {
2669 // Change the drag bar size to nicely fit the scrollbar positions
2670 var $dragBar = $(".ui-resizable-s");
2671 $dragBar.css({'width': $dragBar.parent().width() - 5 + "px"});
Scott Main3b90aff2013-08-01 18:09:35 -07002672
2673 $( "#resize-packages-nav" ).resizable({
Scott Mainf5089842012-08-14 16:31:07 -07002674 containment: "#nav-panels",
2675 handles: "s",
2676 alsoResize: "#packages-nav",
2677 resize: function(event, ui) { resizeNav(); }, /* resize the nav while dragging */
2678 stop: function(event, ui) { saveNavPanels(); } /* once stopped, save the sizes to cookie */
2679 });
Scott Main3b90aff2013-08-01 18:09:35 -07002680
Scott Mainf5089842012-08-14 16:31:07 -07002681 }
Scott Main3b90aff2013-08-01 18:09:35 -07002682
Scott Mainf5089842012-08-14 16:31:07 -07002683function updateSidenavFixedWidth() {
Scott Mainf5257812014-05-22 17:26:38 -07002684 if (!sticky) return;
Scott Mainf5089842012-08-14 16:31:07 -07002685 $('#devdoc-nav').css({
2686 'width' : $('#side-nav').css('width'),
2687 'margin' : $('#side-nav').css('margin')
2688 });
2689 $('#devdoc-nav a.totop').css({'display':'block','width':$("#nav").innerWidth()+'px'});
Scott Main3b90aff2013-08-01 18:09:35 -07002690
Scott Mainf5089842012-08-14 16:31:07 -07002691 initSidenavHeightResize();
2692}
2693
2694function updateSidenavFullscreenWidth() {
Scott Mainf5257812014-05-22 17:26:38 -07002695 if (!sticky) return;
Scott Mainf5089842012-08-14 16:31:07 -07002696 $('#devdoc-nav').css({
2697 'width' : $('#side-nav').css('width'),
2698 'margin' : $('#side-nav').css('margin')
2699 });
2700 $('#devdoc-nav .totop').css({'left': 'inherit'});
Scott Main3b90aff2013-08-01 18:09:35 -07002701
Scott Mainf5089842012-08-14 16:31:07 -07002702 initSidenavHeightResize();
2703}
2704
2705function buildApiLevelSelector() {
2706 maxLevel = SINCE_DATA.length;
2707 var userApiLevel = parseInt(readCookie(API_LEVEL_COOKIE));
2708 userApiLevel = userApiLevel == 0 ? maxLevel : userApiLevel; // If there's no cookie (zero), use the max by default
2709
2710 minLevel = parseInt($("#doc-api-level").attr("class"));
2711 // Handle provisional api levels; the provisional level will always be the highest possible level
2712 // Provisional api levels will also have a length; other stuff that's just missing a level won't,
2713 // so leave those kinds of entities at the default level of 1 (for example, the R.styleable class)
2714 if (isNaN(minLevel) && minLevel.length) {
2715 minLevel = maxLevel;
2716 }
2717 var select = $("#apiLevelSelector").html("").change(changeApiLevel);
2718 for (var i = maxLevel-1; i >= 0; i--) {
2719 var option = $("<option />").attr("value",""+SINCE_DATA[i]).append(""+SINCE_DATA[i]);
2720 // if (SINCE_DATA[i] < minLevel) option.addClass("absent"); // always false for strings (codenames)
2721 select.append(option);
2722 }
2723
2724 // get the DOM element and use setAttribute cuz IE6 fails when using jquery .attr('selected',true)
2725 var selectedLevelItem = $("#apiLevelSelector option[value='"+userApiLevel+"']").get(0);
2726 selectedLevelItem.setAttribute('selected',true);
2727}
2728
2729function changeApiLevel() {
2730 maxLevel = SINCE_DATA.length;
2731 var selectedLevel = maxLevel;
2732
2733 selectedLevel = parseInt($("#apiLevelSelector option:selected").val());
2734 toggleVisisbleApis(selectedLevel, "body");
2735
smain@google.com6bdcb982014-11-14 11:53:07 -08002736 writeCookie(API_LEVEL_COOKIE, selectedLevel, null);
Scott Mainf5089842012-08-14 16:31:07 -07002737
2738 if (selectedLevel < minLevel) {
2739 var thing = ($("#jd-header").html().indexOf("package") != -1) ? "package" : "class";
Scott Main8f24ca82012-11-16 10:34:22 -08002740 $("#naMessage").show().html("<div><p><strong>This " + thing
2741 + " requires API level " + minLevel + " or higher.</strong></p>"
2742 + "<p>This document is hidden because your selected API level for the documentation is "
2743 + selectedLevel + ". You can change the documentation API level with the selector "
2744 + "above the left navigation.</p>"
2745 + "<p>For more information about specifying the API level your app requires, "
2746 + "read <a href='" + toRoot + "training/basics/supporting-devices/platforms.html'"
2747 + ">Supporting Different Platform Versions</a>.</p>"
2748 + "<input type='button' value='OK, make this page visible' "
2749 + "title='Change the API level to " + minLevel + "' "
2750 + "onclick='$(\"#apiLevelSelector\").val(\"" + minLevel + "\");changeApiLevel();' />"
2751 + "</div>");
Scott Mainf5089842012-08-14 16:31:07 -07002752 } else {
2753 $("#naMessage").hide();
2754 }
2755}
2756
2757function toggleVisisbleApis(selectedLevel, context) {
2758 var apis = $(".api",context);
2759 apis.each(function(i) {
2760 var obj = $(this);
2761 var className = obj.attr("class");
2762 var apiLevelIndex = className.lastIndexOf("-")+1;
2763 var apiLevelEndIndex = className.indexOf(" ", apiLevelIndex);
2764 apiLevelEndIndex = apiLevelEndIndex != -1 ? apiLevelEndIndex : className.length;
2765 var apiLevel = className.substring(apiLevelIndex, apiLevelEndIndex);
2766 if (apiLevel.length == 0) { // for odd cases when the since data is actually missing, just bail
2767 return;
2768 }
2769 apiLevel = parseInt(apiLevel);
2770
2771 // Handle provisional api levels; if this item's level is the provisional one, set it to the max
2772 var selectedLevelNum = parseInt(selectedLevel)
2773 var apiLevelNum = parseInt(apiLevel);
2774 if (isNaN(apiLevelNum)) {
2775 apiLevelNum = maxLevel;
2776 }
2777
2778 // Grey things out that aren't available and give a tooltip title
2779 if (apiLevelNum > selectedLevelNum) {
2780 obj.addClass("absent").attr("title","Requires API Level \""
Scott Main641c2c22013-10-31 14:48:45 -07002781 + apiLevel + "\" or higher. To reveal, change the target API level "
2782 + "above the left navigation.");
Scott Main3b90aff2013-08-01 18:09:35 -07002783 }
Scott Mainf5089842012-08-14 16:31:07 -07002784 else obj.removeClass("absent").removeAttr("title");
2785 });
2786}
2787
2788
2789
2790
2791/* ################# SIDENAV TREE VIEW ################### */
2792
2793function new_node(me, mom, text, link, children_data, api_level)
2794{
2795 var node = new Object();
2796 node.children = Array();
2797 node.children_data = children_data;
2798 node.depth = mom.depth + 1;
2799
2800 node.li = document.createElement("li");
2801 mom.get_children_ul().appendChild(node.li);
2802
2803 node.label_div = document.createElement("div");
2804 node.label_div.className = "label";
2805 if (api_level != null) {
2806 $(node.label_div).addClass("api");
2807 $(node.label_div).addClass("api-level-"+api_level);
2808 }
2809 node.li.appendChild(node.label_div);
2810
2811 if (children_data != null) {
2812 node.expand_toggle = document.createElement("a");
2813 node.expand_toggle.href = "javascript:void(0)";
2814 node.expand_toggle.onclick = function() {
2815 if (node.expanded) {
2816 $(node.get_children_ul()).slideUp("fast");
2817 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2818 node.expanded = false;
2819 } else {
2820 expand_node(me, node);
2821 }
2822 };
2823 node.label_div.appendChild(node.expand_toggle);
2824
2825 node.plus_img = document.createElement("img");
2826 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2827 node.plus_img.className = "plus";
2828 node.plus_img.width = "8";
2829 node.plus_img.border = "0";
2830 node.expand_toggle.appendChild(node.plus_img);
2831
2832 node.expanded = false;
2833 }
2834
2835 var a = document.createElement("a");
2836 node.label_div.appendChild(a);
2837 node.label = document.createTextNode(text);
2838 a.appendChild(node.label);
2839 if (link) {
2840 a.href = me.toroot + link;
2841 } else {
2842 if (children_data != null) {
2843 a.className = "nolink";
2844 a.href = "javascript:void(0)";
2845 a.onclick = node.expand_toggle.onclick;
2846 // This next line shouldn't be necessary. I'll buy a beer for the first
2847 // person who figures out how to remove this line and have the link
2848 // toggle shut on the first try. --joeo@android.com
2849 node.expanded = false;
2850 }
2851 }
Scott Main3b90aff2013-08-01 18:09:35 -07002852
Scott Mainf5089842012-08-14 16:31:07 -07002853
2854 node.children_ul = null;
2855 node.get_children_ul = function() {
2856 if (!node.children_ul) {
2857 node.children_ul = document.createElement("ul");
2858 node.children_ul.className = "children_ul";
2859 node.children_ul.style.display = "none";
2860 node.li.appendChild(node.children_ul);
2861 }
2862 return node.children_ul;
2863 };
2864
2865 return node;
2866}
2867
Robert Lyd2dd6e52012-11-29 21:28:48 -08002868
2869
2870
Scott Mainf5089842012-08-14 16:31:07 -07002871function expand_node(me, node)
2872{
2873 if (node.children_data && !node.expanded) {
2874 if (node.children_visited) {
2875 $(node.get_children_ul()).slideDown("fast");
2876 } else {
2877 get_node(me, node);
2878 if ($(node.label_div).hasClass("absent")) {
2879 $(node.get_children_ul()).addClass("absent");
Scott Main3b90aff2013-08-01 18:09:35 -07002880 }
Scott Mainf5089842012-08-14 16:31:07 -07002881 $(node.get_children_ul()).slideDown("fast");
2882 }
2883 node.plus_img.src = me.toroot + "assets/images/triangle-opened-small.png";
2884 node.expanded = true;
2885
2886 // perform api level toggling because new nodes are new to the DOM
2887 var selectedLevel = $("#apiLevelSelector option:selected").val();
2888 toggleVisisbleApis(selectedLevel, "#side-nav");
2889 }
2890}
2891
2892function get_node(me, mom)
2893{
2894 mom.children_visited = true;
2895 for (var i in mom.children_data) {
2896 var node_data = mom.children_data[i];
2897 mom.children[i] = new_node(me, mom, node_data[0], node_data[1],
2898 node_data[2], node_data[3]);
2899 }
2900}
2901
2902function this_page_relative(toroot)
2903{
2904 var full = document.location.pathname;
2905 var file = "";
2906 if (toroot.substr(0, 1) == "/") {
2907 if (full.substr(0, toroot.length) == toroot) {
2908 return full.substr(toroot.length);
2909 } else {
2910 // the file isn't under toroot. Fail.
2911 return null;
2912 }
2913 } else {
2914 if (toroot != "./") {
2915 toroot = "./" + toroot;
2916 }
2917 do {
2918 if (toroot.substr(toroot.length-3, 3) == "../" || toroot == "./") {
2919 var pos = full.lastIndexOf("/");
2920 file = full.substr(pos) + file;
2921 full = full.substr(0, pos);
2922 toroot = toroot.substr(0, toroot.length-3);
2923 }
2924 } while (toroot != "" && toroot != "/");
2925 return file.substr(1);
2926 }
2927}
2928
2929function find_page(url, data)
2930{
2931 var nodes = data;
2932 var result = null;
2933 for (var i in nodes) {
2934 var d = nodes[i];
2935 if (d[1] == url) {
2936 return new Array(i);
2937 }
2938 else if (d[2] != null) {
2939 result = find_page(url, d[2]);
2940 if (result != null) {
2941 return (new Array(i).concat(result));
2942 }
2943 }
2944 }
2945 return null;
2946}
2947
Scott Mainf5089842012-08-14 16:31:07 -07002948function init_default_navtree(toroot) {
Scott Main25e73002013-03-27 15:24:06 -07002949 // load json file for navtree data
2950 $.getScript(toRoot + 'navtree_data.js', function(data, textStatus, jqxhr) {
2951 // when the file is loaded, initialize the tree
2952 if(jqxhr.status === 200) {
2953 init_navtree("tree-list", toroot, NAVTREE_DATA);
2954 }
2955 });
Scott Main3b90aff2013-08-01 18:09:35 -07002956
Scott Mainf5089842012-08-14 16:31:07 -07002957 // perform api level toggling because because the whole tree is new to the DOM
2958 var selectedLevel = $("#apiLevelSelector option:selected").val();
2959 toggleVisisbleApis(selectedLevel, "#side-nav");
2960}
2961
2962function init_navtree(navtree_id, toroot, root_nodes)
2963{
2964 var me = new Object();
2965 me.toroot = toroot;
2966 me.node = new Object();
2967
2968 me.node.li = document.getElementById(navtree_id);
2969 me.node.children_data = root_nodes;
2970 me.node.children = new Array();
2971 me.node.children_ul = document.createElement("ul");
2972 me.node.get_children_ul = function() { return me.node.children_ul; };
2973 //me.node.children_ul.className = "children_ul";
2974 me.node.li.appendChild(me.node.children_ul);
2975 me.node.depth = 0;
2976
2977 get_node(me, me.node);
2978
2979 me.this_page = this_page_relative(toroot);
2980 me.breadcrumbs = find_page(me.this_page, root_nodes);
2981 if (me.breadcrumbs != null && me.breadcrumbs.length != 0) {
2982 var mom = me.node;
2983 for (var i in me.breadcrumbs) {
2984 var j = me.breadcrumbs[i];
2985 mom = mom.children[j];
2986 expand_node(me, mom);
2987 }
2988 mom.label_div.className = mom.label_div.className + " selected";
2989 addLoadEvent(function() {
2990 scrollIntoView("nav-tree");
2991 });
2992 }
2993}
2994
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07002995
2996
2997
2998
2999
3000
3001
Robert Lyd2dd6e52012-11-29 21:28:48 -08003002/* TODO: eliminate redundancy with non-google functions */
3003function init_google_navtree(navtree_id, toroot, root_nodes)
3004{
3005 var me = new Object();
3006 me.toroot = toroot;
3007 me.node = new Object();
3008
3009 me.node.li = document.getElementById(navtree_id);
3010 me.node.children_data = root_nodes;
3011 me.node.children = new Array();
3012 me.node.children_ul = document.createElement("ul");
3013 me.node.get_children_ul = function() { return me.node.children_ul; };
3014 //me.node.children_ul.className = "children_ul";
3015 me.node.li.appendChild(me.node.children_ul);
3016 me.node.depth = 0;
3017
3018 get_google_node(me, me.node);
Robert Lyd2dd6e52012-11-29 21:28:48 -08003019}
3020
3021function new_google_node(me, mom, text, link, children_data, api_level)
3022{
3023 var node = new Object();
3024 var child;
3025 node.children = Array();
3026 node.children_data = children_data;
3027 node.depth = mom.depth + 1;
3028 node.get_children_ul = function() {
3029 if (!node.children_ul) {
Scott Main3b90aff2013-08-01 18:09:35 -07003030 node.children_ul = document.createElement("ul");
3031 node.children_ul.className = "tree-list-children";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003032 node.li.appendChild(node.children_ul);
3033 }
3034 return node.children_ul;
3035 };
3036 node.li = document.createElement("li");
3037
3038 mom.get_children_ul().appendChild(node.li);
Scott Main3b90aff2013-08-01 18:09:35 -07003039
3040
Robert Lyd2dd6e52012-11-29 21:28:48 -08003041 if(link) {
3042 child = document.createElement("a");
3043
3044 }
3045 else {
3046 child = document.createElement("span");
Scott Mainac71b2b2012-11-30 14:40:58 -08003047 child.className = "tree-list-subtitle";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003048
3049 }
3050 if (children_data != null) {
3051 node.li.className="nav-section";
3052 node.label_div = document.createElement("div");
Scott Main3b90aff2013-08-01 18:09:35 -07003053 node.label_div.className = "nav-section-header-ref";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003054 node.li.appendChild(node.label_div);
3055 get_google_node(me, node);
3056 node.label_div.appendChild(child);
3057 }
3058 else {
3059 node.li.appendChild(child);
3060 }
3061 if(link) {
3062 child.href = me.toroot + link;
3063 }
3064 node.label = document.createTextNode(text);
3065 child.appendChild(node.label);
3066
3067 node.children_ul = null;
3068
3069 return node;
3070}
3071
3072function get_google_node(me, mom)
3073{
3074 mom.children_visited = true;
3075 var linkText;
3076 for (var i in mom.children_data) {
3077 var node_data = mom.children_data[i];
3078 linkText = node_data[0];
3079
3080 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3081 linkText = linkText.substr(19, linkText.length);
3082 }
3083 mom.children[i] = new_google_node(me, mom, linkText, node_data[1],
3084 node_data[2], node_data[3]);
3085 }
3086}
Scott Mainad08f072013-08-20 16:49:57 -07003087
3088
3089
3090
3091
3092
3093/****** NEW version of script to build google and sample navs dynamically ******/
3094// TODO: update Google reference docs to tolerate this new implementation
3095
Scott Maine624b3f2013-09-12 12:56:41 -07003096var NODE_NAME = 0;
3097var NODE_HREF = 1;
3098var NODE_GROUP = 2;
3099var NODE_TAGS = 3;
3100var NODE_CHILDREN = 4;
3101
Scott Mainad08f072013-08-20 16:49:57 -07003102function init_google_navtree2(navtree_id, data)
3103{
3104 var $containerUl = $("#"+navtree_id);
Scott Mainad08f072013-08-20 16:49:57 -07003105 for (var i in data) {
3106 var node_data = data[i];
3107 $containerUl.append(new_google_node2(node_data));
3108 }
3109
Scott Main70557ee2013-10-30 14:47:40 -07003110 // Make all third-generation list items 'sticky' to prevent them from collapsing
3111 $containerUl.find('li li li.nav-section').addClass('sticky');
3112
Scott Mainad08f072013-08-20 16:49:57 -07003113 initExpandableNavItems("#"+navtree_id);
3114}
3115
3116function new_google_node2(node_data)
3117{
Scott Maine624b3f2013-09-12 12:56:41 -07003118 var linkText = node_data[NODE_NAME];
Scott Mainad08f072013-08-20 16:49:57 -07003119 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3120 linkText = linkText.substr(19, linkText.length);
3121 }
3122 var $li = $('<li>');
3123 var $a;
Scott Maine624b3f2013-09-12 12:56:41 -07003124 if (node_data[NODE_HREF] != null) {
Scott Main70557ee2013-10-30 14:47:40 -07003125 $a = $('<a href="' + toRoot + node_data[NODE_HREF] + '" title="' + linkText + '" >'
3126 + linkText + '</a>');
Scott Mainad08f072013-08-20 16:49:57 -07003127 } else {
Scott Main70557ee2013-10-30 14:47:40 -07003128 $a = $('<a href="#" onclick="return false;" title="' + linkText + '" >'
3129 + linkText + '/</a>');
Scott Mainad08f072013-08-20 16:49:57 -07003130 }
3131 var $childUl = $('<ul>');
Scott Maine624b3f2013-09-12 12:56:41 -07003132 if (node_data[NODE_CHILDREN] != null) {
Scott Mainad08f072013-08-20 16:49:57 -07003133 $li.addClass("nav-section");
3134 $a = $('<div class="nav-section-header">').append($a);
Scott Maine624b3f2013-09-12 12:56:41 -07003135 if (node_data[NODE_HREF] == null) $a.addClass('empty');
Scott Mainad08f072013-08-20 16:49:57 -07003136
Scott Maine624b3f2013-09-12 12:56:41 -07003137 for (var i in node_data[NODE_CHILDREN]) {
3138 var child_node_data = node_data[NODE_CHILDREN][i];
Scott Mainad08f072013-08-20 16:49:57 -07003139 $childUl.append(new_google_node2(child_node_data));
3140 }
3141 $li.append($childUl);
3142 }
3143 $li.prepend($a);
3144
3145 return $li;
3146}
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
Robert Lyd2dd6e52012-11-29 21:28:48 -08003158function showGoogleRefTree() {
3159 init_default_google_navtree(toRoot);
3160 init_default_gcm_navtree(toRoot);
Robert Lyd2dd6e52012-11-29 21:28:48 -08003161}
3162
3163function init_default_google_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07003164 // load json file for navtree data
3165 $.getScript(toRoot + 'gms_navtree_data.js', function(data, textStatus, jqxhr) {
3166 // when the file is loaded, initialize the tree
3167 if(jqxhr.status === 200) {
3168 init_google_navtree("gms-tree-list", toroot, GMS_NAVTREE_DATA);
3169 highlightSidenav();
3170 resizeNav();
3171 }
3172 });
Robert Lyd2dd6e52012-11-29 21:28:48 -08003173}
3174
3175function init_default_gcm_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07003176 // load json file for navtree data
3177 $.getScript(toRoot + 'gcm_navtree_data.js', function(data, textStatus, jqxhr) {
3178 // when the file is loaded, initialize the tree
3179 if(jqxhr.status === 200) {
3180 init_google_navtree("gcm-tree-list", toroot, GCM_NAVTREE_DATA);
3181 highlightSidenav();
3182 resizeNav();
3183 }
3184 });
Robert Lyd2dd6e52012-11-29 21:28:48 -08003185}
3186
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003187function showSamplesRefTree() {
3188 init_default_samples_navtree(toRoot);
3189}
3190
3191function init_default_samples_navtree(toroot) {
3192 // load json file for navtree data
3193 $.getScript(toRoot + 'samples_navtree_data.js', function(data, textStatus, jqxhr) {
3194 // when the file is loaded, initialize the tree
3195 if(jqxhr.status === 200) {
Scott Mainf1435b72013-10-30 16:27:38 -07003196 // hack to remove the "about the samples" link then put it back in
3197 // after we nuke the list to remove the dummy static list of samples
3198 var $firstLi = $("#nav.samples-nav > li:first-child").clone();
3199 $("#nav.samples-nav").empty();
3200 $("#nav.samples-nav").append($firstLi);
3201
Scott Mainad08f072013-08-20 16:49:57 -07003202 init_google_navtree2("nav.samples-nav", SAMPLES_NAVTREE_DATA);
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003203 highlightSidenav();
3204 resizeNav();
Scott Main03aca9a2013-10-31 07:20:55 -07003205 if ($("#jd-content #samples").length) {
3206 showSamples();
3207 }
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003208 }
3209 });
3210}
3211
Scott Mainf5089842012-08-14 16:31:07 -07003212/* TOGGLE INHERITED MEMBERS */
3213
3214/* Toggle an inherited class (arrow toggle)
3215 * @param linkObj The link that was clicked.
3216 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3217 * 'null' to simply toggle.
3218 */
3219function toggleInherited(linkObj, expand) {
3220 var base = linkObj.getAttribute("id");
3221 var list = document.getElementById(base + "-list");
3222 var summary = document.getElementById(base + "-summary");
3223 var trigger = document.getElementById(base + "-trigger");
3224 var a = $(linkObj);
3225 if ( (expand == null && a.hasClass("closed")) || expand ) {
3226 list.style.display = "none";
3227 summary.style.display = "block";
3228 trigger.src = toRoot + "assets/images/triangle-opened.png";
3229 a.removeClass("closed");
3230 a.addClass("opened");
3231 } else if ( (expand == null && a.hasClass("opened")) || (expand == false) ) {
3232 list.style.display = "block";
3233 summary.style.display = "none";
3234 trigger.src = toRoot + "assets/images/triangle-closed.png";
3235 a.removeClass("opened");
3236 a.addClass("closed");
3237 }
3238 return false;
3239}
3240
3241/* Toggle all inherited classes in a single table (e.g. all inherited methods)
3242 * @param linkObj The link that was clicked.
3243 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3244 * 'null' to simply toggle.
3245 */
3246function toggleAllInherited(linkObj, expand) {
3247 var a = $(linkObj);
3248 var table = $(a.parent().parent().parent()); // ugly way to get table/tbody
3249 var expandos = $(".jd-expando-trigger", table);
3250 if ( (expand == null && a.text() == "[Expand]") || expand ) {
3251 expandos.each(function(i) {
3252 toggleInherited(this, true);
3253 });
3254 a.text("[Collapse]");
3255 } else if ( (expand == null && a.text() == "[Collapse]") || (expand == false) ) {
3256 expandos.each(function(i) {
3257 toggleInherited(this, false);
3258 });
3259 a.text("[Expand]");
3260 }
3261 return false;
3262}
3263
3264/* Toggle all inherited members in the class (link in the class title)
3265 */
3266function toggleAllClassInherited() {
3267 var a = $("#toggleAllClassInherited"); // get toggle link from class title
3268 var toggles = $(".toggle-all", $("#body-content"));
3269 if (a.text() == "[Expand All]") {
3270 toggles.each(function(i) {
3271 toggleAllInherited(this, true);
3272 });
3273 a.text("[Collapse All]");
3274 } else {
3275 toggles.each(function(i) {
3276 toggleAllInherited(this, false);
3277 });
3278 a.text("[Expand All]");
3279 }
3280 return false;
3281}
3282
3283/* Expand all inherited members in the class. Used when initiating page search */
3284function ensureAllInheritedExpanded() {
3285 var toggles = $(".toggle-all", $("#body-content"));
3286 toggles.each(function(i) {
3287 toggleAllInherited(this, true);
3288 });
3289 $("#toggleAllClassInherited").text("[Collapse All]");
3290}
3291
3292
3293/* HANDLE KEY EVENTS
3294 * - Listen for Ctrl+F (Cmd on Mac) and expand all inherited members (to aid page search)
3295 */
3296var agent = navigator['userAgent'].toLowerCase();
3297var mac = agent.indexOf("macintosh") != -1;
3298
3299$(document).keydown( function(e) {
3300var control = mac ? e.metaKey && !e.ctrlKey : e.ctrlKey; // get ctrl key
3301 if (control && e.which == 70) { // 70 is "F"
3302 ensureAllInheritedExpanded();
3303 }
3304});
Scott Main498d7102013-08-21 15:47:38 -07003305
3306
3307
3308
3309
3310
3311/* On-demand functions */
3312
3313/** Move sample code line numbers out of PRE block and into non-copyable column */
3314function initCodeLineNumbers() {
3315 var numbers = $("#codesample-block a.number");
3316 if (numbers.length) {
3317 $("#codesample-line-numbers").removeClass("hidden").append(numbers);
3318 }
3319
3320 $(document).ready(function() {
3321 // select entire line when clicked
3322 $("span.code-line").click(function() {
3323 if (!shifted) {
3324 selectText(this);
3325 }
3326 });
3327 // invoke line link on double click
3328 $(".code-line").dblclick(function() {
3329 document.location.hash = $(this).attr('id');
3330 });
3331 // highlight the line when hovering on the number
3332 $("#codesample-line-numbers a.number").mouseover(function() {
3333 var id = $(this).attr('href');
3334 $(id).css('background','#e7e7e7');
3335 });
3336 $("#codesample-line-numbers a.number").mouseout(function() {
3337 var id = $(this).attr('href');
3338 $(id).css('background','none');
3339 });
3340 });
3341}
3342
3343// create SHIFT key binder to avoid the selectText method when selecting multiple lines
3344var shifted = false;
3345$(document).bind('keyup keydown', function(e){shifted = e.shiftKey; return true;} );
3346
3347// courtesy of jasonedelman.com
3348function selectText(element) {
3349 var doc = document
3350 , range, selection
3351 ;
3352 if (doc.body.createTextRange) { //ms
3353 range = doc.body.createTextRange();
3354 range.moveToElementText(element);
3355 range.select();
3356 } else if (window.getSelection) { //all others
Scott Main70557ee2013-10-30 14:47:40 -07003357 selection = window.getSelection();
Scott Main498d7102013-08-21 15:47:38 -07003358 range = doc.createRange();
3359 range.selectNodeContents(element);
3360 selection.removeAllRanges();
3361 selection.addRange(range);
3362 }
Scott Main285f0772013-08-22 23:22:09 +00003363}
Scott Main03aca9a2013-10-31 07:20:55 -07003364
3365
3366
3367
3368/** Display links and other information about samples that match the
3369 group specified by the URL */
3370function showSamples() {
3371 var group = $("#samples").attr('class');
3372 $("#samples").html("<p>Here are some samples for <b>" + group + "</b> apps:</p>");
3373
3374 var $ul = $("<ul>");
3375 $selectedLi = $("#nav li.selected");
3376
3377 $selectedLi.children("ul").children("li").each(function() {
3378 var $li = $("<li>").append($(this).find("a").first().clone());
3379 $ul.append($li);
3380 });
3381
3382 $("#samples").append($ul);
3383
3384}
Dirk Doughertyc3921652014-05-13 16:55:26 -07003385
3386
3387
3388/* ########################################################## */
3389/* ################### RESOURCE CARDS ##################### */
3390/* ########################################################## */
3391
3392/** Handle resource queries, collections, and grids (sections). Requires
3393 jd_tag_helpers.js and the *_unified_data.js to be loaded. */
3394
3395(function() {
3396 // Prevent the same resource from being loaded more than once per page.
3397 var addedPageResources = {};
3398
3399 $(document).ready(function() {
3400 $('.resource-widget').each(function() {
3401 initResourceWidget(this);
3402 });
3403
3404 /* Pass the line height to ellipsisfade() to adjust the height of the
3405 text container to show the max number of lines possible, without
3406 showing lines that are cut off. This works with the css ellipsis
3407 classes to fade last text line and apply an ellipsis char. */
3408
Scott Mainb16376f2014-05-21 20:35:47 -07003409 //card text currently uses 15px line height.
Dirk Doughertyc3921652014-05-13 16:55:26 -07003410 var lineHeight = 15;
3411 $('.card-info .text').ellipsisfade(lineHeight);
3412 });
3413
3414 /*
3415 Three types of resource layouts:
3416 Flow - Uses a fixed row-height flow using float left style.
3417 Carousel - Single card slideshow all same dimension absolute.
3418 Stack - Uses fixed columns and flexible element height.
3419 */
3420 function initResourceWidget(widget) {
3421 var $widget = $(widget);
3422 var isFlow = $widget.hasClass('resource-flow-layout'),
3423 isCarousel = $widget.hasClass('resource-carousel-layout'),
3424 isStack = $widget.hasClass('resource-stack-layout');
3425
3426 // find size of widget by pulling out its class name
3427 var sizeCols = 1;
3428 var m = $widget.get(0).className.match(/\bcol-(\d+)\b/);
3429 if (m) {
3430 sizeCols = parseInt(m[1], 10);
3431 }
3432
3433 var opts = {
3434 cardSizes: ($widget.data('cardsizes') || '').split(','),
3435 maxResults: parseInt($widget.data('maxresults') || '100', 10),
3436 itemsPerPage: $widget.data('itemsperpage'),
3437 sortOrder: $widget.data('sortorder'),
3438 query: $widget.data('query'),
3439 section: $widget.data('section'),
Robert Lye7eeb402014-06-03 19:35:24 -07003440 sizeCols: sizeCols,
3441 /* Added by LFL 6/6/14 */
3442 resourceStyle: $widget.data('resourcestyle') || 'card',
3443 stackSort: $widget.data('stacksort') || 'true'
Dirk Doughertyc3921652014-05-13 16:55:26 -07003444 };
3445
3446 // run the search for the set of resources to show
3447
3448 var resources = buildResourceList(opts);
3449
3450 if (isFlow) {
3451 drawResourcesFlowWidget($widget, opts, resources);
3452 } else if (isCarousel) {
3453 drawResourcesCarouselWidget($widget, opts, resources);
3454 } else if (isStack) {
smain@google.com95948b82014-06-16 19:24:25 -07003455 /* Looks like this got removed and is not used, so repurposing for the
3456 homepage style layout.
Robert Lye7eeb402014-06-03 19:35:24 -07003457 Modified by LFL 6/6/14
3458 */
3459 //var sections = buildSectionList(opts);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003460 opts['numStacks'] = $widget.data('numstacks');
Robert Lye7eeb402014-06-03 19:35:24 -07003461 drawResourcesStackWidget($widget, opts, resources/*, sections*/);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003462 }
3463 }
3464
3465 /* Initializes a Resource Carousel Widget */
3466 function drawResourcesCarouselWidget($widget, opts, resources) {
3467 $widget.empty();
3468 var plusone = true; //always show plusone on carousel
3469
3470 $widget.addClass('resource-card slideshow-container')
3471 .append($('<a>').addClass('slideshow-prev').text('Prev'))
3472 .append($('<a>').addClass('slideshow-next').text('Next'));
3473
3474 var css = { 'width': $widget.width() + 'px',
3475 'height': $widget.height() + 'px' };
3476
3477 var $ul = $('<ul>');
3478
3479 for (var i = 0; i < resources.length; ++i) {
Dirk Doughertyc3921652014-05-13 16:55:26 -07003480 var $card = $('<a>')
Robert Lye7eeb402014-06-03 19:35:24 -07003481 .attr('href', cleanUrl(resources[i].url))
Dirk Doughertyc3921652014-05-13 16:55:26 -07003482 .decorateResourceCard(resources[i],plusone);
3483
3484 $('<li>').css(css)
3485 .append($card)
3486 .appendTo($ul);
3487 }
3488
3489 $('<div>').addClass('frame')
3490 .append($ul)
3491 .appendTo($widget);
3492
3493 $widget.dacSlideshow({
3494 auto: true,
3495 btnPrev: '.slideshow-prev',
3496 btnNext: '.slideshow-next'
3497 });
3498 };
3499
Robert Lye7eeb402014-06-03 19:35:24 -07003500 /* Initializes a Resource Card Stack Widget (column-based layout)
3501 Modified by LFL 6/6/14
3502 */
Dirk Doughertyc3921652014-05-13 16:55:26 -07003503 function drawResourcesStackWidget($widget, opts, resources, sections) {
3504 // Don't empty widget, grab all items inside since they will be the first
3505 // items stacked, followed by the resource query
3506 var plusone = true; //by default show plusone on section cards
3507 var cards = $widget.find('.resource-card').detach().toArray();
3508 var numStacks = opts.numStacks || 1;
3509 var $stacks = [];
3510 var urlString;
3511
3512 for (var i = 0; i < numStacks; ++i) {
3513 $stacks[i] = $('<div>').addClass('resource-card-stack')
3514 .appendTo($widget);
3515 }
3516
3517 var sectionResources = [];
3518
3519 // Extract any subsections that are actually resource cards
Robert Lye7eeb402014-06-03 19:35:24 -07003520 if (sections) {
3521 for (var i = 0; i < sections.length; ++i) {
3522 if (!sections[i].sections || !sections[i].sections.length) {
3523 // Render it as a resource card
3524 sectionResources.push(
3525 $('<a>')
3526 .addClass('resource-card section-card')
3527 .attr('href', cleanUrl(sections[i].resource.url))
3528 .decorateResourceCard(sections[i].resource,plusone)[0]
3529 );
Dirk Doughertyc3921652014-05-13 16:55:26 -07003530
Robert Lye7eeb402014-06-03 19:35:24 -07003531 } else {
3532 cards.push(
3533 $('<div>')
3534 .addClass('resource-card section-card-menu')
3535 .decorateResourceSection(sections[i],plusone)[0]
3536 );
3537 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003538 }
3539 }
3540
3541 cards = cards.concat(sectionResources);
3542
3543 for (var i = 0; i < resources.length; ++i) {
Robert Lye7eeb402014-06-03 19:35:24 -07003544 var $card = createResourceElement(resources[i], opts);
smain@google.com95948b82014-06-16 19:24:25 -07003545
Robert Lye7eeb402014-06-03 19:35:24 -07003546 if (opts.resourceStyle.indexOf('related') > -1) {
3547 $card.addClass('related-card');
3548 }
smain@google.com95948b82014-06-16 19:24:25 -07003549
Dirk Doughertyc3921652014-05-13 16:55:26 -07003550 cards.push($card[0]);
3551 }
3552
Robert Lye7eeb402014-06-03 19:35:24 -07003553 if (opts.stackSort != 'false') {
3554 for (var i = 0; i < cards.length; ++i) {
3555 // Find the stack with the shortest height, but give preference to
3556 // left to right order.
3557 var minHeight = $stacks[0].height();
3558 var minIndex = 0;
Dirk Doughertyc3921652014-05-13 16:55:26 -07003559
Robert Lye7eeb402014-06-03 19:35:24 -07003560 for (var j = 1; j < numStacks; ++j) {
3561 var height = $stacks[j].height();
3562 if (height < minHeight - 45) {
3563 minHeight = height;
3564 minIndex = j;
3565 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003566 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003567
Robert Lye7eeb402014-06-03 19:35:24 -07003568 $stacks[minIndex].append($(cards[i]));
3569 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003570 }
3571
3572 };
smain@google.com95948b82014-06-16 19:24:25 -07003573
3574 /*
Robert Lye7eeb402014-06-03 19:35:24 -07003575 Create a resource card using the given resource object and a list of html
3576 configured options. Returns a jquery object containing the element.
3577 */
smain@google.com95948b82014-06-16 19:24:25 -07003578 function createResourceElement(resource, opts, plusone) {
Robert Lye7eeb402014-06-03 19:35:24 -07003579 var $el;
smain@google.com95948b82014-06-16 19:24:25 -07003580
Robert Lye7eeb402014-06-03 19:35:24 -07003581 // The difference here is that generic cards are not entirely clickable
3582 // so its a div instead of an a tag, also the generic one is not given
3583 // the resource-card class so it appears with a transparent background
3584 // and can be styled in whatever way the css setup.
3585 if (opts.resourceStyle == 'generic') {
3586 $el = $('<div>')
3587 .addClass('resource')
3588 .attr('href', cleanUrl(resource.url))
3589 .decorateResource(resource, opts);
3590 } else {
3591 var cls = 'resource resource-card';
smain@google.com95948b82014-06-16 19:24:25 -07003592
Robert Lye7eeb402014-06-03 19:35:24 -07003593 $el = $('<a>')
3594 .addClass(cls)
3595 .attr('href', cleanUrl(resource.url))
3596 .decorateResourceCard(resource, plusone);
3597 }
smain@google.com95948b82014-06-16 19:24:25 -07003598
Robert Lye7eeb402014-06-03 19:35:24 -07003599 return $el;
3600 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003601
3602 /* Initializes a flow widget, see distribute.scss for generating accompanying css */
3603 function drawResourcesFlowWidget($widget, opts, resources) {
3604 $widget.empty();
3605 var cardSizes = opts.cardSizes || ['6x6'];
3606 var i = 0, j = 0;
3607 var plusone = true; // by default show plusone on resource cards
3608
3609 while (i < resources.length) {
3610 var cardSize = cardSizes[j++ % cardSizes.length];
3611 cardSize = cardSize.replace(/^\s+|\s+$/,'');
Dirk Doughertyc3921652014-05-13 16:55:26 -07003612 // Some card sizes do not get a plusone button, such as where space is constrained
3613 // or for cards commonly embedded in docs (to improve overall page speed).
3614 plusone = !((cardSize == "6x2") || (cardSize == "6x3") ||
3615 (cardSize == "9x2") || (cardSize == "9x3") ||
3616 (cardSize == "12x2") || (cardSize == "12x3"));
3617
3618 // A stack has a third dimension which is the number of stacked items
3619 var isStack = cardSize.match(/(\d+)x(\d+)x(\d+)/);
3620 var stackCount = 0;
3621 var $stackDiv = null;
3622
3623 if (isStack) {
3624 // Create a stack container which should have the dimensions defined
3625 // by the product of the items inside.
3626 $stackDiv = $('<div>').addClass('resource-card-stack resource-card-' + isStack[1]
3627 + 'x' + isStack[2] * isStack[3]) .appendTo($widget);
3628 }
3629
3630 // Build each stack item or just a single item
3631 do {
3632 var resource = resources[i];
Dirk Doughertyc3921652014-05-13 16:55:26 -07003633
Robert Lye7eeb402014-06-03 19:35:24 -07003634 var $card = createResourceElement(resources[i], opts, plusone);
smain@google.com95948b82014-06-16 19:24:25 -07003635
3636 $card.addClass('resource-card-' + cardSize +
Robert Lye7eeb402014-06-03 19:35:24 -07003637 ' resource-card-' + resource.type);
smain@google.com95948b82014-06-16 19:24:25 -07003638
Dirk Doughertyc3921652014-05-13 16:55:26 -07003639 if (isStack) {
3640 $card.addClass('resource-card-' + isStack[1] + 'x' + isStack[2]);
3641 if (++stackCount == parseInt(isStack[3])) {
3642 $card.addClass('resource-card-row-stack-last');
3643 stackCount = 0;
3644 }
3645 } else {
3646 stackCount = 0;
3647 }
3648
Robert Lye7eeb402014-06-03 19:35:24 -07003649 $card.appendTo($stackDiv || $widget);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003650
3651 } while (++i < resources.length && stackCount > 0);
3652 }
3653 }
3654
3655 /* Build a site map of resources using a section as a root. */
3656 function buildSectionList(opts) {
3657 if (opts.section && SECTION_BY_ID[opts.section]) {
3658 return SECTION_BY_ID[opts.section].sections || [];
3659 }
3660 return [];
3661 }
3662
3663 function buildResourceList(opts) {
3664 var maxResults = opts.maxResults || 100;
3665
3666 var query = opts.query || '';
3667 var expressions = parseResourceQuery(query);
3668 var addedResourceIndices = {};
3669 var results = [];
3670
3671 for (var i = 0; i < expressions.length; i++) {
3672 var clauses = expressions[i];
3673
3674 // build initial set of resources from first clause
3675 var firstClause = clauses[0];
3676 var resources = [];
3677 switch (firstClause.attr) {
3678 case 'type':
3679 resources = ALL_RESOURCES_BY_TYPE[firstClause.value];
3680 break;
3681 case 'lang':
3682 resources = ALL_RESOURCES_BY_LANG[firstClause.value];
3683 break;
3684 case 'tag':
3685 resources = ALL_RESOURCES_BY_TAG[firstClause.value];
3686 break;
3687 case 'collection':
3688 var urls = RESOURCE_COLLECTIONS[firstClause.value].resources || [];
3689 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3690 break;
3691 case 'section':
3692 var urls = SITE_MAP[firstClause.value].sections || [];
3693 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3694 break;
3695 }
3696 // console.log(firstClause.attr + ':' + firstClause.value);
3697 resources = resources || [];
3698
3699 // use additional clauses to filter corpus
3700 if (clauses.length > 1) {
3701 var otherClauses = clauses.slice(1);
3702 resources = resources.filter(getResourceMatchesClausesFilter(otherClauses));
3703 }
3704
3705 // filter out resources already added
3706 if (i > 1) {
3707 resources = resources.filter(getResourceNotAlreadyAddedFilter(addedResourceIndices));
3708 }
3709
3710 // add to list of already added indices
3711 for (var j = 0; j < resources.length; j++) {
3712 // console.log(resources[j].title);
3713 addedResourceIndices[resources[j].index] = 1;
3714 }
3715
3716 // concat to final results list
3717 results = results.concat(resources);
3718 }
3719
3720 if (opts.sortOrder && results.length) {
3721 var attr = opts.sortOrder;
3722
3723 if (opts.sortOrder == 'random') {
3724 var i = results.length, j, temp;
3725 while (--i) {
3726 j = Math.floor(Math.random() * (i + 1));
3727 temp = results[i];
3728 results[i] = results[j];
3729 results[j] = temp;
3730 }
3731 } else {
3732 var desc = attr.charAt(0) == '-';
3733 if (desc) {
3734 attr = attr.substring(1);
3735 }
3736 results = results.sort(function(x,y) {
3737 return (desc ? -1 : 1) * (parseInt(x[attr], 10) - parseInt(y[attr], 10));
3738 });
3739 }
3740 }
3741
3742 results = results.filter(getResourceNotAlreadyAddedFilter(addedPageResources));
3743 results = results.slice(0, maxResults);
3744
3745 for (var j = 0; j < results.length; ++j) {
3746 addedPageResources[results[j].index] = 1;
3747 }
3748
3749 return results;
3750 }
3751
3752
3753 function getResourceNotAlreadyAddedFilter(addedResourceIndices) {
3754 return function(resource) {
3755 return !addedResourceIndices[resource.index];
3756 };
3757 }
3758
3759
3760 function getResourceMatchesClausesFilter(clauses) {
3761 return function(resource) {
3762 return doesResourceMatchClauses(resource, clauses);
3763 };
3764 }
3765
3766
3767 function doesResourceMatchClauses(resource, clauses) {
3768 for (var i = 0; i < clauses.length; i++) {
3769 var map;
3770 switch (clauses[i].attr) {
3771 case 'type':
3772 map = IS_RESOURCE_OF_TYPE[clauses[i].value];
3773 break;
3774 case 'lang':
3775 map = IS_RESOURCE_IN_LANG[clauses[i].value];
3776 break;
3777 case 'tag':
3778 map = IS_RESOURCE_TAGGED[clauses[i].value];
3779 break;
3780 }
3781
3782 if (!map || (!!clauses[i].negative ? map[resource.index] : !map[resource.index])) {
3783 return clauses[i].negative;
3784 }
3785 }
3786 return true;
3787 }
smain@google.com95948b82014-06-16 19:24:25 -07003788
Robert Lye7eeb402014-06-03 19:35:24 -07003789 function cleanUrl(url)
3790 {
3791 if (url && url.indexOf('//') === -1) {
3792 url = toRoot + url;
3793 }
smain@google.com95948b82014-06-16 19:24:25 -07003794
Robert Lye7eeb402014-06-03 19:35:24 -07003795 return url;
3796 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003797
3798
3799 function parseResourceQuery(query) {
3800 // Parse query into array of expressions (expression e.g. 'tag:foo + type:video')
3801 var expressions = [];
3802 var expressionStrs = query.split(',') || [];
3803 for (var i = 0; i < expressionStrs.length; i++) {
3804 var expr = expressionStrs[i] || '';
3805
3806 // Break expression into clauses (clause e.g. 'tag:foo')
3807 var clauses = [];
3808 var clauseStrs = expr.split(/(?=[\+\-])/);
3809 for (var j = 0; j < clauseStrs.length; j++) {
3810 var clauseStr = clauseStrs[j] || '';
3811
3812 // Get attribute and value from clause (e.g. attribute='tag', value='foo')
3813 var parts = clauseStr.split(':');
3814 var clause = {};
3815
3816 clause.attr = parts[0].replace(/^\s+|\s+$/g,'');
3817 if (clause.attr) {
3818 if (clause.attr.charAt(0) == '+') {
3819 clause.attr = clause.attr.substring(1);
3820 } else if (clause.attr.charAt(0) == '-') {
3821 clause.negative = true;
3822 clause.attr = clause.attr.substring(1);
3823 }
3824 }
3825
3826 if (parts.length > 1) {
3827 clause.value = parts[1].replace(/^\s+|\s+$/g,'');
3828 }
3829
3830 clauses.push(clause);
3831 }
3832
3833 if (!clauses.length) {
3834 continue;
3835 }
3836
3837 expressions.push(clauses);
3838 }
3839
3840 return expressions;
3841 }
3842})();
3843
3844(function($) {
Robert Lye7eeb402014-06-03 19:35:24 -07003845
smain@google.com95948b82014-06-16 19:24:25 -07003846 /*
Robert Lye7eeb402014-06-03 19:35:24 -07003847 Utility method for creating dom for the description area of a card.
3848 Used in decorateResourceCard and decorateResource.
3849 */
3850 function buildResourceCardDescription(resource, plusone) {
3851 var $description = $('<div>').addClass('description ellipsis');
smain@google.com95948b82014-06-16 19:24:25 -07003852
Robert Lye7eeb402014-06-03 19:35:24 -07003853 $description.append($('<div>').addClass('text').html(resource.summary));
smain@google.com95948b82014-06-16 19:24:25 -07003854
Robert Lye7eeb402014-06-03 19:35:24 -07003855 if (resource.cta) {
3856 $description.append($('<a>').addClass('cta').html(resource.cta));
3857 }
smain@google.com95948b82014-06-16 19:24:25 -07003858
Robert Lye7eeb402014-06-03 19:35:24 -07003859 if (plusone) {
smain@google.com95948b82014-06-16 19:24:25 -07003860 var plusurl = resource.url.indexOf("//") > -1 ? resource.url :
Robert Lye7eeb402014-06-03 19:35:24 -07003861 "//developer.android.com/" + resource.url;
smain@google.com95948b82014-06-16 19:24:25 -07003862
Robert Lye7eeb402014-06-03 19:35:24 -07003863 $description.append($('<div>').addClass('util')
3864 .append($('<div>').addClass('g-plusone')
3865 .attr('data-size', 'small')
3866 .attr('data-align', 'right')
3867 .attr('data-href', plusurl)));
3868 }
smain@google.com95948b82014-06-16 19:24:25 -07003869
Robert Lye7eeb402014-06-03 19:35:24 -07003870 return $description;
3871 }
smain@google.com95948b82014-06-16 19:24:25 -07003872
3873
Dirk Doughertyc3921652014-05-13 16:55:26 -07003874 /* Simple jquery function to create dom for a standard resource card */
3875 $.fn.decorateResourceCard = function(resource,plusone) {
3876 var section = resource.group || resource.type;
smain@google.com95948b82014-06-16 19:24:25 -07003877 var imgUrl = resource.image ||
Robert Lye7eeb402014-06-03 19:35:24 -07003878 'assets/images/resource-card-default-android.jpg';
smain@google.com95948b82014-06-16 19:24:25 -07003879
Robert Lye7eeb402014-06-03 19:35:24 -07003880 if (imgUrl.indexOf('//') === -1) {
3881 imgUrl = toRoot + imgUrl;
Dirk Doughertyc3921652014-05-13 16:55:26 -07003882 }
Robert Lye7eeb402014-06-03 19:35:24 -07003883
3884 $('<div>').addClass('card-bg')
smain@google.com95948b82014-06-16 19:24:25 -07003885 .css('background-image', 'url(' + (imgUrl || toRoot +
Robert Lye7eeb402014-06-03 19:35:24 -07003886 'assets/images/resource-card-default-android.jpg') + ')')
Dirk Doughertyc3921652014-05-13 16:55:26 -07003887 .appendTo(this);
smain@google.com95948b82014-06-16 19:24:25 -07003888
Robert Lye7eeb402014-06-03 19:35:24 -07003889 $('<div>').addClass('card-info' + (!resource.summary ? ' empty-desc' : ''))
3890 .append($('<div>').addClass('section').text(section))
3891 .append($('<div>').addClass('title').html(resource.title))
3892 .append(buildResourceCardDescription(resource, plusone))
3893 .appendTo(this);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003894
3895 return this;
3896 };
3897
3898 /* Simple jquery function to create dom for a resource section card (menu) */
3899 $.fn.decorateResourceSection = function(section,plusone) {
3900 var resource = section.resource;
3901 //keep url clean for matching and offline mode handling
3902 var urlPrefix = resource.image.indexOf("//") > -1 ? "" : toRoot;
3903 var $base = $('<a>')
3904 .addClass('card-bg')
3905 .attr('href', resource.url)
3906 .append($('<div>').addClass('card-section-icon')
3907 .append($('<div>').addClass('icon'))
3908 .append($('<div>').addClass('section').html(resource.title)))
3909 .appendTo(this);
3910
3911 var $cardInfo = $('<div>').addClass('card-info').appendTo(this);
3912
3913 if (section.sections && section.sections.length) {
3914 // Recurse the section sub-tree to find a resource image.
3915 var stack = [section];
3916
3917 while (stack.length) {
3918 if (stack[0].resource.image) {
3919 $base.css('background-image', 'url(' + urlPrefix + stack[0].resource.image + ')');
3920 break;
3921 }
3922
3923 if (stack[0].sections) {
3924 stack = stack.concat(stack[0].sections);
3925 }
3926
3927 stack.shift();
3928 }
3929
3930 var $ul = $('<ul>')
3931 .appendTo($cardInfo);
3932
3933 var max = section.sections.length > 3 ? 3 : section.sections.length;
3934
3935 for (var i = 0; i < max; ++i) {
3936
3937 var subResource = section.sections[i];
3938 if (!plusone) {
3939 $('<li>')
3940 .append($('<a>').attr('href', subResource.url)
3941 .append($('<div>').addClass('title').html(subResource.title))
3942 .append($('<div>').addClass('description ellipsis')
3943 .append($('<div>').addClass('text').html(subResource.summary))
3944 .append($('<div>').addClass('util'))))
3945 .appendTo($ul);
3946 } else {
3947 $('<li>')
3948 .append($('<a>').attr('href', subResource.url)
3949 .append($('<div>').addClass('title').html(subResource.title))
3950 .append($('<div>').addClass('description ellipsis')
3951 .append($('<div>').addClass('text').html(subResource.summary))
3952 .append($('<div>').addClass('util')
3953 .append($('<div>').addClass('g-plusone')
3954 .attr('data-size', 'small')
3955 .attr('data-align', 'right')
3956 .attr('data-href', resource.url)))))
3957 .appendTo($ul);
3958 }
3959 }
3960
3961 // Add a more row
3962 if (max < section.sections.length) {
3963 $('<li>')
3964 .append($('<a>').attr('href', resource.url)
3965 .append($('<div>')
3966 .addClass('title')
3967 .text('More')))
3968 .appendTo($ul);
3969 }
3970 } else {
3971 // No sub-resources, just render description?
3972 }
3973
3974 return this;
3975 };
smain@google.com95948b82014-06-16 19:24:25 -07003976
3977
3978
3979
Robert Lye7eeb402014-06-03 19:35:24 -07003980 /* Render other types of resource styles that are not cards. */
3981 $.fn.decorateResource = function(resource, opts) {
smain@google.com95948b82014-06-16 19:24:25 -07003982 var imgUrl = resource.image ||
Robert Lye7eeb402014-06-03 19:35:24 -07003983 'assets/images/resource-card-default-android.jpg';
3984 var linkUrl = resource.url;
smain@google.com95948b82014-06-16 19:24:25 -07003985
Robert Lye7eeb402014-06-03 19:35:24 -07003986 if (imgUrl.indexOf('//') === -1) {
3987 imgUrl = toRoot + imgUrl;
3988 }
smain@google.com95948b82014-06-16 19:24:25 -07003989
Robert Lye7eeb402014-06-03 19:35:24 -07003990 if (linkUrl && linkUrl.indexOf('//') === -1) {
3991 linkUrl = toRoot + linkUrl;
3992 }
3993
3994 $(this).append(
3995 $('<div>').addClass('image')
3996 .css('background-image', 'url(' + imgUrl + ')'),
3997 $('<div>').addClass('info').append(
3998 $('<h4>').addClass('title').html(resource.title),
3999 $('<p>').addClass('summary').html(resource.summary),
4000 $('<a>').attr('href', linkUrl).addClass('cta').html('Learn More')
4001 )
4002 );
4003
4004 return this;
4005 };
Dirk Doughertyc3921652014-05-13 16:55:26 -07004006})(jQuery);
Robert Lye7eeb402014-06-03 19:35:24 -07004007
4008
Dirk Doughertyc3921652014-05-13 16:55:26 -07004009/* Calculate the vertical area remaining */
4010(function($) {
4011 $.fn.ellipsisfade= function(lineHeight) {
4012 this.each(function() {
4013 // get element text
4014 var $this = $(this);
4015 var remainingHeight = $this.parent().parent().height();
4016 $this.parent().siblings().each(function ()
smain@google.comc91ecb72014-06-23 10:22:23 -07004017 {
smain@google.comcda1a9a2014-06-19 17:07:46 -07004018 if ($(this).is(":visible")) {
4019 var h = $(this).height();
4020 remainingHeight = remainingHeight - h;
4021 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07004022 });
4023
4024 adjustedRemainingHeight = ((remainingHeight)/lineHeight>>0)*lineHeight
4025 $this.parent().css({'height': adjustedRemainingHeight});
4026 $this.css({'height': "auto"});
4027 });
4028
4029 return this;
4030 };
4031}) (jQuery);
Robert Lye7eeb402014-06-03 19:35:24 -07004032
4033/*
4034 Fullscreen Carousel
smain@google.com95948b82014-06-16 19:24:25 -07004035
Robert Lye7eeb402014-06-03 19:35:24 -07004036 The following allows for an area at the top of the page that takes over the
smain@google.com95948b82014-06-16 19:24:25 -07004037 entire browser height except for its top offset and an optional bottom
Robert Lye7eeb402014-06-03 19:35:24 -07004038 padding specified as a data attribute.
smain@google.com95948b82014-06-16 19:24:25 -07004039
Robert Lye7eeb402014-06-03 19:35:24 -07004040 HTML:
smain@google.com95948b82014-06-16 19:24:25 -07004041
Robert Lye7eeb402014-06-03 19:35:24 -07004042 <div class="fullscreen-carousel">
4043 <div class="fullscreen-carousel-content">
4044 <!-- content here -->
4045 </div>
4046 <div class="fullscreen-carousel-content">
4047 <!-- content here -->
4048 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004049
Robert Lye7eeb402014-06-03 19:35:24 -07004050 etc ...
smain@google.com95948b82014-06-16 19:24:25 -07004051
Robert Lye7eeb402014-06-03 19:35:24 -07004052 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004053
Robert Lye7eeb402014-06-03 19:35:24 -07004054 Control over how the carousel takes over the screen can mostly be defined in
4055 a css file. Setting min-height on the .fullscreen-carousel-content elements
smain@google.com95948b82014-06-16 19:24:25 -07004056 will prevent them from shrinking to far vertically when the browser is very
Robert Lye7eeb402014-06-03 19:35:24 -07004057 short, and setting max-height on the .fullscreen-carousel itself will prevent
smain@google.com95948b82014-06-16 19:24:25 -07004058 the area from becoming to long in the case that the browser is stretched very
Robert Lye7eeb402014-06-03 19:35:24 -07004059 tall.
smain@google.com95948b82014-06-16 19:24:25 -07004060
Robert Lye7eeb402014-06-03 19:35:24 -07004061 There is limited functionality for having multiple sections since that request
4062 was removed, but it is possible to add .next-arrow and .prev-arrow elements to
4063 scroll between multiple content areas.
4064*/
4065
4066(function() {
4067 $(document).ready(function() {
4068 $('.fullscreen-carousel').each(function() {
4069 initWidget(this);
4070 });
4071 });
4072
4073 function initWidget(widget) {
4074 var $widget = $(widget);
smain@google.com95948b82014-06-16 19:24:25 -07004075
Robert Lye7eeb402014-06-03 19:35:24 -07004076 var topOffset = $widget.offset().top;
4077 var padBottom = parseInt($widget.data('paddingbottom')) || 0;
4078 var maxHeight = 0;
4079 var minHeight = 0;
4080 var $content = $widget.find('.fullscreen-carousel-content');
4081 var $nextArrow = $widget.find('.next-arrow');
4082 var $prevArrow = $widget.find('.prev-arrow');
4083 var $curSection = $($content[0]);
smain@google.com95948b82014-06-16 19:24:25 -07004084
Robert Lye7eeb402014-06-03 19:35:24 -07004085 if ($content.length <= 1) {
4086 $nextArrow.hide();
4087 $prevArrow.hide();
4088 } else {
4089 $nextArrow.click(function() {
4090 var index = ($content.index($curSection) + 1);
4091 $curSection.hide();
4092 $curSection = $($content[index >= $content.length ? 0 : index]);
4093 $curSection.show();
4094 });
smain@google.com95948b82014-06-16 19:24:25 -07004095
Robert Lye7eeb402014-06-03 19:35:24 -07004096 $prevArrow.click(function() {
4097 var index = ($content.index($curSection) - 1);
4098 $curSection.hide();
4099 $curSection = $($content[index < 0 ? $content.length - 1 : 0]);
4100 $curSection.show();
4101 });
4102 }
4103
4104 // Just hide all content sections except first.
4105 $content.each(function(index) {
4106 if ($(this).height() > minHeight) minHeight = $(this).height();
4107 $(this).css({position: 'absolute', display: index > 0 ? 'none' : ''});
4108 });
4109
4110 // Register for changes to window size, and trigger.
4111 $(window).resize(resizeWidget);
4112 resizeWidget();
4113
4114 function resizeWidget() {
4115 var height = $(window).height() - topOffset - padBottom;
4116 $widget.width($(window).width());
smain@google.com95948b82014-06-16 19:24:25 -07004117 $widget.height(height < minHeight ? minHeight :
Robert Lye7eeb402014-06-03 19:35:24 -07004118 (maxHeight && height > maxHeight ? maxHeight : height));
4119 }
smain@google.com95948b82014-06-16 19:24:25 -07004120 }
Robert Lye7eeb402014-06-03 19:35:24 -07004121})();
4122
4123
4124
4125
4126
4127/*
4128 Tab Carousel
smain@google.com95948b82014-06-16 19:24:25 -07004129
Robert Lye7eeb402014-06-03 19:35:24 -07004130 The following allows tab widgets to be installed via the html below. Each
4131 tab content section should have a data-tab attribute matching one of the
4132 nav items'. Also each tab content section should have a width matching the
4133 tab carousel.
smain@google.com95948b82014-06-16 19:24:25 -07004134
Robert Lye7eeb402014-06-03 19:35:24 -07004135 HTML:
smain@google.com95948b82014-06-16 19:24:25 -07004136
Robert Lye7eeb402014-06-03 19:35:24 -07004137 <div class="tab-carousel">
4138 <ul class="tab-nav">
4139 <li><a href="#" data-tab="handsets">Handsets</a>
4140 <li><a href="#" data-tab="wearable">Wearable</a>
4141 <li><a href="#" data-tab="tv">TV</a>
4142 </ul>
smain@google.com95948b82014-06-16 19:24:25 -07004143
Robert Lye7eeb402014-06-03 19:35:24 -07004144 <div class="tab-carousel-content">
4145 <div data-tab="handsets">
4146 <!--Full width content here-->
4147 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004148
Robert Lye7eeb402014-06-03 19:35:24 -07004149 <div data-tab="wearable">
4150 <!--Full width content here-->
4151 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004152
Robert Lye7eeb402014-06-03 19:35:24 -07004153 <div data-tab="tv">
4154 <!--Full width content here-->
4155 </div>
4156 </div>
4157 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004158
Robert Lye7eeb402014-06-03 19:35:24 -07004159*/
4160(function() {
4161 $(document).ready(function() {
4162 $('.tab-carousel').each(function() {
4163 initWidget(this);
4164 });
4165 });
4166
4167 function initWidget(widget) {
4168 var $widget = $(widget);
4169 var $nav = $widget.find('.tab-nav');
4170 var $anchors = $nav.find('[data-tab]');
4171 var $li = $nav.find('li');
4172 var $contentContainer = $widget.find('.tab-carousel-content');
4173 var $tabs = $contentContainer.find('[data-tab]');
4174 var $curTab = $($tabs[0]); // Current tab is first tab.
4175 var width = $widget.width();
4176
4177 // Setup nav interactivity.
4178 $anchors.click(function(evt) {
4179 evt.preventDefault();
4180 var query = '[data-tab=' + $(this).data('tab') + ']';
smain@google.com95948b82014-06-16 19:24:25 -07004181 transitionWidget($tabs.filter(query));
Robert Lye7eeb402014-06-03 19:35:24 -07004182 });
smain@google.com95948b82014-06-16 19:24:25 -07004183
Robert Lye7eeb402014-06-03 19:35:24 -07004184 // Add highlight for navigation on first item.
4185 var $highlight = $('<div>').addClass('highlight')
4186 .css({left:$li.position().left + 'px', width:$li.outerWidth() + 'px'})
4187 .appendTo($nav);
smain@google.com95948b82014-06-16 19:24:25 -07004188
Robert Lye7eeb402014-06-03 19:35:24 -07004189 // Store height since we will change contents to absolute.
4190 $contentContainer.height($contentContainer.height());
smain@google.com95948b82014-06-16 19:24:25 -07004191
Robert Lye7eeb402014-06-03 19:35:24 -07004192 // Absolutely position tabs so they're ready for transition.
4193 $tabs.each(function(index) {
4194 $(this).css({position: 'absolute', left: index > 0 ? width + 'px' : '0'});
4195 });
smain@google.com95948b82014-06-16 19:24:25 -07004196
Robert Lye7eeb402014-06-03 19:35:24 -07004197 function transitionWidget($toTab) {
4198 if (!$curTab.is($toTab)) {
4199 var curIndex = $tabs.index($curTab[0]);
4200 var toIndex = $tabs.index($toTab[0]);
4201 var dir = toIndex > curIndex ? 1 : -1;
smain@google.com95948b82014-06-16 19:24:25 -07004202
Robert Lye7eeb402014-06-03 19:35:24 -07004203 // Animate content sections.
4204 $toTab.css({left:(width * dir) + 'px'});
4205 $curTab.animate({left:(width * -dir) + 'px'});
4206 $toTab.animate({left:'0'});
smain@google.com95948b82014-06-16 19:24:25 -07004207
Robert Lye7eeb402014-06-03 19:35:24 -07004208 // Animate navigation highlight.
smain@google.com95948b82014-06-16 19:24:25 -07004209 $highlight.animate({left:$($li[toIndex]).position().left + 'px',
Robert Lye7eeb402014-06-03 19:35:24 -07004210 width:$($li[toIndex]).outerWidth() + 'px'})
smain@google.com95948b82014-06-16 19:24:25 -07004211
Robert Lye7eeb402014-06-03 19:35:24 -07004212 // Store new current section.
4213 $curTab = $toTab;
4214 }
4215 }
smain@google.com95948b82014-06-16 19:24:25 -07004216 }
Dirk Doughertyb87e3002014-11-18 19:34:34 -08004217})();