blob: ffd9eef34dfe06e37fcd8fbc62d2501e9d653e8b [file] [log] [blame]
Scott Maine4d8f1b2012-06-21 18:03:05 -07001var classesNav;
2var devdocNav;
3var sidenav;
4var cookie_namespace = 'android_developer';
5var NAV_PREF_TREE = "tree";
6var NAV_PREF_PANELS = "panels";
7var nav_pref;
Scott Maine4d8f1b2012-06-21 18:03:05 -07008var isMobile = false; // true if mobile, so we can adjust some layout
Scott Mainf6145542013-04-01 16:38:11 -07009var mPagePath; // initialized in ready() function
Scott Maine4d8f1b2012-06-21 18:03:05 -070010
Scott Main1b3db112012-07-03 14:06:22 -070011var basePath = getBaseUri(location.pathname);
12var SITE_ROOT = toRoot + basePath.substring(1,basePath.indexOf("/",1));
Scott Main7e447ed2013-02-19 17:22:37 -080013var GOOGLE_DATA; // combined data for google service apis, used for search suggest
Scott Main3b90aff2013-08-01 18:09:35 -070014
Scott Main25e73002013-03-27 15:24:06 -070015// Ensure that all ajax getScript() requests allow caching
16$.ajaxSetup({
17 cache: true
18});
Scott Maine4d8f1b2012-06-21 18:03:05 -070019
20/****** ON LOAD SET UP STUFF *********/
21
Scott Maine4d8f1b2012-06-21 18:03:05 -070022$(document).ready(function() {
Scott Main7e447ed2013-02-19 17:22:37 -080023
Scott Main0e76e7e2013-03-12 10:24:07 -070024 // load json file for JD doc search suggestions
Scott Main719acb42013-12-05 16:05:09 -080025 $.getScript(toRoot + 'jd_lists_unified.js');
Scott Main7e447ed2013-02-19 17:22:37 -080026 // load json file for Android API search suggestions
27 $.getScript(toRoot + 'reference/lists.js');
28 // load json files for Google services API suggestions
Scott Main9f2971d2013-02-26 13:07:41 -080029 $.getScript(toRoot + 'reference/gcm_lists.js', function(data, textStatus, jqxhr) {
Scott Main7e447ed2013-02-19 17:22:37 -080030 // once the GCM json (GCM_DATA) is loaded, load the GMS json (GMS_DATA) and merge the data
31 if(jqxhr.status === 200) {
Scott Main9f2971d2013-02-26 13:07:41 -080032 $.getScript(toRoot + 'reference/gms_lists.js', function(data, textStatus, jqxhr) {
Scott Main7e447ed2013-02-19 17:22:37 -080033 if(jqxhr.status === 200) {
34 // combine GCM and GMS data
35 GOOGLE_DATA = GMS_DATA;
36 var start = GOOGLE_DATA.length;
37 for (var i=0; i<GCM_DATA.length; i++) {
38 GOOGLE_DATA.push({id:start+i, label:GCM_DATA[i].label,
39 link:GCM_DATA[i].link, type:GCM_DATA[i].type});
40 }
41 }
42 });
43 }
44 });
45
Scott Main0e76e7e2013-03-12 10:24:07 -070046 // setup keyboard listener for search shortcut
47 $('body').keyup(function(event) {
48 if (event.which == 191) {
49 $('#search_autocomplete').focus();
50 }
51 });
Scott Main015d6162013-01-29 09:01:52 -080052
Scott Maine4d8f1b2012-06-21 18:03:05 -070053 // init the fullscreen toggle click event
54 $('#nav-swap .fullscreen').click(function(){
55 if ($(this).hasClass('disabled')) {
56 toggleFullscreen(true);
57 } else {
58 toggleFullscreen(false);
59 }
60 });
Scott Main3b90aff2013-08-01 18:09:35 -070061
Scott Maine4d8f1b2012-06-21 18:03:05 -070062 // initialize the divs with custom scrollbars
63 $('.scroll-pane').jScrollPane( {verticalGutter:0} );
Scott Main3b90aff2013-08-01 18:09:35 -070064
Scott Maine4d8f1b2012-06-21 18:03:05 -070065 // add HRs below all H2s (except for a few other h2 variants)
Scott Mainc29b3f52014-05-30 21:18:30 -070066 $('h2').not('#qv h2')
67 .not('#tb h2')
68 .not('.sidebox h2')
69 .not('#devdoc-nav h2')
70 .not('h2.norule').css({marginBottom:0})
71 .after('<hr/>');
Scott Maine4d8f1b2012-06-21 18:03:05 -070072
73 // set up the search close button
74 $('.search .close').click(function() {
75 $searchInput = $('#search_autocomplete');
76 $searchInput.attr('value', '');
77 $(this).addClass("hide");
78 $("#search-container").removeClass('active');
79 $("#search_autocomplete").blur();
Scott Main0e76e7e2013-03-12 10:24:07 -070080 search_focus_changed($searchInput.get(), false);
81 hideResults();
Scott Maine4d8f1b2012-06-21 18:03:05 -070082 });
83
84 // Set up quicknav
Scott Main3b90aff2013-08-01 18:09:35 -070085 var quicknav_open = false;
Scott Maine4d8f1b2012-06-21 18:03:05 -070086 $("#btn-quicknav").click(function() {
87 if (quicknav_open) {
88 $(this).removeClass('active');
89 quicknav_open = false;
90 collapse();
91 } else {
92 $(this).addClass('active');
93 quicknav_open = true;
94 expand();
95 }
96 })
Scott Main3b90aff2013-08-01 18:09:35 -070097
Scott Maine4d8f1b2012-06-21 18:03:05 -070098 var expand = function() {
99 $('#header-wrap').addClass('quicknav');
100 $('#quicknav').stop().show().animate({opacity:'1'});
101 }
Scott Main3b90aff2013-08-01 18:09:35 -0700102
Scott Maine4d8f1b2012-06-21 18:03:05 -0700103 var collapse = function() {
104 $('#quicknav').stop().animate({opacity:'0'}, 100, function() {
105 $(this).hide();
106 $('#header-wrap').removeClass('quicknav');
107 });
108 }
Scott Main3b90aff2013-08-01 18:09:35 -0700109
110
Scott Maine4d8f1b2012-06-21 18:03:05 -0700111 //Set up search
112 $("#search_autocomplete").focus(function() {
113 $("#search-container").addClass('active');
114 })
115 $("#search-container").mouseover(function() {
116 $("#search-container").addClass('active');
117 $("#search_autocomplete").focus();
118 })
119 $("#search-container").mouseout(function() {
120 if ($("#search_autocomplete").is(":focus")) return;
121 if ($("#search_autocomplete").val() == '') {
122 setTimeout(function(){
123 $("#search-container").removeClass('active');
124 $("#search_autocomplete").blur();
125 },250);
126 }
127 })
128 $("#search_autocomplete").blur(function() {
129 if ($("#search_autocomplete").val() == '') {
130 $("#search-container").removeClass('active');
131 }
132 })
133
Scott Main3b90aff2013-08-01 18:09:35 -0700134
Scott Maine4d8f1b2012-06-21 18:03:05 -0700135 // prep nav expandos
136 var pagePath = document.location.pathname;
137 // account for intl docs by removing the intl/*/ path
138 if (pagePath.indexOf("/intl/") == 0) {
139 pagePath = pagePath.substr(pagePath.indexOf("/",6)); // start after intl/ to get last /
140 }
Scott Mainac2aef52013-02-12 14:15:23 -0800141
Scott Maine4d8f1b2012-06-21 18:03:05 -0700142 if (pagePath.indexOf(SITE_ROOT) == 0) {
143 if (pagePath == '' || pagePath.charAt(pagePath.length - 1) == '/') {
144 pagePath += 'index.html';
145 }
146 }
147
Scott Main01a25452013-02-12 17:32:27 -0800148 // Need a copy of the pagePath before it gets changed in the next block;
149 // it's needed to perform proper tab highlighting in offline docs (see rootDir below)
150 var pagePathOriginal = pagePath;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700151 if (SITE_ROOT.match(/\.\.\//) || SITE_ROOT == '') {
152 // If running locally, SITE_ROOT will be a relative path, so account for that by
153 // finding the relative URL to this page. This will allow us to find links on the page
154 // leading back to this page.
155 var pathParts = pagePath.split('/');
156 var relativePagePathParts = [];
157 var upDirs = (SITE_ROOT.match(/(\.\.\/)+/) || [''])[0].length / 3;
158 for (var i = 0; i < upDirs; i++) {
159 relativePagePathParts.push('..');
160 }
161 for (var i = 0; i < upDirs; i++) {
162 relativePagePathParts.push(pathParts[pathParts.length - (upDirs - i) - 1]);
163 }
164 relativePagePathParts.push(pathParts[pathParts.length - 1]);
165 pagePath = relativePagePathParts.join('/');
166 } else {
167 // Otherwise the page path is already an absolute URL
168 }
169
Scott Mainac2aef52013-02-12 14:15:23 -0800170 // Highlight the header tabs...
171 // highlight Design tab
172 if ($("body").hasClass("design")) {
173 $("#header li.design a").addClass("selected");
Dirk Doughertyc3921652014-05-13 16:55:26 -0700174 $("#sticky-header").addClass("design");
Scott Mainac2aef52013-02-12 14:15:23 -0800175
176 // highlight Develop tab
177 } else if ($("body").hasClass("develop") || $("body").hasClass("google")) {
178 $("#header li.develop a").addClass("selected");
Dirk Doughertyc3921652014-05-13 16:55:26 -0700179 $("#sticky-header").addClass("develop");
Scott Mainac2aef52013-02-12 14:15:23 -0800180 // In Develop docs, also highlight appropriate sub-tab
Scott Main01a25452013-02-12 17:32:27 -0800181 var rootDir = pagePathOriginal.substring(1,pagePathOriginal.indexOf('/', 1));
Scott Mainac2aef52013-02-12 14:15:23 -0800182 if (rootDir == "training") {
183 $("#nav-x li.training a").addClass("selected");
184 } else if (rootDir == "guide") {
185 $("#nav-x li.guide a").addClass("selected");
186 } else if (rootDir == "reference") {
187 // If the root is reference, but page is also part of Google Services, select Google
188 if ($("body").hasClass("google")) {
189 $("#nav-x li.google a").addClass("selected");
190 } else {
191 $("#nav-x li.reference a").addClass("selected");
192 }
193 } else if ((rootDir == "tools") || (rootDir == "sdk")) {
194 $("#nav-x li.tools a").addClass("selected");
195 } else if ($("body").hasClass("google")) {
196 $("#nav-x li.google a").addClass("selected");
Dirk Dougherty4f7e5152010-09-16 10:43:40 -0700197 } else if ($("body").hasClass("samples")) {
198 $("#nav-x li.samples a").addClass("selected");
Scott Mainac2aef52013-02-12 14:15:23 -0800199 }
200
201 // highlight Distribute tab
202 } else if ($("body").hasClass("distribute")) {
203 $("#header li.distribute a").addClass("selected");
Dirk Doughertyc3921652014-05-13 16:55:26 -0700204 $("#sticky-header").addClass("distribute");
205
206 var baseFrag = pagePathOriginal.indexOf('/', 1) + 1;
207 var secondFrag = pagePathOriginal.substring(baseFrag, pagePathOriginal.indexOf('/', baseFrag));
208 if (secondFrag == "users") {
209 $("#nav-x li.users a").addClass("selected");
210 } else if (secondFrag == "engage") {
211 $("#nav-x li.engage a").addClass("selected");
212 } else if (secondFrag == "monetize") {
213 $("#nav-x li.monetize a").addClass("selected");
214 } else if (secondFrag == "tools") {
215 $("#nav-x li.disttools a").addClass("selected");
216 } else if (secondFrag == "stories") {
217 $("#nav-x li.stories a").addClass("selected");
218 } else if (secondFrag == "essentials") {
219 $("#nav-x li.essentials a").addClass("selected");
220 } else if (secondFrag == "googleplay") {
221 $("#nav-x li.googleplay a").addClass("selected");
222 }
223 } else if ($("body").hasClass("about")) {
224 $("#sticky-header").addClass("about");
Scott Mainb16376f2014-05-21 20:35:47 -0700225 }
Scott Mainac2aef52013-02-12 14:15:23 -0800226
Scott Mainf6145542013-04-01 16:38:11 -0700227 // set global variable so we can highlight the sidenav a bit later (such as for google reference)
228 // and highlight the sidenav
229 mPagePath = pagePath;
230 highlightSidenav();
Dirk Doughertyc3921652014-05-13 16:55:26 -0700231 buildBreadcrumbs();
Scott Mainac2aef52013-02-12 14:15:23 -0800232
Scott Mainf6145542013-04-01 16:38:11 -0700233 // set up prev/next links if they exist
Scott Maine4d8f1b2012-06-21 18:03:05 -0700234 var $selNavLink = $('#nav').find('a[href="' + pagePath + '"]');
Scott Main5a1123e2012-09-26 12:51:28 -0700235 var $selListItem;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700236 if ($selNavLink.length) {
Scott Mainac2aef52013-02-12 14:15:23 -0800237 $selListItem = $selNavLink.closest('li');
Scott Maine4d8f1b2012-06-21 18:03:05 -0700238
239 // set up prev links
240 var $prevLink = [];
241 var $prevListItem = $selListItem.prev('li');
Scott Main3b90aff2013-08-01 18:09:35 -0700242
Scott Maine4d8f1b2012-06-21 18:03:05 -0700243 var crossBoundaries = ($("body.design").length > 0) || ($("body.guide").length > 0) ? true :
244false; // navigate across topic boundaries only in design docs
245 if ($prevListItem.length) {
246 if ($prevListItem.hasClass('nav-section')) {
Scott Main5a1123e2012-09-26 12:51:28 -0700247 // jump to last topic of previous section
248 $prevLink = $prevListItem.find('a:last');
249 } else if (!$selListItem.hasClass('nav-section')) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700250 // jump to previous topic in this section
251 $prevLink = $prevListItem.find('a:eq(0)');
252 }
253 } else {
254 // jump to this section's index page (if it exists)
255 var $parentListItem = $selListItem.parents('li');
256 $prevLink = $selListItem.parents('li').find('a');
Scott Main3b90aff2013-08-01 18:09:35 -0700257
Scott Maine4d8f1b2012-06-21 18:03:05 -0700258 // except if cross boundaries aren't allowed, and we're at the top of a section already
259 // (and there's another parent)
Scott Main3b90aff2013-08-01 18:09:35 -0700260 if (!crossBoundaries && $parentListItem.hasClass('nav-section')
Scott Maine4d8f1b2012-06-21 18:03:05 -0700261 && $selListItem.hasClass('nav-section')) {
262 $prevLink = [];
263 }
264 }
265
Scott Maine4d8f1b2012-06-21 18:03:05 -0700266 // set up next links
267 var $nextLink = [];
Scott Maine4d8f1b2012-06-21 18:03:05 -0700268 var startClass = false;
269 var training = $(".next-class-link").length; // decides whether to provide "next class" link
270 var isCrossingBoundary = false;
Scott Main3b90aff2013-08-01 18:09:35 -0700271
Scott Main1a00f7f2013-10-29 11:11:19 -0700272 if ($selListItem.hasClass('nav-section') && $selListItem.children('div.empty').length == 0) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700273 // we're on an index page, jump to the first topic
Scott Mainb505ca62012-07-26 18:00:14 -0700274 $nextLink = $selListItem.find('ul:eq(0)').find('a:eq(0)');
Scott Maine4d8f1b2012-06-21 18:03:05 -0700275
276 // if there aren't any children, go to the next section (required for About pages)
277 if($nextLink.length == 0) {
278 $nextLink = $selListItem.next('li').find('a');
Scott Mainb505ca62012-07-26 18:00:14 -0700279 } else if ($('.topic-start-link').length) {
280 // as long as there's a child link and there is a "topic start link" (we're on a landing)
281 // then set the landing page "start link" text to be the first doc title
282 $('.topic-start-link').text($nextLink.text().toUpperCase());
Scott Maine4d8f1b2012-06-21 18:03:05 -0700283 }
Scott Main3b90aff2013-08-01 18:09:35 -0700284
Scott Main5a1123e2012-09-26 12:51:28 -0700285 // If the selected page has a description, then it's a class or article homepage
286 if ($selListItem.find('a[description]').length) {
287 // this means we're on a class landing page
Scott Maine4d8f1b2012-06-21 18:03:05 -0700288 startClass = true;
289 }
290 } else {
291 // jump to the next topic in this section (if it exists)
292 $nextLink = $selListItem.next('li').find('a:eq(0)');
Scott Main1a00f7f2013-10-29 11:11:19 -0700293 if ($nextLink.length == 0) {
Scott Main5a1123e2012-09-26 12:51:28 -0700294 isCrossingBoundary = true;
295 // no more topics in this section, jump to the first topic in the next section
296 $nextLink = $selListItem.parents('li:eq(0)').next('li.nav-section').find('a:eq(0)');
297 if (!$nextLink.length) { // Go up another layer to look for next page (lesson > class > course)
298 $nextLink = $selListItem.parents('li:eq(1)').next('li.nav-section').find('a:eq(0)');
Scott Main1a00f7f2013-10-29 11:11:19 -0700299 if ($nextLink.length == 0) {
300 // if that doesn't work, we're at the end of the list, so disable NEXT link
301 $('.next-page-link').attr('href','').addClass("disabled")
302 .click(function() { return false; });
303 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700304 }
305 }
306 }
Scott Main5a1123e2012-09-26 12:51:28 -0700307
308 if (startClass) {
309 $('.start-class-link').attr('href', $nextLink.attr('href')).removeClass("hide");
310
Scott Main3b90aff2013-08-01 18:09:35 -0700311 // if there's no training bar (below the start button),
Scott Main5a1123e2012-09-26 12:51:28 -0700312 // then we need to add a bottom border to button
313 if (!$("#tb").length) {
314 $('.start-class-link').css({'border-bottom':'1px solid #DADADA'});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700315 }
Scott Main5a1123e2012-09-26 12:51:28 -0700316 } else if (isCrossingBoundary && !$('body.design').length) { // Design always crosses boundaries
317 $('.content-footer.next-class').show();
318 $('.next-page-link').attr('href','')
319 .removeClass("hide").addClass("disabled")
320 .click(function() { return false; });
Scott Main1a00f7f2013-10-29 11:11:19 -0700321 if ($nextLink.length) {
322 $('.next-class-link').attr('href',$nextLink.attr('href'))
323 .removeClass("hide").append($nextLink.html());
324 $('.next-class-link').find('.new').empty();
325 }
Scott Main5a1123e2012-09-26 12:51:28 -0700326 } else {
327 $('.next-page-link').attr('href', $nextLink.attr('href')).removeClass("hide");
328 }
329
330 if (!startClass && $prevLink.length) {
331 var prevHref = $prevLink.attr('href');
332 if (prevHref == SITE_ROOT + 'index.html') {
333 // Don't show Previous when it leads to the homepage
334 } else {
335 $('.prev-page-link').attr('href', $prevLink.attr('href')).removeClass("hide");
336 }
Scott Main3b90aff2013-08-01 18:09:35 -0700337 }
Scott Main5a1123e2012-09-26 12:51:28 -0700338
339 // If this is a training 'article', there should be no prev/next nav
340 // ... if the grandparent is the "nav" ... and it has no child list items...
341 if (training && $selListItem.parents('ul').eq(1).is('[id="nav"]') &&
342 !$selListItem.find('li').length) {
343 $('.next-page-link,.prev-page-link').attr('href','').addClass("disabled")
344 .click(function() { return false; });
Scott Maine4d8f1b2012-06-21 18:03:05 -0700345 }
Scott Main3b90aff2013-08-01 18:09:35 -0700346
Scott Maine4d8f1b2012-06-21 18:03:05 -0700347 }
Scott Main3b90aff2013-08-01 18:09:35 -0700348
349
350
Scott Main5a1123e2012-09-26 12:51:28 -0700351 // Set up the course landing pages for Training with class names and descriptions
352 if ($('body.trainingcourse').length) {
353 var $classLinks = $selListItem.find('ul li a').not('#nav .nav-section .nav-section ul a');
Scott Maine7d75352014-05-22 15:50:56 -0700354
355 // create an array for all the class descriptions
356 var $classDescriptions = new Array($classLinks.length);
357 var lang = getLangPref();
358 $classLinks.each(function(index) {
359 var langDescr = $(this).attr(lang + "-description");
360 if (typeof langDescr !== 'undefined' && langDescr !== false) {
361 // if there's a class description in the selected language, use that
362 $classDescriptions[index] = langDescr;
363 } else {
364 // otherwise, use the default english description
365 $classDescriptions[index] = $(this).attr("description");
366 }
367 });
Scott Main3b90aff2013-08-01 18:09:35 -0700368
Scott Main5a1123e2012-09-26 12:51:28 -0700369 var $olClasses = $('<ol class="class-list"></ol>');
370 var $liClass;
371 var $imgIcon;
372 var $h2Title;
373 var $pSummary;
374 var $olLessons;
375 var $liLesson;
376 $classLinks.each(function(index) {
377 $liClass = $('<li></li>');
378 $h2Title = $('<a class="title" href="'+$(this).attr('href')+'"><h2>' + $(this).html()+'</h2><span></span></a>');
Scott Maine7d75352014-05-22 15:50:56 -0700379 $pSummary = $('<p class="description">' + $classDescriptions[index] + '</p>');
Scott Main3b90aff2013-08-01 18:09:35 -0700380
Scott Main5a1123e2012-09-26 12:51:28 -0700381 $olLessons = $('<ol class="lesson-list"></ol>');
Scott Main3b90aff2013-08-01 18:09:35 -0700382
Scott Main5a1123e2012-09-26 12:51:28 -0700383 $lessons = $(this).closest('li').find('ul li a');
Scott Main3b90aff2013-08-01 18:09:35 -0700384
Scott Main5a1123e2012-09-26 12:51:28 -0700385 if ($lessons.length) {
Scott Main3b90aff2013-08-01 18:09:35 -0700386 $imgIcon = $('<img src="'+toRoot+'assets/images/resource-tutorial.png" '
387 + ' width="64" height="64" alt=""/>');
Scott Main5a1123e2012-09-26 12:51:28 -0700388 $lessons.each(function(index) {
389 $olLessons.append('<li><a href="'+$(this).attr('href')+'">' + $(this).html()+'</a></li>');
390 });
391 } else {
Scott Main3b90aff2013-08-01 18:09:35 -0700392 $imgIcon = $('<img src="'+toRoot+'assets/images/resource-article.png" '
393 + ' width="64" height="64" alt=""/>');
Scott Main5a1123e2012-09-26 12:51:28 -0700394 $pSummary.addClass('article');
395 }
396
397 $liClass.append($h2Title).append($imgIcon).append($pSummary).append($olLessons);
398 $olClasses.append($liClass);
399 });
400 $('.jd-descr').append($olClasses);
401 }
402
Scott Maine4d8f1b2012-06-21 18:03:05 -0700403 // Set up expand/collapse behavior
Scott Mainad08f072013-08-20 16:49:57 -0700404 initExpandableNavItems("#nav");
Scott Main3b90aff2013-08-01 18:09:35 -0700405
Scott Main3b90aff2013-08-01 18:09:35 -0700406
Scott Maine4d8f1b2012-06-21 18:03:05 -0700407 $(".scroll-pane").scroll(function(event) {
408 event.preventDefault();
409 return false;
410 });
411
412 /* Resize nav height when window height changes */
413 $(window).resize(function() {
414 if ($('#side-nav').length == 0) return;
415 var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
416 setNavBarLeftPos(); // do this even if sidenav isn't fixed because it could become fixed
417 // make sidenav behave when resizing the window and side-scolling is a concern
Scott Mainf5257812014-05-22 17:26:38 -0700418 if (sticky) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700419 if ((stylesheet.attr("disabled") == "disabled") || stylesheet.length == 0) {
420 updateSideNavPosition();
421 } else {
422 updateSidenavFullscreenWidth();
423 }
424 }
425 resizeNav();
426 });
427
428
Scott Maine4d8f1b2012-06-21 18:03:05 -0700429 var navBarLeftPos;
430 if ($('#devdoc-nav').length) {
431 setNavBarLeftPos();
432 }
433
434
Scott Maine4d8f1b2012-06-21 18:03:05 -0700435 // Set up play-on-hover <video> tags.
436 $('video.play-on-hover').bind('click', function(){
437 $(this).get(0).load(); // in case the video isn't seekable
438 $(this).get(0).play();
439 });
440
441 // Set up tooltips
442 var TOOLTIP_MARGIN = 10;
Scott Maindb3678b2012-10-23 14:13:41 -0700443 $('acronym,.tooltip-link').each(function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700444 var $target = $(this);
445 var $tooltip = $('<div>')
446 .addClass('tooltip-box')
Scott Maindb3678b2012-10-23 14:13:41 -0700447 .append($target.attr('title'))
Scott Maine4d8f1b2012-06-21 18:03:05 -0700448 .hide()
449 .appendTo('body');
450 $target.removeAttr('title');
451
452 $target.hover(function() {
453 // in
454 var targetRect = $target.offset();
455 targetRect.width = $target.width();
456 targetRect.height = $target.height();
457
458 $tooltip.css({
459 left: targetRect.left,
460 top: targetRect.top + targetRect.height + TOOLTIP_MARGIN
461 });
462 $tooltip.addClass('below');
463 $tooltip.show();
464 }, function() {
465 // out
466 $tooltip.hide();
467 });
468 });
469
470 // Set up <h2> deeplinks
471 $('h2').click(function() {
472 var id = $(this).attr('id');
473 if (id) {
474 document.location.hash = id;
475 }
476 });
477
478 //Loads the +1 button
479 var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
480 po.src = 'https://apis.google.com/js/plusone.js';
481 var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
482
483
Scott Main3b90aff2013-08-01 18:09:35 -0700484 // Revise the sidenav widths to make room for the scrollbar
Scott Maine4d8f1b2012-06-21 18:03:05 -0700485 // which avoids the visible width from changing each time the bar appears
486 var $sidenav = $("#side-nav");
487 var sidenav_width = parseInt($sidenav.innerWidth());
Scott Main3b90aff2013-08-01 18:09:35 -0700488
Scott Maine4d8f1b2012-06-21 18:03:05 -0700489 $("#devdoc-nav #nav").css("width", sidenav_width - 4 + "px"); // 4px is scrollbar width
490
491
492 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
Scott Main3b90aff2013-08-01 18:09:35 -0700493
Scott Maine4d8f1b2012-06-21 18:03:05 -0700494 if ($(".scroll-pane").length > 1) {
495 // Check if there's a user preference for the panel heights
496 var cookieHeight = readCookie("reference_height");
497 if (cookieHeight) {
498 restoreHeight(cookieHeight);
499 }
500 }
Scott Main3b90aff2013-08-01 18:09:35 -0700501
Scott Main06f3f2c2014-05-30 11:23:00 -0700502 // Resize once loading is finished
Scott Maine4d8f1b2012-06-21 18:03:05 -0700503 resizeNav();
Scott Main06f3f2c2014-05-30 11:23:00 -0700504 // Check if there's an anchor that we need to scroll into view.
505 // A delay is needed, because some browsers do not immediately scroll down to the anchor
506 window.setTimeout(offsetScrollForSticky, 100);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700507
Scott Main015d6162013-01-29 09:01:52 -0800508 /* init the language selector based on user cookie for lang */
509 loadLangPref();
510 changeNavLang(getLangPref());
511
512 /* setup event handlers to ensure the overflow menu is visible while picking lang */
513 $("#language select")
514 .mousedown(function() {
515 $("div.morehover").addClass("hover"); })
516 .blur(function() {
517 $("div.morehover").removeClass("hover"); });
518
519 /* some global variable setup */
520 resizePackagesNav = $("#resize-packages-nav");
521 classesNav = $("#classes-nav");
522 devdocNav = $("#devdoc-nav");
523
524 var cookiePath = "";
525 if (location.href.indexOf("/reference/") != -1) {
526 cookiePath = "reference_";
527 } else if (location.href.indexOf("/guide/") != -1) {
528 cookiePath = "guide_";
529 } else if (location.href.indexOf("/tools/") != -1) {
530 cookiePath = "tools_";
531 } else if (location.href.indexOf("/training/") != -1) {
532 cookiePath = "training_";
533 } else if (location.href.indexOf("/design/") != -1) {
534 cookiePath = "design_";
535 } else if (location.href.indexOf("/distribute/") != -1) {
536 cookiePath = "distribute_";
537 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700538
539});
Scott Main7e447ed2013-02-19 17:22:37 -0800540// END of the onload event
Scott Maine4d8f1b2012-06-21 18:03:05 -0700541
542
Scott Mainad08f072013-08-20 16:49:57 -0700543function initExpandableNavItems(rootTag) {
544 $(rootTag + ' li.nav-section .nav-section-header').click(function() {
545 var section = $(this).closest('li.nav-section');
546 if (section.hasClass('expanded')) {
Scott Mainf0093852013-08-22 11:37:11 -0700547 /* hide me and descendants */
548 section.find('ul').slideUp(250, function() {
549 // remove 'expanded' class from my section and any children
Scott Mainad08f072013-08-20 16:49:57 -0700550 section.closest('li').removeClass('expanded');
Scott Mainf0093852013-08-22 11:37:11 -0700551 $('li.nav-section', section).removeClass('expanded');
Scott Mainad08f072013-08-20 16:49:57 -0700552 resizeNav();
553 });
554 } else {
555 /* show me */
556 // first hide all other siblings
Scott Main70557ee2013-10-30 14:47:40 -0700557 var $others = $('li.nav-section.expanded', $(this).closest('ul')).not('.sticky');
Scott Mainad08f072013-08-20 16:49:57 -0700558 $others.removeClass('expanded').children('ul').slideUp(250);
559
560 // now expand me
561 section.closest('li').addClass('expanded');
562 section.children('ul').slideDown(250, function() {
563 resizeNav();
564 });
565 }
566 });
Scott Mainf0093852013-08-22 11:37:11 -0700567
568 // Stop expand/collapse behavior when clicking on nav section links
569 // (since we're navigating away from the page)
570 // This selector captures the first instance of <a>, but not those with "#" as the href.
571 $('.nav-section-header').find('a:eq(0)').not('a[href="#"]').click(function(evt) {
572 window.location.href = $(this).attr('href');
573 return false;
574 });
Scott Mainad08f072013-08-20 16:49:57 -0700575}
576
Dirk Doughertyc3921652014-05-13 16:55:26 -0700577
578/** Create the list of breadcrumb links in the sticky header */
579function buildBreadcrumbs() {
580 var $breadcrumbUl = $("#sticky-header ul.breadcrumb");
581 // Add the secondary horizontal nav item, if provided
582 var $selectedSecondNav = $("div#nav-x ul.nav-x a.selected").clone().removeClass("selected");
583 if ($selectedSecondNav.length) {
584 $breadcrumbUl.prepend($("<li>").append($selectedSecondNav))
585 }
586 // Add the primary horizontal nav
587 var $selectedFirstNav = $("div#header-wrap ul.nav-x a.selected").clone().removeClass("selected");
588 // If there's no header nav item, use the logo link and title from alt text
589 if ($selectedFirstNav.length < 1) {
590 $selectedFirstNav = $("<a>")
591 .attr('href', $("div#header .logo a").attr('href'))
592 .text($("div#header .logo img").attr('alt'));
593 }
594 $breadcrumbUl.prepend($("<li>").append($selectedFirstNav));
595}
596
597
598
Scott Maine624b3f2013-09-12 12:56:41 -0700599/** Highlight the current page in sidenav, expanding children as appropriate */
Scott Mainf6145542013-04-01 16:38:11 -0700600function highlightSidenav() {
Scott Maine624b3f2013-09-12 12:56:41 -0700601 // if something is already highlighted, undo it. This is for dynamic navigation (Samples index)
602 if ($("ul#nav li.selected").length) {
603 unHighlightSidenav();
604 }
605 // look for URL in sidenav, including the hash
606 var $selNavLink = $('#nav').find('a[href="' + mPagePath + location.hash + '"]');
607
608 // If the selNavLink is still empty, look for it without the hash
609 if ($selNavLink.length == 0) {
610 $selNavLink = $('#nav').find('a[href="' + mPagePath + '"]');
611 }
612
Scott Mainf6145542013-04-01 16:38:11 -0700613 var $selListItem;
614 if ($selNavLink.length) {
Scott Mainf6145542013-04-01 16:38:11 -0700615 // Find this page's <li> in sidenav and set selected
616 $selListItem = $selNavLink.closest('li');
617 $selListItem.addClass('selected');
Scott Main3b90aff2013-08-01 18:09:35 -0700618
Scott Mainf6145542013-04-01 16:38:11 -0700619 // Traverse up the tree and expand all parent nav-sections
620 $selNavLink.parents('li.nav-section').each(function() {
621 $(this).addClass('expanded');
622 $(this).children('ul').show();
623 });
624 }
625}
626
Scott Maine624b3f2013-09-12 12:56:41 -0700627function unHighlightSidenav() {
628 $("ul#nav li.selected").removeClass("selected");
629 $('ul#nav li.nav-section.expanded').removeClass('expanded').children('ul').hide();
630}
Scott Maine4d8f1b2012-06-21 18:03:05 -0700631
632function toggleFullscreen(enable) {
633 var delay = 20;
634 var enabled = true;
635 var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
636 if (enable) {
637 // Currently NOT USING fullscreen; enable fullscreen
638 stylesheet.removeAttr('disabled');
639 $('#nav-swap .fullscreen').removeClass('disabled');
640 $('#devdoc-nav').css({left:''});
641 setTimeout(updateSidenavFullscreenWidth,delay); // need to wait a moment for css to switch
642 enabled = true;
643 } else {
644 // Currently USING fullscreen; disable fullscreen
645 stylesheet.attr('disabled', 'disabled');
646 $('#nav-swap .fullscreen').addClass('disabled');
647 setTimeout(updateSidenavFixedWidth,delay); // need to wait a moment for css to switch
648 enabled = false;
649 }
650 writeCookie("fullscreen", enabled, null, null);
651 setNavBarLeftPos();
652 resizeNav(delay);
653 updateSideNavPosition();
654 setTimeout(initSidenavHeightResize,delay);
655}
656
657
658function setNavBarLeftPos() {
659 navBarLeftPos = $('#body-content').offset().left;
660}
661
662
663function updateSideNavPosition() {
664 var newLeft = $(window).scrollLeft() - navBarLeftPos;
665 $('#devdoc-nav').css({left: -newLeft});
666 $('#devdoc-nav .totop').css({left: -(newLeft - parseInt($('#side-nav').css('margin-left')))});
667}
Scott Main3b90aff2013-08-01 18:09:35 -0700668
Scott Maine4d8f1b2012-06-21 18:03:05 -0700669// TODO: use $(document).ready instead
670function addLoadEvent(newfun) {
671 var current = window.onload;
672 if (typeof window.onload != 'function') {
673 window.onload = newfun;
674 } else {
675 window.onload = function() {
676 current();
677 newfun();
678 }
679 }
680}
681
682var agent = navigator['userAgent'].toLowerCase();
683// If a mobile phone, set flag and do mobile setup
684if ((agent.indexOf("mobile") != -1) || // android, iphone, ipod
685 (agent.indexOf("blackberry") != -1) ||
686 (agent.indexOf("webos") != -1) ||
687 (agent.indexOf("mini") != -1)) { // opera mini browsers
688 isMobile = true;
689}
690
691
Scott Main498d7102013-08-21 15:47:38 -0700692$(document).ready(function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700693 $("pre:not(.no-pretty-print)").addClass("prettyprint");
694 prettyPrint();
Scott Main498d7102013-08-21 15:47:38 -0700695});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700696
Scott Maine4d8f1b2012-06-21 18:03:05 -0700697
698
699
700/* ######### RESIZE THE SIDENAV HEIGHT ########## */
701
702function resizeNav(delay) {
703 var $nav = $("#devdoc-nav");
704 var $window = $(window);
705 var navHeight;
Scott Main3b90aff2013-08-01 18:09:35 -0700706
Scott Maine4d8f1b2012-06-21 18:03:05 -0700707 // Get the height of entire window and the total header height.
708 // Then figure out based on scroll position whether the header is visible
709 var windowHeight = $window.height();
710 var scrollTop = $window.scrollTop();
Dirk Doughertyc3921652014-05-13 16:55:26 -0700711 var headerHeight = $('#header-wrapper').outerHeight();
712 var headerVisible = scrollTop < stickyTop;
Scott Main3b90aff2013-08-01 18:09:35 -0700713
714 // get the height of space between nav and top of window.
Scott Maine4d8f1b2012-06-21 18:03:05 -0700715 // Could be either margin or top position, depending on whether the nav is fixed.
Scott Main3b90aff2013-08-01 18:09:35 -0700716 var topMargin = (parseInt($nav.css('margin-top')) || parseInt($nav.css('top'))) + 1;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700717 // add 1 for the #side-nav bottom margin
Scott Main3b90aff2013-08-01 18:09:35 -0700718
Scott Maine4d8f1b2012-06-21 18:03:05 -0700719 // Depending on whether the header is visible, set the side nav's height.
720 if (headerVisible) {
721 // The sidenav height grows as the header goes off screen
Dirk Doughertyc3921652014-05-13 16:55:26 -0700722 navHeight = windowHeight - (headerHeight - scrollTop) - topMargin;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700723 } else {
724 // Once header is off screen, the nav height is almost full window height
725 navHeight = windowHeight - topMargin;
726 }
Scott Main3b90aff2013-08-01 18:09:35 -0700727
728
729
Scott Maine4d8f1b2012-06-21 18:03:05 -0700730 $scrollPanes = $(".scroll-pane");
731 if ($scrollPanes.length > 1) {
732 // subtract the height of the api level widget and nav swapper from the available nav height
733 navHeight -= ($('#api-nav-header').outerHeight(true) + $('#nav-swap').outerHeight(true));
Scott Main3b90aff2013-08-01 18:09:35 -0700734
Scott Maine4d8f1b2012-06-21 18:03:05 -0700735 $("#swapper").css({height:navHeight + "px"});
736 if ($("#nav-tree").is(":visible")) {
737 $("#nav-tree").css({height:navHeight});
738 }
Scott Main3b90aff2013-08-01 18:09:35 -0700739
740 var classesHeight = navHeight - parseInt($("#resize-packages-nav").css("height")) - 10 + "px";
Scott Maine4d8f1b2012-06-21 18:03:05 -0700741 //subtract 10px to account for drag bar
Scott Main3b90aff2013-08-01 18:09:35 -0700742
743 // if the window becomes small enough to make the class panel height 0,
Scott Maine4d8f1b2012-06-21 18:03:05 -0700744 // then the package panel should begin to shrink
745 if (parseInt(classesHeight) <= 0) {
746 $("#resize-packages-nav").css({height:navHeight - 10}); //subtract 10px for drag bar
747 $("#packages-nav").css({height:navHeight - 10});
748 }
Scott Main3b90aff2013-08-01 18:09:35 -0700749
Scott Maine4d8f1b2012-06-21 18:03:05 -0700750 $("#classes-nav").css({'height':classesHeight, 'margin-top':'10px'});
751 $("#classes-nav .jspContainer").css({height:classesHeight});
Scott Main3b90aff2013-08-01 18:09:35 -0700752
753
Scott Maine4d8f1b2012-06-21 18:03:05 -0700754 } else {
755 $nav.height(navHeight);
756 }
Scott Main3b90aff2013-08-01 18:09:35 -0700757
Scott Maine4d8f1b2012-06-21 18:03:05 -0700758 if (delay) {
759 updateFromResize = true;
760 delayedReInitScrollbars(delay);
761 } else {
762 reInitScrollbars();
763 }
Scott Main3b90aff2013-08-01 18:09:35 -0700764
Scott Maine4d8f1b2012-06-21 18:03:05 -0700765}
766
767var updateScrollbars = false;
768var updateFromResize = false;
769
770/* Re-initialize the scrollbars to account for changed nav size.
771 * This method postpones the actual update by a 1/4 second in order to optimize the
772 * scroll performance while the header is still visible, because re-initializing the
773 * scroll panes is an intensive process.
774 */
775function delayedReInitScrollbars(delay) {
776 // If we're scheduled for an update, but have received another resize request
777 // before the scheduled resize has occured, just ignore the new request
778 // (and wait for the scheduled one).
779 if (updateScrollbars && updateFromResize) {
780 updateFromResize = false;
781 return;
782 }
Scott Main3b90aff2013-08-01 18:09:35 -0700783
Scott Maine4d8f1b2012-06-21 18:03:05 -0700784 // We're scheduled for an update and the update request came from this method's setTimeout
785 if (updateScrollbars && !updateFromResize) {
786 reInitScrollbars();
787 updateScrollbars = false;
788 } else {
789 updateScrollbars = true;
790 updateFromResize = false;
791 setTimeout('delayedReInitScrollbars()',delay);
792 }
793}
794
795/* Re-initialize the scrollbars to account for changed nav size. */
796function reInitScrollbars() {
797 var pane = $(".scroll-pane").each(function(){
798 var api = $(this).data('jsp');
799 if (!api) { setTimeout(reInitScrollbars,300); return;}
800 api.reinitialise( {verticalGutter:0} );
Scott Main3b90aff2013-08-01 18:09:35 -0700801 });
Scott Maine4d8f1b2012-06-21 18:03:05 -0700802 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
803}
804
805
806/* Resize the height of the nav panels in the reference,
807 * and save the new size to a cookie */
808function saveNavPanels() {
809 var basePath = getBaseUri(location.pathname);
810 var section = basePath.substring(1,basePath.indexOf("/",1));
811 writeCookie("height", resizePackagesNav.css("height"), section, null);
812}
813
814
815
816function restoreHeight(packageHeight) {
817 $("#resize-packages-nav").height(packageHeight);
818 $("#packages-nav").height(packageHeight);
819 // var classesHeight = navHeight - packageHeight;
820 // $("#classes-nav").css({height:classesHeight});
821 // $("#classes-nav .jspContainer").css({height:classesHeight});
822}
823
824
825
826/* ######### END RESIZE THE SIDENAV HEIGHT ########## */
827
828
829
830
831
Scott Main3b90aff2013-08-01 18:09:35 -0700832/** Scroll the jScrollPane to make the currently selected item visible
Scott Maine4d8f1b2012-06-21 18:03:05 -0700833 This is called when the page finished loading. */
834function scrollIntoView(nav) {
835 var $nav = $("#"+nav);
836 var element = $nav.jScrollPane({/* ...settings... */});
837 var api = element.data('jsp');
838
839 if ($nav.is(':visible')) {
840 var $selected = $(".selected", $nav);
Scott Mainbc729572013-07-30 18:00:51 -0700841 if ($selected.length == 0) {
842 // If no selected item found, exit
843 return;
844 }
Scott Main52dd2062013-08-15 12:22:28 -0700845 // get the selected item's offset from its container nav by measuring the item's offset
846 // relative to the document then subtract the container nav's offset relative to the document
847 var selectedOffset = $selected.offset().top - $nav.offset().top;
848 if (selectedOffset > $nav.height() * .8) { // multiply nav height by .8 so we move up the item
849 // if it's more than 80% down the nav
850 // scroll the item up by an amount equal to 80% the container nav's height
851 api.scrollTo(0, selectedOffset - ($nav.height() * .8), false);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700852 }
853 }
854}
855
856
857
858
859
860
861/* Show popup dialogs */
862function showDialog(id) {
863 $dialog = $("#"+id);
864 $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>');
865 $dialog.wrapInner('<div/>');
866 $dialog.removeClass("hide");
867}
868
869
870
871
872
873/* ######### COOKIES! ########## */
874
875function readCookie(cookie) {
876 var myCookie = cookie_namespace+"_"+cookie+"=";
877 if (document.cookie) {
878 var index = document.cookie.indexOf(myCookie);
879 if (index != -1) {
880 var valStart = index + myCookie.length;
881 var valEnd = document.cookie.indexOf(";", valStart);
882 if (valEnd == -1) {
883 valEnd = document.cookie.length;
884 }
885 var val = document.cookie.substring(valStart, valEnd);
886 return val;
887 }
888 }
889 return 0;
890}
891
892function writeCookie(cookie, val, section, expiration) {
893 if (val==undefined) return;
894 section = section == null ? "_" : "_"+section+"_";
895 if (expiration == null) {
896 var date = new Date();
897 date.setTime(date.getTime()+(10*365*24*60*60*1000)); // default expiration is one week
898 expiration = date.toGMTString();
899 }
Scott Main3b90aff2013-08-01 18:09:35 -0700900 var cookieValue = cookie_namespace + section + cookie + "=" + val
Scott Maine4d8f1b2012-06-21 18:03:05 -0700901 + "; expires=" + expiration+"; path=/";
902 document.cookie = cookieValue;
903}
904
905/* ######### END COOKIES! ########## */
906
907
Dirk Doughertyca1230c2014-05-14 20:00:03 -0700908var sticky = false;
Dirk Doughertyc3921652014-05-13 16:55:26 -0700909var stickyTop;
Dirk Doughertyca1230c2014-05-14 20:00:03 -0700910var prevScrollLeft = 0; // used to compare current position to previous position of horiz scroll
Dirk Doughertyc3921652014-05-13 16:55:26 -0700911/* Sets the vertical scoll position at which the sticky bar should appear.
912 This method is called to reset the position when search results appear or hide */
913function setStickyTop() {
914 stickyTop = $('#header-wrapper').outerHeight() - $('#sticky-header').outerHeight();
915}
Scott Maine4d8f1b2012-06-21 18:03:05 -0700916
Dirk Doughertyca1230c2014-05-14 20:00:03 -0700917/*
Scott Mainb16376f2014-05-21 20:35:47 -0700918 * Displays sticky nav bar on pages when dac header scrolls out of view
Dirk Doughertyc3921652014-05-13 16:55:26 -0700919 */
Dirk Doughertyca1230c2014-05-14 20:00:03 -0700920$(window).scroll(function(event) {
921
922 setStickyTop();
923 var hiding = false;
924 var $stickyEl = $('#sticky-header');
925 var $menuEl = $('.menu-container');
926 // Exit if there's no sidenav
927 if ($('#side-nav').length == 0) return;
928 // Exit if the mouse target is a DIV, because that means the event is coming
929 // from a scrollable div and so there's no need to make adjustments to our layout
930 if ($(event.target).nodeName == "DIV") {
931 return;
932 }
933
934 var top = $(window).scrollTop();
935 // we set the navbar fixed when the scroll position is beyond the height of the site header...
936 var shouldBeSticky = top >= stickyTop;
937 // ... except if the document content is shorter than the sidenav height.
938 // (this is necessary to avoid crazy behavior on OSX Lion due to overscroll bouncing)
939 if ($("#doc-col").height() < $("#side-nav").height()) {
940 shouldBeSticky = false;
941 }
Scott Mainf5257812014-05-22 17:26:38 -0700942 // Account for horizontal scroll
943 var scrollLeft = $(window).scrollLeft();
944 // When the sidenav is fixed and user scrolls horizontally, reposition the sidenav to match
945 if (sticky && (scrollLeft != prevScrollLeft)) {
946 updateSideNavPosition();
947 prevScrollLeft = scrollLeft;
948 }
Dirk Doughertyca1230c2014-05-14 20:00:03 -0700949
950 // Don't continue if the header is sufficently far away
951 // (to avoid intensive resizing that slows scrolling)
952 if (sticky == shouldBeSticky) {
953 return;
954 }
Dirk Doughertyca1230c2014-05-14 20:00:03 -0700955
956 // If sticky header visible and position is now near top, hide sticky
957 if (sticky && !shouldBeSticky) {
958 sticky = false;
959 hiding = true;
960 // make the sidenav static again
961 $('#devdoc-nav')
962 .removeClass('fixed')
963 .css({'width':'auto','margin':''})
964 .prependTo('#side-nav');
965 // delay hide the sticky
966 $menuEl.removeClass('sticky-menu');
967 $stickyEl.fadeOut(250);
968 hiding = false;
969
970 // update the sidenaav position for side scrolling
971 updateSideNavPosition();
972 } else if (!sticky && shouldBeSticky) {
973 sticky = true;
974 $stickyEl.fadeIn(10);
975 $menuEl.addClass('sticky-menu');
976
977 // make the sidenav fixed
978 var width = $('#devdoc-nav').width();
979 $('#devdoc-nav')
980 .addClass('fixed')
981 .css({'width':width+'px'})
982 .prependTo('#body-content');
983
984 // update the sidenaav position for side scrolling
985 updateSideNavPosition();
986
987 } else if (hiding && top < 15) {
988 $menuEl.removeClass('sticky-menu');
989 $stickyEl.hide();
990 hiding = false;
991 }
992 resizeNav(250); // pass true in order to delay the scrollbar re-initialization for performance
993});
994
995/*
996 * Manages secion card states and nav resize to conclude loading
997 */
Dirk Doughertyc3921652014-05-13 16:55:26 -0700998(function() {
999 $(document).ready(function() {
1000
Dirk Doughertyc3921652014-05-13 16:55:26 -07001001 // Stack hover states
1002 $('.section-card-menu').each(function(index, el) {
1003 var height = $(el).height();
1004 $(el).css({height:height+'px', position:'relative'});
1005 var $cardInfo = $(el).find('.card-info');
1006
1007 $cardInfo.css({position: 'absolute', bottom:'0px', left:'0px', right:'0px', overflow:'visible'});
1008 });
1009
Dirk Doughertyc3921652014-05-13 16:55:26 -07001010 });
1011
1012})();
1013
Scott Maine4d8f1b2012-06-21 18:03:05 -07001014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
Scott Maind7026f72013-06-17 15:08:49 -07001027/* MISC LIBRARY FUNCTIONS */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001028
1029
1030
1031
1032
1033function toggle(obj, slide) {
1034 var ul = $("ul:first", obj);
1035 var li = ul.parent();
1036 if (li.hasClass("closed")) {
1037 if (slide) {
1038 ul.slideDown("fast");
1039 } else {
1040 ul.show();
1041 }
1042 li.removeClass("closed");
1043 li.addClass("open");
1044 $(".toggle-img", li).attr("title", "hide pages");
1045 } else {
1046 ul.slideUp("fast");
1047 li.removeClass("open");
1048 li.addClass("closed");
1049 $(".toggle-img", li).attr("title", "show pages");
1050 }
1051}
1052
1053
Scott Maine4d8f1b2012-06-21 18:03:05 -07001054function buildToggleLists() {
1055 $(".toggle-list").each(
1056 function(i) {
1057 $("div:first", this).append("<a class='toggle-img' href='#' title='show pages' onClick='toggle(this.parentNode.parentNode, true); return false;'></a>");
1058 $(this).addClass("closed");
1059 });
1060}
1061
1062
1063
Scott Maind7026f72013-06-17 15:08:49 -07001064function hideNestedItems(list, toggle) {
1065 $list = $(list);
1066 // hide nested lists
1067 if($list.hasClass('showing')) {
1068 $("li ol", $list).hide('fast');
1069 $list.removeClass('showing');
1070 // show nested lists
1071 } else {
1072 $("li ol", $list).show('fast');
1073 $list.addClass('showing');
1074 }
1075 $(".more,.less",$(toggle)).toggle();
1076}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105/* REFERENCE NAV SWAP */
1106
1107
1108function getNavPref() {
1109 var v = readCookie('reference_nav');
1110 if (v != NAV_PREF_TREE) {
1111 v = NAV_PREF_PANELS;
1112 }
1113 return v;
1114}
1115
1116function chooseDefaultNav() {
1117 nav_pref = getNavPref();
1118 if (nav_pref == NAV_PREF_TREE) {
1119 $("#nav-panels").toggle();
1120 $("#panel-link").toggle();
1121 $("#nav-tree").toggle();
1122 $("#tree-link").toggle();
1123 }
1124}
1125
1126function swapNav() {
1127 if (nav_pref == NAV_PREF_TREE) {
1128 nav_pref = NAV_PREF_PANELS;
1129 } else {
1130 nav_pref = NAV_PREF_TREE;
1131 init_default_navtree(toRoot);
1132 }
1133 var date = new Date();
1134 date.setTime(date.getTime()+(10*365*24*60*60*1000)); // keep this for 10 years
1135 writeCookie("nav", nav_pref, "reference", date.toGMTString());
1136
1137 $("#nav-panels").toggle();
1138 $("#panel-link").toggle();
1139 $("#nav-tree").toggle();
1140 $("#tree-link").toggle();
Scott Main3b90aff2013-08-01 18:09:35 -07001141
Scott Maine4d8f1b2012-06-21 18:03:05 -07001142 resizeNav();
1143
1144 // Gross nasty hack to make tree view show up upon first swap by setting height manually
1145 $("#nav-tree .jspContainer:visible")
1146 .css({'height':$("#nav-tree .jspContainer .jspPane").height() +'px'});
1147 // Another nasty hack to make the scrollbar appear now that we have height
1148 resizeNav();
Scott Main3b90aff2013-08-01 18:09:35 -07001149
Scott Maine4d8f1b2012-06-21 18:03:05 -07001150 if ($("#nav-tree").is(':visible')) {
1151 scrollIntoView("nav-tree");
1152 } else {
1153 scrollIntoView("packages-nav");
1154 scrollIntoView("classes-nav");
1155 }
1156}
1157
1158
1159
Scott Mainf5089842012-08-14 16:31:07 -07001160/* ############################################ */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001161/* ########## LOCALIZATION ############ */
Scott Mainf5089842012-08-14 16:31:07 -07001162/* ############################################ */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001163
1164function getBaseUri(uri) {
1165 var intlUrl = (uri.substring(0,6) == "/intl/");
1166 if (intlUrl) {
1167 base = uri.substring(uri.indexOf('intl/')+5,uri.length);
1168 base = base.substring(base.indexOf('/')+1, base.length);
1169 //alert("intl, returning base url: /" + base);
1170 return ("/" + base);
1171 } else {
1172 //alert("not intl, returning uri as found.");
1173 return uri;
1174 }
1175}
1176
1177function requestAppendHL(uri) {
1178//append "?hl=<lang> to an outgoing request (such as to blog)
1179 var lang = getLangPref();
1180 if (lang) {
1181 var q = 'hl=' + lang;
1182 uri += '?' + q;
1183 window.location = uri;
1184 return false;
1185 } else {
1186 return true;
1187 }
1188}
1189
1190
Scott Maine4d8f1b2012-06-21 18:03:05 -07001191function changeNavLang(lang) {
Scott Main6eb95f12012-10-02 17:12:23 -07001192 var $links = $("#devdoc-nav,#header,#nav-x,.training-nav-top,.content-footer").find("a["+lang+"-lang]");
1193 $links.each(function(i){ // for each link with a translation
1194 var $link = $(this);
1195 if (lang != "en") { // No need to worry about English, because a language change invokes new request
1196 // put the desired language from the attribute as the text
1197 $link.text($link.attr(lang+"-lang"))
Scott Maine4d8f1b2012-06-21 18:03:05 -07001198 }
Scott Main6eb95f12012-10-02 17:12:23 -07001199 });
Scott Maine4d8f1b2012-06-21 18:03:05 -07001200}
1201
Scott Main015d6162013-01-29 09:01:52 -08001202function changeLangPref(lang, submit) {
Scott Maine4d8f1b2012-06-21 18:03:05 -07001203 var date = new Date();
Scott Main3b90aff2013-08-01 18:09:35 -07001204 expires = date.toGMTString(date.setTime(date.getTime()+(10*365*24*60*60*1000)));
Scott Maine4d8f1b2012-06-21 18:03:05 -07001205 // keep this for 50 years
1206 //alert("expires: " + expires)
1207 writeCookie("pref_lang", lang, null, expires);
Scott Main015d6162013-01-29 09:01:52 -08001208
1209 // ####### TODO: Remove this condition once we're stable on devsite #######
1210 // This condition is only needed if we still need to support legacy GAE server
1211 if (devsite) {
1212 // Switch language when on Devsite server
1213 if (submit) {
1214 $("#setlang").submit();
1215 }
1216 } else {
1217 // Switch language when on legacy GAE server
Scott Main015d6162013-01-29 09:01:52 -08001218 if (submit) {
1219 window.location = getBaseUri(location.pathname);
1220 }
Scott Maine4d8f1b2012-06-21 18:03:05 -07001221 }
1222}
1223
1224function loadLangPref() {
1225 var lang = readCookie("pref_lang");
1226 if (lang != 0) {
1227 $("#language").find("option[value='"+lang+"']").attr("selected",true);
1228 }
1229}
1230
1231function getLangPref() {
1232 var lang = $("#language").find(":selected").attr("value");
1233 if (!lang) {
1234 lang = readCookie("pref_lang");
1235 }
1236 return (lang != 0) ? lang : 'en';
1237}
1238
1239/* ########## END LOCALIZATION ############ */
1240
1241
1242
1243
1244
1245
1246/* Used to hide and reveal supplemental content, such as long code samples.
1247 See the companion CSS in android-developer-docs.css */
1248function toggleContent(obj) {
Scott Maindc63dda2013-08-22 16:03:21 -07001249 var div = $(obj).closest(".toggle-content");
1250 var toggleMe = $(".toggle-content-toggleme:eq(0)",div);
Scott Maine4d8f1b2012-06-21 18:03:05 -07001251 if (div.hasClass("closed")) { // if it's closed, open it
1252 toggleMe.slideDown();
Scott Maindc63dda2013-08-22 16:03:21 -07001253 $(".toggle-content-text:eq(0)", obj).toggle();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001254 div.removeClass("closed").addClass("open");
Scott Maindc63dda2013-08-22 16:03:21 -07001255 $(".toggle-content-img:eq(0)", div).attr("title", "hide").attr("src", toRoot
Scott Maine4d8f1b2012-06-21 18:03:05 -07001256 + "assets/images/triangle-opened.png");
1257 } else { // if it's open, close it
1258 toggleMe.slideUp('fast', function() { // Wait until the animation is done before closing arrow
Scott Maindc63dda2013-08-22 16:03:21 -07001259 $(".toggle-content-text:eq(0)", obj).toggle();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001260 div.removeClass("open").addClass("closed");
Scott Maindc63dda2013-08-22 16:03:21 -07001261 div.find(".toggle-content").removeClass("open").addClass("closed")
1262 .find(".toggle-content-toggleme").hide();
Scott Main3b90aff2013-08-01 18:09:35 -07001263 $(".toggle-content-img", div).attr("title", "show").attr("src", toRoot
Scott Maine4d8f1b2012-06-21 18:03:05 -07001264 + "assets/images/triangle-closed.png");
1265 });
1266 }
1267 return false;
1268}
Scott Mainf5089842012-08-14 16:31:07 -07001269
1270
Scott Maindb3678b2012-10-23 14:13:41 -07001271/* New version of expandable content */
1272function toggleExpandable(link,id) {
1273 if($(id).is(':visible')) {
1274 $(id).slideUp();
1275 $(link).removeClass('expanded');
1276 } else {
1277 $(id).slideDown();
1278 $(link).addClass('expanded');
1279 }
1280}
1281
1282function hideExpandable(ids) {
1283 $(ids).slideUp();
Scott Main55d99832012-11-12 23:03:59 -08001284 $(ids).prev('h4').find('a.expandable').removeClass('expanded');
Scott Maindb3678b2012-10-23 14:13:41 -07001285}
1286
Scott Mainf5089842012-08-14 16:31:07 -07001287
1288
1289
1290
Scott Main3b90aff2013-08-01 18:09:35 -07001291/*
Scott Mainf5089842012-08-14 16:31:07 -07001292 * Slideshow 1.0
1293 * Used on /index.html and /develop/index.html for carousel
1294 *
1295 * Sample usage:
1296 * HTML -
1297 * <div class="slideshow-container">
1298 * <a href="" class="slideshow-prev">Prev</a>
1299 * <a href="" class="slideshow-next">Next</a>
1300 * <ul>
1301 * <li class="item"><img src="images/marquee1.jpg"></li>
1302 * <li class="item"><img src="images/marquee2.jpg"></li>
1303 * <li class="item"><img src="images/marquee3.jpg"></li>
1304 * <li class="item"><img src="images/marquee4.jpg"></li>
1305 * </ul>
1306 * </div>
1307 *
1308 * <script type="text/javascript">
1309 * $('.slideshow-container').dacSlideshow({
1310 * auto: true,
1311 * btnPrev: '.slideshow-prev',
1312 * btnNext: '.slideshow-next'
1313 * });
1314 * </script>
1315 *
1316 * Options:
1317 * btnPrev: optional identifier for previous button
1318 * btnNext: optional identifier for next button
Scott Maineb410352013-01-14 19:03:40 -08001319 * btnPause: optional identifier for pause button
Scott Mainf5089842012-08-14 16:31:07 -07001320 * auto: whether or not to auto-proceed
1321 * speed: animation speed
1322 * autoTime: time between auto-rotation
1323 * easing: easing function for transition
1324 * start: item to select by default
1325 * scroll: direction to scroll in
1326 * pagination: whether or not to include dotted pagination
1327 *
1328 */
1329
1330 (function($) {
1331 $.fn.dacSlideshow = function(o) {
Scott Main3b90aff2013-08-01 18:09:35 -07001332
Scott Mainf5089842012-08-14 16:31:07 -07001333 //Options - see above
1334 o = $.extend({
1335 btnPrev: null,
1336 btnNext: null,
Scott Maineb410352013-01-14 19:03:40 -08001337 btnPause: null,
Scott Mainf5089842012-08-14 16:31:07 -07001338 auto: true,
1339 speed: 500,
1340 autoTime: 12000,
1341 easing: null,
1342 start: 0,
1343 scroll: 1,
1344 pagination: true
1345
1346 }, o || {});
Scott Main3b90aff2013-08-01 18:09:35 -07001347
1348 //Set up a carousel for each
Scott Mainf5089842012-08-14 16:31:07 -07001349 return this.each(function() {
1350
1351 var running = false;
1352 var animCss = o.vertical ? "top" : "left";
1353 var sizeCss = o.vertical ? "height" : "width";
1354 var div = $(this);
1355 var ul = $("ul", div);
1356 var tLi = $("li", ul);
Scott Main3b90aff2013-08-01 18:09:35 -07001357 var tl = tLi.size();
Scott Mainf5089842012-08-14 16:31:07 -07001358 var timer = null;
1359
1360 var li = $("li", ul);
1361 var itemLength = li.size();
1362 var curr = o.start;
1363
1364 li.css({float: o.vertical ? "none" : "left"});
1365 ul.css({margin: "0", padding: "0", position: "relative", "list-style-type": "none", "z-index": "1"});
1366 div.css({position: "relative", "z-index": "2", left: "0px"});
1367
1368 var liSize = o.vertical ? height(li) : width(li);
1369 var ulSize = liSize * itemLength;
1370 var divSize = liSize;
1371
1372 li.css({width: li.width(), height: li.height()});
1373 ul.css(sizeCss, ulSize+"px").css(animCss, -(curr*liSize));
1374
1375 div.css(sizeCss, divSize+"px");
Scott Main3b90aff2013-08-01 18:09:35 -07001376
Scott Mainf5089842012-08-14 16:31:07 -07001377 //Pagination
1378 if (o.pagination) {
1379 var pagination = $("<div class='pagination'></div>");
1380 var pag_ul = $("<ul></ul>");
1381 if (tl > 1) {
1382 for (var i=0;i<tl;i++) {
1383 var li = $("<li>"+i+"</li>");
1384 pag_ul.append(li);
1385 if (i==o.start) li.addClass('active');
1386 li.click(function() {
1387 go(parseInt($(this).text()));
1388 })
1389 }
1390 pagination.append(pag_ul);
1391 div.append(pagination);
1392 }
1393 }
Scott Main3b90aff2013-08-01 18:09:35 -07001394
Scott Mainf5089842012-08-14 16:31:07 -07001395 //Previous button
1396 if(o.btnPrev)
1397 $(o.btnPrev).click(function(e) {
1398 e.preventDefault();
1399 return go(curr-o.scroll);
1400 });
1401
1402 //Next button
1403 if(o.btnNext)
1404 $(o.btnNext).click(function(e) {
1405 e.preventDefault();
1406 return go(curr+o.scroll);
1407 });
Scott Maineb410352013-01-14 19:03:40 -08001408
1409 //Pause button
1410 if(o.btnPause)
1411 $(o.btnPause).click(function(e) {
1412 e.preventDefault();
1413 if ($(this).hasClass('paused')) {
1414 startRotateTimer();
1415 } else {
1416 pauseRotateTimer();
1417 }
1418 });
Scott Main3b90aff2013-08-01 18:09:35 -07001419
Scott Mainf5089842012-08-14 16:31:07 -07001420 //Auto rotation
1421 if(o.auto) startRotateTimer();
Scott Main3b90aff2013-08-01 18:09:35 -07001422
Scott Mainf5089842012-08-14 16:31:07 -07001423 function startRotateTimer() {
1424 clearInterval(timer);
1425 timer = setInterval(function() {
1426 if (curr == tl-1) {
1427 go(0);
1428 } else {
Scott Main3b90aff2013-08-01 18:09:35 -07001429 go(curr+o.scroll);
1430 }
Scott Mainf5089842012-08-14 16:31:07 -07001431 }, o.autoTime);
Scott Maineb410352013-01-14 19:03:40 -08001432 $(o.btnPause).removeClass('paused');
1433 }
1434
1435 function pauseRotateTimer() {
1436 clearInterval(timer);
1437 $(o.btnPause).addClass('paused');
Scott Mainf5089842012-08-14 16:31:07 -07001438 }
1439
1440 //Go to an item
1441 function go(to) {
1442 if(!running) {
1443
1444 if(to<0) {
1445 to = itemLength-1;
1446 } else if (to>itemLength-1) {
1447 to = 0;
1448 }
1449 curr = to;
1450
1451 running = true;
1452
1453 ul.animate(
1454 animCss == "left" ? { left: -(curr*liSize) } : { top: -(curr*liSize) } , o.speed, o.easing,
1455 function() {
1456 running = false;
1457 }
1458 );
1459
1460 $(o.btnPrev + "," + o.btnNext).removeClass("disabled");
1461 $( (curr-o.scroll<0 && o.btnPrev)
1462 ||
1463 (curr+o.scroll > itemLength && o.btnNext)
1464 ||
1465 []
1466 ).addClass("disabled");
1467
Scott Main3b90aff2013-08-01 18:09:35 -07001468
Scott Mainf5089842012-08-14 16:31:07 -07001469 var nav_items = $('li', pagination);
1470 nav_items.removeClass('active');
1471 nav_items.eq(to).addClass('active');
Scott Main3b90aff2013-08-01 18:09:35 -07001472
Scott Mainf5089842012-08-14 16:31:07 -07001473
1474 }
1475 if(o.auto) startRotateTimer();
1476 return false;
1477 };
1478 });
1479 };
1480
1481 function css(el, prop) {
1482 return parseInt($.css(el[0], prop)) || 0;
1483 };
1484 function width(el) {
1485 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1486 };
1487 function height(el) {
1488 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1489 };
1490
1491 })(jQuery);
1492
1493
Scott Main3b90aff2013-08-01 18:09:35 -07001494/*
Scott Mainf5089842012-08-14 16:31:07 -07001495 * dacSlideshow 1.0
1496 * Used on develop/index.html for side-sliding tabs
1497 *
1498 * Sample usage:
1499 * HTML -
1500 * <div class="slideshow-container">
1501 * <a href="" class="slideshow-prev">Prev</a>
1502 * <a href="" class="slideshow-next">Next</a>
1503 * <ul>
1504 * <li class="item"><img src="images/marquee1.jpg"></li>
1505 * <li class="item"><img src="images/marquee2.jpg"></li>
1506 * <li class="item"><img src="images/marquee3.jpg"></li>
1507 * <li class="item"><img src="images/marquee4.jpg"></li>
1508 * </ul>
1509 * </div>
1510 *
1511 * <script type="text/javascript">
1512 * $('.slideshow-container').dacSlideshow({
1513 * auto: true,
1514 * btnPrev: '.slideshow-prev',
1515 * btnNext: '.slideshow-next'
1516 * });
1517 * </script>
1518 *
1519 * Options:
1520 * btnPrev: optional identifier for previous button
1521 * btnNext: optional identifier for next button
1522 * auto: whether or not to auto-proceed
1523 * speed: animation speed
1524 * autoTime: time between auto-rotation
1525 * easing: easing function for transition
1526 * start: item to select by default
1527 * scroll: direction to scroll in
1528 * pagination: whether or not to include dotted pagination
1529 *
1530 */
1531 (function($) {
1532 $.fn.dacTabbedList = function(o) {
Scott Main3b90aff2013-08-01 18:09:35 -07001533
Scott Mainf5089842012-08-14 16:31:07 -07001534 //Options - see above
1535 o = $.extend({
1536 speed : 250,
1537 easing: null,
1538 nav_id: null,
1539 frame_id: null
1540 }, o || {});
Scott Main3b90aff2013-08-01 18:09:35 -07001541
1542 //Set up a carousel for each
Scott Mainf5089842012-08-14 16:31:07 -07001543 return this.each(function() {
1544
1545 var curr = 0;
1546 var running = false;
1547 var animCss = "margin-left";
1548 var sizeCss = "width";
1549 var div = $(this);
Scott Main3b90aff2013-08-01 18:09:35 -07001550
Scott Mainf5089842012-08-14 16:31:07 -07001551 var nav = $(o.nav_id, div);
1552 var nav_li = $("li", nav);
Scott Main3b90aff2013-08-01 18:09:35 -07001553 var nav_size = nav_li.size();
Scott Mainf5089842012-08-14 16:31:07 -07001554 var frame = div.find(o.frame_id);
1555 var content_width = $(frame).find('ul').width();
1556 //Buttons
1557 $(nav_li).click(function(e) {
1558 go($(nav_li).index($(this)));
1559 })
Scott Main3b90aff2013-08-01 18:09:35 -07001560
Scott Mainf5089842012-08-14 16:31:07 -07001561 //Go to an item
1562 function go(to) {
1563 if(!running) {
1564 curr = to;
1565 running = true;
1566
1567 frame.animate({ 'margin-left' : -(curr*content_width) }, o.speed, o.easing,
1568 function() {
1569 running = false;
1570 }
1571 );
1572
Scott Main3b90aff2013-08-01 18:09:35 -07001573
Scott Mainf5089842012-08-14 16:31:07 -07001574 nav_li.removeClass('active');
1575 nav_li.eq(to).addClass('active');
Scott Main3b90aff2013-08-01 18:09:35 -07001576
Scott Mainf5089842012-08-14 16:31:07 -07001577
1578 }
1579 return false;
1580 };
1581 });
1582 };
1583
1584 function css(el, prop) {
1585 return parseInt($.css(el[0], prop)) || 0;
1586 };
1587 function width(el) {
1588 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1589 };
1590 function height(el) {
1591 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1592 };
1593
1594 })(jQuery);
1595
1596
1597
1598
1599
1600/* ######################################################## */
1601/* ################ SEARCH SUGGESTIONS ################## */
1602/* ######################################################## */
1603
1604
Scott Main7e447ed2013-02-19 17:22:37 -08001605
Scott Main0e76e7e2013-03-12 10:24:07 -07001606var gSelectedIndex = -1; // the index position of currently highlighted suggestion
1607var gSelectedColumn = -1; // which column of suggestion lists is currently focused
1608
Scott Mainf5089842012-08-14 16:31:07 -07001609var gMatches = new Array();
1610var gLastText = "";
Scott Mainf5089842012-08-14 16:31:07 -07001611var gInitialized = false;
Scott Main7e447ed2013-02-19 17:22:37 -08001612var ROW_COUNT_FRAMEWORK = 20; // max number of results in list
1613var gListLength = 0;
1614
1615
1616var gGoogleMatches = new Array();
1617var ROW_COUNT_GOOGLE = 15; // max number of results in list
1618var gGoogleListLength = 0;
Scott Mainf5089842012-08-14 16:31:07 -07001619
Scott Main0e76e7e2013-03-12 10:24:07 -07001620var gDocsMatches = new Array();
1621var ROW_COUNT_DOCS = 100; // max number of results in list
1622var gDocsListLength = 0;
1623
Scott Mainde295272013-03-25 15:48:35 -07001624function onSuggestionClick(link) {
1625 // When user clicks a suggested document, track it
1626 _gaq.push(['_trackEvent', 'Suggestion Click', 'clicked: ' + $(link).text(),
1627 'from: ' + $("#search_autocomplete").val()]);
1628}
1629
Scott Mainf5089842012-08-14 16:31:07 -07001630function set_item_selected($li, selected)
1631{
1632 if (selected) {
1633 $li.attr('class','jd-autocomplete jd-selected');
1634 } else {
1635 $li.attr('class','jd-autocomplete');
1636 }
1637}
1638
1639function set_item_values(toroot, $li, match)
1640{
1641 var $link = $('a',$li);
1642 $link.html(match.__hilabel || match.label);
1643 $link.attr('href',toroot + match.link);
1644}
1645
Scott Main719acb42013-12-05 16:05:09 -08001646function set_item_values_jd(toroot, $li, match)
1647{
1648 var $link = $('a',$li);
1649 $link.html(match.title);
1650 $link.attr('href',toroot + match.url);
1651}
1652
Scott Main0e76e7e2013-03-12 10:24:07 -07001653function new_suggestion($list) {
Scott Main7e447ed2013-02-19 17:22:37 -08001654 var $li = $("<li class='jd-autocomplete'></li>");
1655 $list.append($li);
1656
1657 $li.mousedown(function() {
1658 window.location = this.firstChild.getAttribute("href");
1659 });
1660 $li.mouseover(function() {
Scott Main0e76e7e2013-03-12 10:24:07 -07001661 $('.search_filtered_wrapper li').removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001662 $(this).addClass('jd-selected');
Scott Main0e76e7e2013-03-12 10:24:07 -07001663 gSelectedColumn = $(".search_filtered:visible").index($(this).closest('.search_filtered'));
1664 gSelectedIndex = $("li", $(".search_filtered:visible")[gSelectedColumn]).index(this);
Scott Main7e447ed2013-02-19 17:22:37 -08001665 });
Scott Mainde295272013-03-25 15:48:35 -07001666 $li.append("<a onclick='onSuggestionClick(this)'></a>");
Scott Main7e447ed2013-02-19 17:22:37 -08001667 $li.attr('class','show-item');
1668 return $li;
1669}
1670
Scott Mainf5089842012-08-14 16:31:07 -07001671function sync_selection_table(toroot)
1672{
Scott Mainf5089842012-08-14 16:31:07 -07001673 var $li; //list item jquery object
1674 var i; //list item iterator
Scott Main7e447ed2013-02-19 17:22:37 -08001675
Scott Main0e76e7e2013-03-12 10:24:07 -07001676 // if there are NO results at all, hide all columns
1677 if (!(gMatches.length > 0) && !(gGoogleMatches.length > 0) && !(gDocsMatches.length > 0)) {
1678 $('.suggest-card').hide(300);
1679 return;
1680 }
1681
1682 // if there are api results
Scott Main7e447ed2013-02-19 17:22:37 -08001683 if ((gMatches.length > 0) || (gGoogleMatches.length > 0)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001684 // reveal suggestion list
1685 $('.suggest-card.dummy').show();
1686 $('.suggest-card.reference').show();
1687 var listIndex = 0; // list index position
Scott Main7e447ed2013-02-19 17:22:37 -08001688
Scott Main0e76e7e2013-03-12 10:24:07 -07001689 // reset the lists
1690 $(".search_filtered_wrapper.reference li").remove();
Scott Main7e447ed2013-02-19 17:22:37 -08001691
Scott Main0e76e7e2013-03-12 10:24:07 -07001692 // ########### ANDROID RESULTS #############
1693 if (gMatches.length > 0) {
Scott Main7e447ed2013-02-19 17:22:37 -08001694
Scott Main0e76e7e2013-03-12 10:24:07 -07001695 // determine android results to show
1696 gListLength = gMatches.length < ROW_COUNT_FRAMEWORK ?
1697 gMatches.length : ROW_COUNT_FRAMEWORK;
1698 for (i=0; i<gListLength; i++) {
1699 var $li = new_suggestion($(".suggest-card.reference ul"));
1700 set_item_values(toroot, $li, gMatches[i]);
1701 set_item_selected($li, i == gSelectedIndex);
1702 }
1703 }
Scott Main7e447ed2013-02-19 17:22:37 -08001704
Scott Main0e76e7e2013-03-12 10:24:07 -07001705 // ########### GOOGLE RESULTS #############
1706 if (gGoogleMatches.length > 0) {
1707 // show header for list
1708 $(".suggest-card.reference ul").append("<li class='header'>in Google Services:</li>");
Scott Main7e447ed2013-02-19 17:22:37 -08001709
Scott Main0e76e7e2013-03-12 10:24:07 -07001710 // determine google results to show
1711 gGoogleListLength = gGoogleMatches.length < ROW_COUNT_GOOGLE ? gGoogleMatches.length : ROW_COUNT_GOOGLE;
1712 for (i=0; i<gGoogleListLength; i++) {
1713 var $li = new_suggestion($(".suggest-card.reference ul"));
1714 set_item_values(toroot, $li, gGoogleMatches[i]);
1715 set_item_selected($li, i == gSelectedIndex);
1716 }
1717 }
Scott Mainf5089842012-08-14 16:31:07 -07001718 } else {
Scott Main0e76e7e2013-03-12 10:24:07 -07001719 $('.suggest-card.reference').hide();
1720 $('.suggest-card.dummy').hide();
1721 }
1722
1723 // ########### JD DOC RESULTS #############
1724 if (gDocsMatches.length > 0) {
1725 // reset the lists
1726 $(".search_filtered_wrapper.docs li").remove();
1727
1728 // determine google results to show
Scott Main719acb42013-12-05 16:05:09 -08001729 // NOTE: The order of the conditions below for the sugg.type MUST BE SPECIFIC:
1730 // The order must match the reverse order that each section appears as a card in
1731 // the suggestion UI... this may be only for the "develop" grouped items though.
Scott Main0e76e7e2013-03-12 10:24:07 -07001732 gDocsListLength = gDocsMatches.length < ROW_COUNT_DOCS ? gDocsMatches.length : ROW_COUNT_DOCS;
1733 for (i=0; i<gDocsListLength; i++) {
1734 var sugg = gDocsMatches[i];
1735 var $li;
1736 if (sugg.type == "design") {
1737 $li = new_suggestion($(".suggest-card.design ul"));
1738 } else
1739 if (sugg.type == "distribute") {
1740 $li = new_suggestion($(".suggest-card.distribute ul"));
1741 } else
Scott Main719acb42013-12-05 16:05:09 -08001742 if (sugg.type == "samples") {
1743 $li = new_suggestion($(".suggest-card.develop .child-card.samples"));
1744 } else
Scott Main0e76e7e2013-03-12 10:24:07 -07001745 if (sugg.type == "training") {
1746 $li = new_suggestion($(".suggest-card.develop .child-card.training"));
1747 } else
Scott Main719acb42013-12-05 16:05:09 -08001748 if (sugg.type == "about"||"guide"||"tools"||"google") {
Scott Main0e76e7e2013-03-12 10:24:07 -07001749 $li = new_suggestion($(".suggest-card.develop .child-card.guides"));
1750 } else {
1751 continue;
1752 }
1753
Scott Main719acb42013-12-05 16:05:09 -08001754 set_item_values_jd(toroot, $li, sugg);
Scott Main0e76e7e2013-03-12 10:24:07 -07001755 set_item_selected($li, i == gSelectedIndex);
1756 }
1757
1758 // add heading and show or hide card
1759 if ($(".suggest-card.design li").length > 0) {
1760 $(".suggest-card.design ul").prepend("<li class='header'>Design:</li>");
1761 $(".suggest-card.design").show(300);
1762 } else {
1763 $('.suggest-card.design').hide(300);
1764 }
1765 if ($(".suggest-card.distribute li").length > 0) {
1766 $(".suggest-card.distribute ul").prepend("<li class='header'>Distribute:</li>");
1767 $(".suggest-card.distribute").show(300);
1768 } else {
1769 $('.suggest-card.distribute').hide(300);
1770 }
1771 if ($(".child-card.guides li").length > 0) {
1772 $(".child-card.guides").prepend("<li class='header'>Guides:</li>");
1773 $(".child-card.guides li").appendTo(".suggest-card.develop ul");
1774 }
1775 if ($(".child-card.training li").length > 0) {
1776 $(".child-card.training").prepend("<li class='header'>Training:</li>");
1777 $(".child-card.training li").appendTo(".suggest-card.develop ul");
1778 }
Scott Main719acb42013-12-05 16:05:09 -08001779 if ($(".child-card.samples li").length > 0) {
1780 $(".child-card.samples").prepend("<li class='header'>Samples:</li>");
1781 $(".child-card.samples li").appendTo(".suggest-card.develop ul");
1782 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001783
1784 if ($(".suggest-card.develop li").length > 0) {
1785 $(".suggest-card.develop").show(300);
1786 } else {
1787 $('.suggest-card.develop').hide(300);
1788 }
1789
1790 } else {
1791 $('.search_filtered_wrapper.docs .suggest-card:not(.dummy)').hide(300);
Scott Mainf5089842012-08-14 16:31:07 -07001792 }
1793}
1794
Scott Main0e76e7e2013-03-12 10:24:07 -07001795/** Called by the search input's onkeydown and onkeyup events.
1796 * Handles navigation with keyboard arrows, Enter key to invoke search,
1797 * otherwise invokes search suggestions on key-up event.
1798 * @param e The JS event
1799 * @param kd True if the event is key-down
Scott Main3b90aff2013-08-01 18:09:35 -07001800 * @param toroot A string for the site's root path
Scott Main0e76e7e2013-03-12 10:24:07 -07001801 * @returns True if the event should bubble up
1802 */
Scott Mainf5089842012-08-14 16:31:07 -07001803function search_changed(e, kd, toroot)
1804{
Scott Main719acb42013-12-05 16:05:09 -08001805 var currentLang = getLangPref();
Scott Mainf5089842012-08-14 16:31:07 -07001806 var search = document.getElementById("search_autocomplete");
1807 var text = search.value.replace(/(^ +)|( +$)/g, '');
Scott Main0e76e7e2013-03-12 10:24:07 -07001808 // get the ul hosting the currently selected item
1809 gSelectedColumn = gSelectedColumn >= 0 ? gSelectedColumn : 0;
1810 var $columns = $(".search_filtered_wrapper").find(".search_filtered:visible");
1811 var $selectedUl = $columns[gSelectedColumn];
1812
Scott Mainf5089842012-08-14 16:31:07 -07001813 // show/hide the close button
1814 if (text != '') {
1815 $(".search .close").removeClass("hide");
1816 } else {
1817 $(".search .close").addClass("hide");
1818 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001819 // 27 = esc
1820 if (e.keyCode == 27) {
1821 // close all search results
1822 if (kd) $('.search .close').trigger('click');
1823 return true;
1824 }
Scott Mainf5089842012-08-14 16:31:07 -07001825 // 13 = enter
Scott Main0e76e7e2013-03-12 10:24:07 -07001826 else if (e.keyCode == 13) {
1827 if (gSelectedIndex < 0) {
1828 $('.suggest-card').hide();
Scott Main7e447ed2013-02-19 17:22:37 -08001829 if ($("#searchResults").is(":hidden") && (search.value != "")) {
1830 // if results aren't showing (and text not empty), return true to allow search to execute
Dirk Doughertyc3921652014-05-13 16:55:26 -07001831 $('body,html').animate({scrollTop:0}, '500', 'swing');
Scott Mainf5089842012-08-14 16:31:07 -07001832 return true;
1833 } else {
1834 // otherwise, results are already showing, so allow ajax to auto refresh the results
1835 // and ignore this Enter press to avoid the reload.
1836 return false;
1837 }
1838 } else if (kd && gSelectedIndex >= 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001839 // click the link corresponding to selected item
1840 $("a",$("li",$selectedUl)[gSelectedIndex]).get()[0].click();
Scott Mainf5089842012-08-14 16:31:07 -07001841 return false;
1842 }
1843 }
Scott Mainb16376f2014-05-21 20:35:47 -07001844 // If Google results are showing, return true to allow ajax search to execute
Scott Main0e76e7e2013-03-12 10:24:07 -07001845 else if ($("#searchResults").is(":visible")) {
Scott Mainb16376f2014-05-21 20:35:47 -07001846 // Also, if search_results is scrolled out of view, scroll to top to make results visible
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001847 if ((sticky ) && (search.value != "")) {
1848 $('body,html').animate({scrollTop:0}, '500', 'swing');
1849 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001850 return true;
1851 }
1852 // 38 UP ARROW
Scott Mainf5089842012-08-14 16:31:07 -07001853 else if (kd && (e.keyCode == 38)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001854 // if the next item is a header, skip it
1855 if ($($("li", $selectedUl)[gSelectedIndex-1]).hasClass("header")) {
Scott Mainf5089842012-08-14 16:31:07 -07001856 gSelectedIndex--;
Scott Main7e447ed2013-02-19 17:22:37 -08001857 }
1858 if (gSelectedIndex >= 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001859 $('li', $selectedUl).removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001860 gSelectedIndex--;
Scott Main0e76e7e2013-03-12 10:24:07 -07001861 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
1862 // If user reaches top, reset selected column
1863 if (gSelectedIndex < 0) {
1864 gSelectedColumn = -1;
1865 }
Scott Mainf5089842012-08-14 16:31:07 -07001866 }
1867 return false;
1868 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001869 // 40 DOWN ARROW
Scott Mainf5089842012-08-14 16:31:07 -07001870 else if (kd && (e.keyCode == 40)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001871 // if the next item is a header, skip it
1872 if ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header")) {
Scott Mainf5089842012-08-14 16:31:07 -07001873 gSelectedIndex++;
Scott Main7e447ed2013-02-19 17:22:37 -08001874 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001875 if ((gSelectedIndex < $("li", $selectedUl).length-1) ||
1876 ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header"))) {
1877 $('li', $selectedUl).removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001878 gSelectedIndex++;
Scott Main0e76e7e2013-03-12 10:24:07 -07001879 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
Scott Mainf5089842012-08-14 16:31:07 -07001880 }
1881 return false;
1882 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001883 // Consider left/right arrow navigation
1884 // NOTE: Order of suggest columns are reverse order (index position 0 is on right)
1885 else if (kd && $columns.length > 1 && gSelectedColumn >= 0) {
1886 // 37 LEFT ARROW
1887 // go left only if current column is not left-most column (last column)
1888 if (e.keyCode == 37 && gSelectedColumn < $columns.length - 1) {
1889 $('li', $selectedUl).removeClass('jd-selected');
1890 gSelectedColumn++;
1891 $selectedUl = $columns[gSelectedColumn];
1892 // keep or reset the selected item to last item as appropriate
1893 gSelectedIndex = gSelectedIndex >
1894 $("li", $selectedUl).length-1 ?
1895 $("li", $selectedUl).length-1 : gSelectedIndex;
1896 // if the corresponding item is a header, move down
1897 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
1898 gSelectedIndex++;
1899 }
Scott Main3b90aff2013-08-01 18:09:35 -07001900 // set item selected
Scott Main0e76e7e2013-03-12 10:24:07 -07001901 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
1902 return false;
1903 }
1904 // 39 RIGHT ARROW
1905 // go right only if current column is not the right-most column (first column)
1906 else if (e.keyCode == 39 && gSelectedColumn > 0) {
1907 $('li', $selectedUl).removeClass('jd-selected');
1908 gSelectedColumn--;
1909 $selectedUl = $columns[gSelectedColumn];
1910 // keep or reset the selected item to last item as appropriate
1911 gSelectedIndex = gSelectedIndex >
1912 $("li", $selectedUl).length-1 ?
1913 $("li", $selectedUl).length-1 : gSelectedIndex;
1914 // if the corresponding item is a header, move down
1915 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
1916 gSelectedIndex++;
1917 }
Scott Main3b90aff2013-08-01 18:09:35 -07001918 // set item selected
Scott Main0e76e7e2013-03-12 10:24:07 -07001919 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
1920 return false;
1921 }
1922 }
1923
Scott Main719acb42013-12-05 16:05:09 -08001924 // if key-up event and not arrow down/up/left/right,
1925 // read the search query and add suggestions to gMatches
Scott Main0e76e7e2013-03-12 10:24:07 -07001926 else if (!kd && (e.keyCode != 40)
1927 && (e.keyCode != 38)
1928 && (e.keyCode != 37)
1929 && (e.keyCode != 39)) {
1930 gSelectedIndex = -1;
Scott Mainf5089842012-08-14 16:31:07 -07001931 gMatches = new Array();
1932 matchedCount = 0;
Scott Main7e447ed2013-02-19 17:22:37 -08001933 gGoogleMatches = new Array();
1934 matchedCountGoogle = 0;
Scott Main0e76e7e2013-03-12 10:24:07 -07001935 gDocsMatches = new Array();
1936 matchedCountDocs = 0;
Scott Main7e447ed2013-02-19 17:22:37 -08001937
1938 // Search for Android matches
Scott Mainf5089842012-08-14 16:31:07 -07001939 for (var i=0; i<DATA.length; i++) {
1940 var s = DATA[i];
1941 if (text.length != 0 &&
1942 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
1943 gMatches[matchedCount] = s;
1944 matchedCount++;
1945 }
1946 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001947 rank_autocomplete_api_results(text, gMatches);
Scott Mainf5089842012-08-14 16:31:07 -07001948 for (var i=0; i<gMatches.length; i++) {
1949 var s = gMatches[i];
Scott Main7e447ed2013-02-19 17:22:37 -08001950 }
1951
1952
1953 // Search for Google matches
1954 for (var i=0; i<GOOGLE_DATA.length; i++) {
1955 var s = GOOGLE_DATA[i];
1956 if (text.length != 0 &&
1957 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
1958 gGoogleMatches[matchedCountGoogle] = s;
1959 matchedCountGoogle++;
Scott Mainf5089842012-08-14 16:31:07 -07001960 }
1961 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001962 rank_autocomplete_api_results(text, gGoogleMatches);
Scott Main7e447ed2013-02-19 17:22:37 -08001963 for (var i=0; i<gGoogleMatches.length; i++) {
1964 var s = gGoogleMatches[i];
1965 }
1966
Scott Mainf5089842012-08-14 16:31:07 -07001967 highlight_autocomplete_result_labels(text);
Scott Main0e76e7e2013-03-12 10:24:07 -07001968
1969
1970
Scott Main719acb42013-12-05 16:05:09 -08001971 // Search for matching JD docs
Scott Main0e76e7e2013-03-12 10:24:07 -07001972 if (text.length >= 3) {
Scott Main719acb42013-12-05 16:05:09 -08001973 // Regex to match only the beginning of a word
1974 var textRegex = new RegExp("\\b" + text.toLowerCase(), "g");
1975
1976
1977 // Search for Training classes
1978 for (var i=0; i<TRAINING_RESOURCES.length; i++) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001979 // current search comparison, with counters for tag and title,
1980 // used later to improve ranking
Scott Main719acb42013-12-05 16:05:09 -08001981 var s = TRAINING_RESOURCES[i];
Scott Main0e76e7e2013-03-12 10:24:07 -07001982 s.matched_tag = 0;
1983 s.matched_title = 0;
1984 var matched = false;
1985
1986 // Check if query matches any tags; work backwards toward 1 to assist ranking
Scott Main719acb42013-12-05 16:05:09 -08001987 for (var j = s.keywords.length - 1; j >= 0; j--) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001988 // it matches a tag
Scott Main719acb42013-12-05 16:05:09 -08001989 if (s.keywords[j].toLowerCase().match(textRegex)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001990 matched = true;
1991 s.matched_tag = j + 1; // add 1 to index position
1992 }
1993 }
Scott Main719acb42013-12-05 16:05:09 -08001994 // Don't consider doc title for lessons (only for class landing pages),
1995 // unless the lesson has a tag that already matches
1996 if ((s.lang == currentLang) &&
1997 (!(s.type == "training" && s.url.indexOf("index.html") == -1) || matched)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001998 // it matches the doc title
Scott Main719acb42013-12-05 16:05:09 -08001999 if (s.title.toLowerCase().match(textRegex)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002000 matched = true;
2001 s.matched_title = 1;
2002 }
2003 }
2004 if (matched) {
2005 gDocsMatches[matchedCountDocs] = s;
2006 matchedCountDocs++;
2007 }
2008 }
Scott Main719acb42013-12-05 16:05:09 -08002009
2010
2011 // Search for API Guides
2012 for (var i=0; i<GUIDE_RESOURCES.length; i++) {
2013 // current search comparison, with counters for tag and title,
2014 // used later to improve ranking
2015 var s = GUIDE_RESOURCES[i];
2016 s.matched_tag = 0;
2017 s.matched_title = 0;
2018 var matched = false;
2019
2020 // Check if query matches any tags; work backwards toward 1 to assist ranking
2021 for (var j = s.keywords.length - 1; j >= 0; j--) {
2022 // it matches a tag
2023 if (s.keywords[j].toLowerCase().match(textRegex)) {
2024 matched = true;
2025 s.matched_tag = j + 1; // add 1 to index position
2026 }
2027 }
2028 // Check if query matches the doc title, but only for current language
2029 if (s.lang == currentLang) {
2030 // if query matches the doc title
2031 if (s.title.toLowerCase().match(textRegex)) {
2032 matched = true;
2033 s.matched_title = 1;
2034 }
2035 }
2036 if (matched) {
2037 gDocsMatches[matchedCountDocs] = s;
2038 matchedCountDocs++;
2039 }
2040 }
2041
2042
2043 // Search for Tools Guides
2044 for (var i=0; i<TOOLS_RESOURCES.length; i++) {
2045 // current search comparison, with counters for tag and title,
2046 // used later to improve ranking
2047 var s = TOOLS_RESOURCES[i];
2048 s.matched_tag = 0;
2049 s.matched_title = 0;
2050 var matched = false;
2051
2052 // Check if query matches any tags; work backwards toward 1 to assist ranking
2053 for (var j = s.keywords.length - 1; j >= 0; j--) {
2054 // it matches a tag
2055 if (s.keywords[j].toLowerCase().match(textRegex)) {
2056 matched = true;
2057 s.matched_tag = j + 1; // add 1 to index position
2058 }
2059 }
2060 // Check if query matches the doc title, but only for current language
2061 if (s.lang == currentLang) {
2062 // if query matches the doc title
2063 if (s.title.toLowerCase().match(textRegex)) {
2064 matched = true;
2065 s.matched_title = 1;
2066 }
2067 }
2068 if (matched) {
2069 gDocsMatches[matchedCountDocs] = s;
2070 matchedCountDocs++;
2071 }
2072 }
2073
2074
2075 // Search for About docs
2076 for (var i=0; i<ABOUT_RESOURCES.length; i++) {
2077 // current search comparison, with counters for tag and title,
2078 // used later to improve ranking
2079 var s = ABOUT_RESOURCES[i];
2080 s.matched_tag = 0;
2081 s.matched_title = 0;
2082 var matched = false;
2083
2084 // Check if query matches any tags; work backwards toward 1 to assist ranking
2085 for (var j = s.keywords.length - 1; j >= 0; j--) {
2086 // it matches a tag
2087 if (s.keywords[j].toLowerCase().match(textRegex)) {
2088 matched = true;
2089 s.matched_tag = j + 1; // add 1 to index position
2090 }
2091 }
2092 // Check if query matches the doc title, but only for current language
2093 if (s.lang == currentLang) {
2094 // if query matches the doc title
2095 if (s.title.toLowerCase().match(textRegex)) {
2096 matched = true;
2097 s.matched_title = 1;
2098 }
2099 }
2100 if (matched) {
2101 gDocsMatches[matchedCountDocs] = s;
2102 matchedCountDocs++;
2103 }
2104 }
2105
2106
2107 // Search for Design guides
2108 for (var i=0; i<DESIGN_RESOURCES.length; i++) {
2109 // current search comparison, with counters for tag and title,
2110 // used later to improve ranking
2111 var s = DESIGN_RESOURCES[i];
2112 s.matched_tag = 0;
2113 s.matched_title = 0;
2114 var matched = false;
2115
2116 // Check if query matches any tags; work backwards toward 1 to assist ranking
2117 for (var j = s.keywords.length - 1; j >= 0; j--) {
2118 // it matches a tag
2119 if (s.keywords[j].toLowerCase().match(textRegex)) {
2120 matched = true;
2121 s.matched_tag = j + 1; // add 1 to index position
2122 }
2123 }
2124 // Check if query matches the doc title, but only for current language
2125 if (s.lang == currentLang) {
2126 // if query matches the doc title
2127 if (s.title.toLowerCase().match(textRegex)) {
2128 matched = true;
2129 s.matched_title = 1;
2130 }
2131 }
2132 if (matched) {
2133 gDocsMatches[matchedCountDocs] = s;
2134 matchedCountDocs++;
2135 }
2136 }
2137
2138
2139 // Search for Distribute guides
2140 for (var i=0; i<DISTRIBUTE_RESOURCES.length; i++) {
2141 // current search comparison, with counters for tag and title,
2142 // used later to improve ranking
2143 var s = DISTRIBUTE_RESOURCES[i];
2144 s.matched_tag = 0;
2145 s.matched_title = 0;
2146 var matched = false;
2147
2148 // Check if query matches any tags; work backwards toward 1 to assist ranking
2149 for (var j = s.keywords.length - 1; j >= 0; j--) {
2150 // it matches a tag
2151 if (s.keywords[j].toLowerCase().match(textRegex)) {
2152 matched = true;
2153 s.matched_tag = j + 1; // add 1 to index position
2154 }
2155 }
2156 // Check if query matches the doc title, but only for current language
2157 if (s.lang == currentLang) {
2158 // if query matches the doc title
2159 if (s.title.toLowerCase().match(textRegex)) {
2160 matched = true;
2161 s.matched_title = 1;
2162 }
2163 }
2164 if (matched) {
2165 gDocsMatches[matchedCountDocs] = s;
2166 matchedCountDocs++;
2167 }
2168 }
2169
2170
2171 // Search for Google guides
2172 for (var i=0; i<GOOGLE_RESOURCES.length; i++) {
2173 // current search comparison, with counters for tag and title,
2174 // used later to improve ranking
2175 var s = GOOGLE_RESOURCES[i];
2176 s.matched_tag = 0;
2177 s.matched_title = 0;
2178 var matched = false;
2179
2180 // Check if query matches any tags; work backwards toward 1 to assist ranking
2181 for (var j = s.keywords.length - 1; j >= 0; j--) {
2182 // it matches a tag
2183 if (s.keywords[j].toLowerCase().match(textRegex)) {
2184 matched = true;
2185 s.matched_tag = j + 1; // add 1 to index position
2186 }
2187 }
2188 // Check if query matches the doc title, but only for current language
2189 if (s.lang == currentLang) {
2190 // if query matches the doc title
2191 if (s.title.toLowerCase().match(textRegex)) {
2192 matched = true;
2193 s.matched_title = 1;
2194 }
2195 }
2196 if (matched) {
2197 gDocsMatches[matchedCountDocs] = s;
2198 matchedCountDocs++;
2199 }
2200 }
2201
2202
2203 // Search for Samples
2204 for (var i=0; i<SAMPLES_RESOURCES.length; i++) {
2205 // current search comparison, with counters for tag and title,
2206 // used later to improve ranking
2207 var s = SAMPLES_RESOURCES[i];
2208 s.matched_tag = 0;
2209 s.matched_title = 0;
2210 var matched = false;
2211 // Check if query matches any tags; work backwards toward 1 to assist ranking
2212 for (var j = s.keywords.length - 1; j >= 0; j--) {
2213 // it matches a tag
2214 if (s.keywords[j].toLowerCase().match(textRegex)) {
2215 matched = true;
2216 s.matched_tag = j + 1; // add 1 to index position
2217 }
2218 }
2219 // Check if query matches the doc title, but only for current language
2220 if (s.lang == currentLang) {
2221 // if query matches the doc title.t
2222 if (s.title.toLowerCase().match(textRegex)) {
2223 matched = true;
2224 s.matched_title = 1;
2225 }
2226 }
2227 if (matched) {
2228 gDocsMatches[matchedCountDocs] = s;
2229 matchedCountDocs++;
2230 }
2231 }
2232
2233 // Rank/sort all the matched pages
Scott Main0e76e7e2013-03-12 10:24:07 -07002234 rank_autocomplete_doc_results(text, gDocsMatches);
2235 }
2236
2237 // draw the suggestions
Scott Mainf5089842012-08-14 16:31:07 -07002238 sync_selection_table(toroot);
2239 return true; // allow the event to bubble up to the search api
2240 }
2241}
2242
Scott Main0e76e7e2013-03-12 10:24:07 -07002243/* Order the jd doc result list based on match quality */
2244function rank_autocomplete_doc_results(query, matches) {
2245 query = query || '';
2246 if (!matches || !matches.length)
2247 return;
2248
2249 var _resultScoreFn = function(match) {
2250 var score = 1.0;
2251
2252 // if the query matched a tag
2253 if (match.matched_tag > 0) {
2254 // multiply score by factor relative to position in tags list (max of 3)
2255 score *= 3 / match.matched_tag;
2256
2257 // if it also matched the title
2258 if (match.matched_title > 0) {
2259 score *= 2;
2260 }
2261 } else if (match.matched_title > 0) {
2262 score *= 3;
2263 }
2264
2265 return score;
2266 };
2267
2268 for (var i=0; i<matches.length; i++) {
2269 matches[i].__resultScore = _resultScoreFn(matches[i]);
2270 }
2271
2272 matches.sort(function(a,b){
2273 var n = b.__resultScore - a.__resultScore;
2274 if (n == 0) // lexicographical sort if scores are the same
2275 n = (a.label < b.label) ? -1 : 1;
2276 return n;
2277 });
2278}
2279
Scott Main7e447ed2013-02-19 17:22:37 -08002280/* Order the result list based on match quality */
Scott Main0e76e7e2013-03-12 10:24:07 -07002281function rank_autocomplete_api_results(query, matches) {
Scott Mainf5089842012-08-14 16:31:07 -07002282 query = query || '';
Scott Main7e447ed2013-02-19 17:22:37 -08002283 if (!matches || !matches.length)
Scott Mainf5089842012-08-14 16:31:07 -07002284 return;
2285
2286 // helper function that gets the last occurence index of the given regex
2287 // in the given string, or -1 if not found
2288 var _lastSearch = function(s, re) {
2289 if (s == '')
2290 return -1;
2291 var l = -1;
2292 var tmp;
2293 while ((tmp = s.search(re)) >= 0) {
2294 if (l < 0) l = 0;
2295 l += tmp;
2296 s = s.substr(tmp + 1);
2297 }
2298 return l;
2299 };
2300
2301 // helper function that counts the occurrences of a given character in
2302 // a given string
2303 var _countChar = function(s, c) {
2304 var n = 0;
2305 for (var i=0; i<s.length; i++)
2306 if (s.charAt(i) == c) ++n;
2307 return n;
2308 };
2309
2310 var queryLower = query.toLowerCase();
2311 var queryAlnum = (queryLower.match(/\w+/) || [''])[0];
2312 var partPrefixAlnumRE = new RegExp('\\b' + queryAlnum);
2313 var partExactAlnumRE = new RegExp('\\b' + queryAlnum + '\\b');
2314
2315 var _resultScoreFn = function(result) {
2316 // scores are calculated based on exact and prefix matches,
2317 // and then number of path separators (dots) from the last
2318 // match (i.e. favoring classes and deep package names)
2319 var score = 1.0;
2320 var labelLower = result.label.toLowerCase();
2321 var t;
2322 t = _lastSearch(labelLower, partExactAlnumRE);
2323 if (t >= 0) {
2324 // exact part match
2325 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2326 score *= 200 / (partsAfter + 1);
2327 } else {
2328 t = _lastSearch(labelLower, partPrefixAlnumRE);
2329 if (t >= 0) {
2330 // part prefix match
2331 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2332 score *= 20 / (partsAfter + 1);
2333 }
2334 }
2335
2336 return score;
2337 };
2338
Scott Main7e447ed2013-02-19 17:22:37 -08002339 for (var i=0; i<matches.length; i++) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002340 // if the API is deprecated, default score is 0; otherwise, perform scoring
2341 if (matches[i].deprecated == "true") {
2342 matches[i].__resultScore = 0;
2343 } else {
2344 matches[i].__resultScore = _resultScoreFn(matches[i]);
2345 }
Scott Mainf5089842012-08-14 16:31:07 -07002346 }
2347
Scott Main7e447ed2013-02-19 17:22:37 -08002348 matches.sort(function(a,b){
Scott Mainf5089842012-08-14 16:31:07 -07002349 var n = b.__resultScore - a.__resultScore;
2350 if (n == 0) // lexicographical sort if scores are the same
2351 n = (a.label < b.label) ? -1 : 1;
2352 return n;
2353 });
2354}
2355
Scott Main7e447ed2013-02-19 17:22:37 -08002356/* Add emphasis to part of string that matches query */
Scott Mainf5089842012-08-14 16:31:07 -07002357function highlight_autocomplete_result_labels(query) {
2358 query = query || '';
Scott Main7e447ed2013-02-19 17:22:37 -08002359 if ((!gMatches || !gMatches.length) && (!gGoogleMatches || !gGoogleMatches.length))
Scott Mainf5089842012-08-14 16:31:07 -07002360 return;
2361
2362 var queryLower = query.toLowerCase();
2363 var queryAlnumDot = (queryLower.match(/[\w\.]+/) || [''])[0];
2364 var queryRE = new RegExp(
2365 '(' + queryAlnumDot.replace(/\./g, '\\.') + ')', 'ig');
2366 for (var i=0; i<gMatches.length; i++) {
2367 gMatches[i].__hilabel = gMatches[i].label.replace(
2368 queryRE, '<b>$1</b>');
2369 }
Scott Main7e447ed2013-02-19 17:22:37 -08002370 for (var i=0; i<gGoogleMatches.length; i++) {
2371 gGoogleMatches[i].__hilabel = gGoogleMatches[i].label.replace(
2372 queryRE, '<b>$1</b>');
2373 }
Scott Mainf5089842012-08-14 16:31:07 -07002374}
2375
2376function search_focus_changed(obj, focused)
2377{
Scott Main3b90aff2013-08-01 18:09:35 -07002378 if (!focused) {
Scott Mainf5089842012-08-14 16:31:07 -07002379 if(obj.value == ""){
2380 $(".search .close").addClass("hide");
2381 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002382 $(".suggest-card").hide();
Scott Mainf5089842012-08-14 16:31:07 -07002383 }
2384}
2385
2386function submit_search() {
2387 var query = document.getElementById('search_autocomplete').value;
2388 location.hash = 'q=' + query;
2389 loadSearchResults();
Dirk Doughertyc3921652014-05-13 16:55:26 -07002390 $("#searchResults").slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002391 return false;
2392}
2393
2394
2395function hideResults() {
Dirk Doughertyc3921652014-05-13 16:55:26 -07002396 $("#searchResults").slideUp('fast', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002397 $(".search .close").addClass("hide");
2398 location.hash = '';
Scott Main3b90aff2013-08-01 18:09:35 -07002399
Scott Mainf5089842012-08-14 16:31:07 -07002400 $("#search_autocomplete").val("").blur();
Scott Main3b90aff2013-08-01 18:09:35 -07002401
Scott Mainf5089842012-08-14 16:31:07 -07002402 // reset the ajax search callback to nothing, so results don't appear unless ENTER
2403 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {});
Scott Main0e76e7e2013-03-12 10:24:07 -07002404
2405 // forcefully regain key-up event control (previously jacked by search api)
2406 $("#search_autocomplete").keyup(function(event) {
2407 return search_changed(event, false, toRoot);
2408 });
2409
Scott Mainf5089842012-08-14 16:31:07 -07002410 return false;
2411}
2412
2413
2414
2415/* ########################################################## */
2416/* ################ CUSTOM SEARCH ENGINE ################## */
2417/* ########################################################## */
2418
Scott Mainf5089842012-08-14 16:31:07 -07002419var searchControl;
Scott Main0e76e7e2013-03-12 10:24:07 -07002420google.load('search', '1', {"callback" : function() {
2421 searchControl = new google.search.SearchControl();
2422 } });
Scott Mainf5089842012-08-14 16:31:07 -07002423
2424function loadSearchResults() {
2425 document.getElementById("search_autocomplete").style.color = "#000";
2426
Scott Mainf5089842012-08-14 16:31:07 -07002427 searchControl = new google.search.SearchControl();
2428
2429 // use our existing search form and use tabs when multiple searchers are used
2430 drawOptions = new google.search.DrawOptions();
2431 drawOptions.setDrawMode(google.search.SearchControl.DRAW_MODE_TABBED);
2432 drawOptions.setInput(document.getElementById("search_autocomplete"));
2433
2434 // configure search result options
2435 searchOptions = new google.search.SearcherOptions();
2436 searchOptions.setExpandMode(GSearchControl.EXPAND_MODE_OPEN);
2437
2438 // configure each of the searchers, for each tab
2439 devSiteSearcher = new google.search.WebSearch();
2440 devSiteSearcher.setUserDefinedLabel("All");
2441 devSiteSearcher.setSiteRestriction("001482626316274216503:zu90b7s047u");
2442
2443 designSearcher = new google.search.WebSearch();
2444 designSearcher.setUserDefinedLabel("Design");
2445 designSearcher.setSiteRestriction("http://developer.android.com/design/");
2446
2447 trainingSearcher = new google.search.WebSearch();
2448 trainingSearcher.setUserDefinedLabel("Training");
2449 trainingSearcher.setSiteRestriction("http://developer.android.com/training/");
2450
2451 guidesSearcher = new google.search.WebSearch();
2452 guidesSearcher.setUserDefinedLabel("Guides");
2453 guidesSearcher.setSiteRestriction("http://developer.android.com/guide/");
2454
2455 referenceSearcher = new google.search.WebSearch();
2456 referenceSearcher.setUserDefinedLabel("Reference");
2457 referenceSearcher.setSiteRestriction("http://developer.android.com/reference/");
2458
Scott Maindf08ada2012-12-03 08:54:37 -08002459 googleSearcher = new google.search.WebSearch();
2460 googleSearcher.setUserDefinedLabel("Google Services");
2461 googleSearcher.setSiteRestriction("http://developer.android.com/google/");
2462
Scott Mainf5089842012-08-14 16:31:07 -07002463 blogSearcher = new google.search.WebSearch();
2464 blogSearcher.setUserDefinedLabel("Blog");
2465 blogSearcher.setSiteRestriction("http://android-developers.blogspot.com");
2466
2467 // add each searcher to the search control
2468 searchControl.addSearcher(devSiteSearcher, searchOptions);
2469 searchControl.addSearcher(designSearcher, searchOptions);
2470 searchControl.addSearcher(trainingSearcher, searchOptions);
2471 searchControl.addSearcher(guidesSearcher, searchOptions);
2472 searchControl.addSearcher(referenceSearcher, searchOptions);
Scott Maindf08ada2012-12-03 08:54:37 -08002473 searchControl.addSearcher(googleSearcher, searchOptions);
Scott Mainf5089842012-08-14 16:31:07 -07002474 searchControl.addSearcher(blogSearcher, searchOptions);
2475
2476 // configure result options
2477 searchControl.setResultSetSize(google.search.Search.LARGE_RESULTSET);
2478 searchControl.setLinkTarget(google.search.Search.LINK_TARGET_SELF);
2479 searchControl.setTimeoutInterval(google.search.SearchControl.TIMEOUT_SHORT);
2480 searchControl.setNoResultsString(google.search.SearchControl.NO_RESULTS_DEFAULT_STRING);
2481
2482 // upon ajax search, refresh the url and search title
2483 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {
2484 updateResultTitle(query);
2485 var query = document.getElementById('search_autocomplete').value;
2486 location.hash = 'q=' + query;
2487 });
2488
Scott Mainde295272013-03-25 15:48:35 -07002489 // once search results load, set up click listeners
2490 searchControl.setSearchCompleteCallback(this, function(control, searcher, query) {
2491 addResultClickListeners();
2492 });
2493
Scott Mainf5089842012-08-14 16:31:07 -07002494 // draw the search results box
2495 searchControl.draw(document.getElementById("leftSearchControl"), drawOptions);
2496
2497 // get query and execute the search
2498 searchControl.execute(decodeURI(getQuery(location.hash)));
2499
2500 document.getElementById("search_autocomplete").focus();
2501 addTabListeners();
2502}
2503// End of loadSearchResults
2504
2505
2506google.setOnLoadCallback(function(){
2507 if (location.hash.indexOf("q=") == -1) {
2508 // if there's no query in the url, don't search and make sure results are hidden
2509 $('#searchResults').hide();
2510 return;
2511 } else {
2512 // first time loading search results for this page
Dirk Doughertyc3921652014-05-13 16:55:26 -07002513 $('#searchResults').slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002514 $(".search .close").removeClass("hide");
2515 loadSearchResults();
2516 }
2517}, true);
2518
Scott Mainb16376f2014-05-21 20:35:47 -07002519/* Adjust the scroll position to account for sticky header, only if the hash matches an id */
2520function offsetScrollForSticky() {
2521 var hash = location.hash;
2522 var $matchingElement = $(hash);
2523 // If there's no element with the hash as an ID, then look for an <a name=''> with it.
2524 if ($matchingElement.length < 1) {
2525 $matchingElement = $('a[name="' + hash.substr(1) + '"]');
2526 }
2527 // Sanity check that hash is a real hash and that there's an element with that ID on the page
2528 if ((hash.indexOf("#") == 0) && $matchingElement.length) {
2529 // If the position of the target element is near the top of the page (<20px, where we expect it
2530 // to be because we need to move it down 60px to become in view), then move it down 60px
2531 if (Math.abs($matchingElement.offset().top - $(window).scrollTop()) < 20) {
2532 $(window).scrollTop($(window).scrollTop() - 60);
Scott Mainb16376f2014-05-21 20:35:47 -07002533 }
2534 }
2535}
2536
Scott Mainf5089842012-08-14 16:31:07 -07002537// when an event on the browser history occurs (back, forward, load) requery hash and do search
2538$(window).hashchange( function(){
Dirk Doughertyc3921652014-05-13 16:55:26 -07002539 // If the hash isn't a search query or there's an error in the query,
2540 // then adjust the scroll position to account for sticky header, then exit.
Scott Mainf5089842012-08-14 16:31:07 -07002541 if ((location.hash.indexOf("q=") == -1) || (query == "undefined")) {
2542 // If the results pane is open, close it.
2543 if (!$("#searchResults").is(":hidden")) {
2544 hideResults();
2545 }
Scott Mainb16376f2014-05-21 20:35:47 -07002546 offsetScrollForSticky();
Scott Mainf5089842012-08-14 16:31:07 -07002547 return;
2548 }
2549
2550 // Otherwise, we have a search to do
2551 var query = decodeURI(getQuery(location.hash));
2552 searchControl.execute(query);
Dirk Doughertyc3921652014-05-13 16:55:26 -07002553 $('#searchResults').slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002554 $("#search_autocomplete").focus();
2555 $(".search .close").removeClass("hide");
2556
2557 updateResultTitle(query);
2558});
2559
2560function updateResultTitle(query) {
2561 $("#searchTitle").html("Results for <em>" + escapeHTML(query) + "</em>");
2562}
2563
2564// forcefully regain key-up event control (previously jacked by search api)
2565$("#search_autocomplete").keyup(function(event) {
2566 return search_changed(event, false, toRoot);
2567});
2568
2569// add event listeners to each tab so we can track the browser history
2570function addTabListeners() {
2571 var tabHeaders = $(".gsc-tabHeader");
2572 for (var i = 0; i < tabHeaders.length; i++) {
2573 $(tabHeaders[i]).attr("id",i).click(function() {
2574 /*
2575 // make a copy of the page numbers for the search left pane
2576 setTimeout(function() {
2577 // remove any residual page numbers
2578 $('#searchResults .gsc-tabsArea .gsc-cursor-box.gs-bidi-start-align').remove();
Scott Main3b90aff2013-08-01 18:09:35 -07002579 // move the page numbers to the left position; make a clone,
Scott Mainf5089842012-08-14 16:31:07 -07002580 // because the element is drawn to the DOM only once
Scott Main3b90aff2013-08-01 18:09:35 -07002581 // and because we're going to remove it (previous line),
2582 // we need it to be available to move again as the user navigates
Scott Mainf5089842012-08-14 16:31:07 -07002583 $('#searchResults .gsc-webResult .gsc-cursor-box.gs-bidi-start-align:visible')
2584 .clone().appendTo('#searchResults .gsc-tabsArea');
2585 }, 200);
2586 */
2587 });
2588 }
2589 setTimeout(function(){$(tabHeaders[0]).click()},200);
2590}
2591
Scott Mainde295272013-03-25 15:48:35 -07002592// add analytics tracking events to each result link
2593function addResultClickListeners() {
2594 $("#searchResults a.gs-title").each(function(index, link) {
2595 // When user clicks enter for Google search results, track it
2596 $(link).click(function() {
2597 _gaq.push(['_trackEvent', 'Google Click', 'clicked: ' + $(this).text(),
2598 'from: ' + $("#search_autocomplete").val()]);
2599 });
2600 });
2601}
2602
Scott Mainf5089842012-08-14 16:31:07 -07002603
2604function getQuery(hash) {
2605 var queryParts = hash.split('=');
2606 return queryParts[1];
2607}
2608
2609/* returns the given string with all HTML brackets converted to entities
2610 TODO: move this to the site's JS library */
2611function escapeHTML(string) {
2612 return string.replace(/</g,"&lt;")
2613 .replace(/>/g,"&gt;");
2614}
2615
2616
2617
2618
2619
2620
2621
2622/* ######################################################## */
2623/* ################# JAVADOC REFERENCE ################### */
2624/* ######################################################## */
2625
Scott Main65511c02012-09-07 15:51:32 -07002626/* Initialize some droiddoc stuff, but only if we're in the reference */
Scott Main52dd2062013-08-15 12:22:28 -07002627if (location.pathname.indexOf("/reference") == 0) {
2628 if(!(location.pathname.indexOf("/reference-gms/packages.html") == 0)
2629 && !(location.pathname.indexOf("/reference-gcm/packages.html") == 0)
2630 && !(location.pathname.indexOf("/reference/com/google") == 0)) {
Robert Ly67d75f12012-12-03 12:53:42 -08002631 $(document).ready(function() {
2632 // init available apis based on user pref
2633 changeApiLevel();
2634 initSidenavHeightResize()
2635 });
2636 }
Scott Main65511c02012-09-07 15:51:32 -07002637}
Scott Mainf5089842012-08-14 16:31:07 -07002638
2639var API_LEVEL_COOKIE = "api_level";
2640var minLevel = 1;
2641var maxLevel = 1;
2642
2643/******* SIDENAV DIMENSIONS ************/
Scott Main3b90aff2013-08-01 18:09:35 -07002644
Scott Mainf5089842012-08-14 16:31:07 -07002645 function initSidenavHeightResize() {
2646 // Change the drag bar size to nicely fit the scrollbar positions
2647 var $dragBar = $(".ui-resizable-s");
2648 $dragBar.css({'width': $dragBar.parent().width() - 5 + "px"});
Scott Main3b90aff2013-08-01 18:09:35 -07002649
2650 $( "#resize-packages-nav" ).resizable({
Scott Mainf5089842012-08-14 16:31:07 -07002651 containment: "#nav-panels",
2652 handles: "s",
2653 alsoResize: "#packages-nav",
2654 resize: function(event, ui) { resizeNav(); }, /* resize the nav while dragging */
2655 stop: function(event, ui) { saveNavPanels(); } /* once stopped, save the sizes to cookie */
2656 });
Scott Main3b90aff2013-08-01 18:09:35 -07002657
Scott Mainf5089842012-08-14 16:31:07 -07002658 }
Scott Main3b90aff2013-08-01 18:09:35 -07002659
Scott Mainf5089842012-08-14 16:31:07 -07002660function updateSidenavFixedWidth() {
Scott Mainf5257812014-05-22 17:26:38 -07002661 if (!sticky) return;
Scott Mainf5089842012-08-14 16:31:07 -07002662 $('#devdoc-nav').css({
2663 'width' : $('#side-nav').css('width'),
2664 'margin' : $('#side-nav').css('margin')
2665 });
2666 $('#devdoc-nav a.totop').css({'display':'block','width':$("#nav").innerWidth()+'px'});
Scott Main3b90aff2013-08-01 18:09:35 -07002667
Scott Mainf5089842012-08-14 16:31:07 -07002668 initSidenavHeightResize();
2669}
2670
2671function updateSidenavFullscreenWidth() {
Scott Mainf5257812014-05-22 17:26:38 -07002672 if (!sticky) return;
Scott Mainf5089842012-08-14 16:31:07 -07002673 $('#devdoc-nav').css({
2674 'width' : $('#side-nav').css('width'),
2675 'margin' : $('#side-nav').css('margin')
2676 });
2677 $('#devdoc-nav .totop').css({'left': 'inherit'});
Scott Main3b90aff2013-08-01 18:09:35 -07002678
Scott Mainf5089842012-08-14 16:31:07 -07002679 initSidenavHeightResize();
2680}
2681
2682function buildApiLevelSelector() {
2683 maxLevel = SINCE_DATA.length;
2684 var userApiLevel = parseInt(readCookie(API_LEVEL_COOKIE));
2685 userApiLevel = userApiLevel == 0 ? maxLevel : userApiLevel; // If there's no cookie (zero), use the max by default
2686
2687 minLevel = parseInt($("#doc-api-level").attr("class"));
2688 // Handle provisional api levels; the provisional level will always be the highest possible level
2689 // Provisional api levels will also have a length; other stuff that's just missing a level won't,
2690 // so leave those kinds of entities at the default level of 1 (for example, the R.styleable class)
2691 if (isNaN(minLevel) && minLevel.length) {
2692 minLevel = maxLevel;
2693 }
2694 var select = $("#apiLevelSelector").html("").change(changeApiLevel);
2695 for (var i = maxLevel-1; i >= 0; i--) {
2696 var option = $("<option />").attr("value",""+SINCE_DATA[i]).append(""+SINCE_DATA[i]);
2697 // if (SINCE_DATA[i] < minLevel) option.addClass("absent"); // always false for strings (codenames)
2698 select.append(option);
2699 }
2700
2701 // get the DOM element and use setAttribute cuz IE6 fails when using jquery .attr('selected',true)
2702 var selectedLevelItem = $("#apiLevelSelector option[value='"+userApiLevel+"']").get(0);
2703 selectedLevelItem.setAttribute('selected',true);
2704}
2705
2706function changeApiLevel() {
2707 maxLevel = SINCE_DATA.length;
2708 var selectedLevel = maxLevel;
2709
2710 selectedLevel = parseInt($("#apiLevelSelector option:selected").val());
2711 toggleVisisbleApis(selectedLevel, "body");
2712
2713 var date = new Date();
2714 date.setTime(date.getTime()+(10*365*24*60*60*1000)); // keep this for 10 years
2715 var expiration = date.toGMTString();
2716 writeCookie(API_LEVEL_COOKIE, selectedLevel, null, expiration);
2717
2718 if (selectedLevel < minLevel) {
2719 var thing = ($("#jd-header").html().indexOf("package") != -1) ? "package" : "class";
Scott Main8f24ca82012-11-16 10:34:22 -08002720 $("#naMessage").show().html("<div><p><strong>This " + thing
2721 + " requires API level " + minLevel + " or higher.</strong></p>"
2722 + "<p>This document is hidden because your selected API level for the documentation is "
2723 + selectedLevel + ". You can change the documentation API level with the selector "
2724 + "above the left navigation.</p>"
2725 + "<p>For more information about specifying the API level your app requires, "
2726 + "read <a href='" + toRoot + "training/basics/supporting-devices/platforms.html'"
2727 + ">Supporting Different Platform Versions</a>.</p>"
2728 + "<input type='button' value='OK, make this page visible' "
2729 + "title='Change the API level to " + minLevel + "' "
2730 + "onclick='$(\"#apiLevelSelector\").val(\"" + minLevel + "\");changeApiLevel();' />"
2731 + "</div>");
Scott Mainf5089842012-08-14 16:31:07 -07002732 } else {
2733 $("#naMessage").hide();
2734 }
2735}
2736
2737function toggleVisisbleApis(selectedLevel, context) {
2738 var apis = $(".api",context);
2739 apis.each(function(i) {
2740 var obj = $(this);
2741 var className = obj.attr("class");
2742 var apiLevelIndex = className.lastIndexOf("-")+1;
2743 var apiLevelEndIndex = className.indexOf(" ", apiLevelIndex);
2744 apiLevelEndIndex = apiLevelEndIndex != -1 ? apiLevelEndIndex : className.length;
2745 var apiLevel = className.substring(apiLevelIndex, apiLevelEndIndex);
2746 if (apiLevel.length == 0) { // for odd cases when the since data is actually missing, just bail
2747 return;
2748 }
2749 apiLevel = parseInt(apiLevel);
2750
2751 // Handle provisional api levels; if this item's level is the provisional one, set it to the max
2752 var selectedLevelNum = parseInt(selectedLevel)
2753 var apiLevelNum = parseInt(apiLevel);
2754 if (isNaN(apiLevelNum)) {
2755 apiLevelNum = maxLevel;
2756 }
2757
2758 // Grey things out that aren't available and give a tooltip title
2759 if (apiLevelNum > selectedLevelNum) {
2760 obj.addClass("absent").attr("title","Requires API Level \""
Scott Main641c2c22013-10-31 14:48:45 -07002761 + apiLevel + "\" or higher. To reveal, change the target API level "
2762 + "above the left navigation.");
Scott Main3b90aff2013-08-01 18:09:35 -07002763 }
Scott Mainf5089842012-08-14 16:31:07 -07002764 else obj.removeClass("absent").removeAttr("title");
2765 });
2766}
2767
2768
2769
2770
2771/* ################# SIDENAV TREE VIEW ################### */
2772
2773function new_node(me, mom, text, link, children_data, api_level)
2774{
2775 var node = new Object();
2776 node.children = Array();
2777 node.children_data = children_data;
2778 node.depth = mom.depth + 1;
2779
2780 node.li = document.createElement("li");
2781 mom.get_children_ul().appendChild(node.li);
2782
2783 node.label_div = document.createElement("div");
2784 node.label_div.className = "label";
2785 if (api_level != null) {
2786 $(node.label_div).addClass("api");
2787 $(node.label_div).addClass("api-level-"+api_level);
2788 }
2789 node.li.appendChild(node.label_div);
2790
2791 if (children_data != null) {
2792 node.expand_toggle = document.createElement("a");
2793 node.expand_toggle.href = "javascript:void(0)";
2794 node.expand_toggle.onclick = function() {
2795 if (node.expanded) {
2796 $(node.get_children_ul()).slideUp("fast");
2797 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2798 node.expanded = false;
2799 } else {
2800 expand_node(me, node);
2801 }
2802 };
2803 node.label_div.appendChild(node.expand_toggle);
2804
2805 node.plus_img = document.createElement("img");
2806 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2807 node.plus_img.className = "plus";
2808 node.plus_img.width = "8";
2809 node.plus_img.border = "0";
2810 node.expand_toggle.appendChild(node.plus_img);
2811
2812 node.expanded = false;
2813 }
2814
2815 var a = document.createElement("a");
2816 node.label_div.appendChild(a);
2817 node.label = document.createTextNode(text);
2818 a.appendChild(node.label);
2819 if (link) {
2820 a.href = me.toroot + link;
2821 } else {
2822 if (children_data != null) {
2823 a.className = "nolink";
2824 a.href = "javascript:void(0)";
2825 a.onclick = node.expand_toggle.onclick;
2826 // This next line shouldn't be necessary. I'll buy a beer for the first
2827 // person who figures out how to remove this line and have the link
2828 // toggle shut on the first try. --joeo@android.com
2829 node.expanded = false;
2830 }
2831 }
Scott Main3b90aff2013-08-01 18:09:35 -07002832
Scott Mainf5089842012-08-14 16:31:07 -07002833
2834 node.children_ul = null;
2835 node.get_children_ul = function() {
2836 if (!node.children_ul) {
2837 node.children_ul = document.createElement("ul");
2838 node.children_ul.className = "children_ul";
2839 node.children_ul.style.display = "none";
2840 node.li.appendChild(node.children_ul);
2841 }
2842 return node.children_ul;
2843 };
2844
2845 return node;
2846}
2847
Robert Lyd2dd6e52012-11-29 21:28:48 -08002848
2849
2850
Scott Mainf5089842012-08-14 16:31:07 -07002851function expand_node(me, node)
2852{
2853 if (node.children_data && !node.expanded) {
2854 if (node.children_visited) {
2855 $(node.get_children_ul()).slideDown("fast");
2856 } else {
2857 get_node(me, node);
2858 if ($(node.label_div).hasClass("absent")) {
2859 $(node.get_children_ul()).addClass("absent");
Scott Main3b90aff2013-08-01 18:09:35 -07002860 }
Scott Mainf5089842012-08-14 16:31:07 -07002861 $(node.get_children_ul()).slideDown("fast");
2862 }
2863 node.plus_img.src = me.toroot + "assets/images/triangle-opened-small.png";
2864 node.expanded = true;
2865
2866 // perform api level toggling because new nodes are new to the DOM
2867 var selectedLevel = $("#apiLevelSelector option:selected").val();
2868 toggleVisisbleApis(selectedLevel, "#side-nav");
2869 }
2870}
2871
2872function get_node(me, mom)
2873{
2874 mom.children_visited = true;
2875 for (var i in mom.children_data) {
2876 var node_data = mom.children_data[i];
2877 mom.children[i] = new_node(me, mom, node_data[0], node_data[1],
2878 node_data[2], node_data[3]);
2879 }
2880}
2881
2882function this_page_relative(toroot)
2883{
2884 var full = document.location.pathname;
2885 var file = "";
2886 if (toroot.substr(0, 1) == "/") {
2887 if (full.substr(0, toroot.length) == toroot) {
2888 return full.substr(toroot.length);
2889 } else {
2890 // the file isn't under toroot. Fail.
2891 return null;
2892 }
2893 } else {
2894 if (toroot != "./") {
2895 toroot = "./" + toroot;
2896 }
2897 do {
2898 if (toroot.substr(toroot.length-3, 3) == "../" || toroot == "./") {
2899 var pos = full.lastIndexOf("/");
2900 file = full.substr(pos) + file;
2901 full = full.substr(0, pos);
2902 toroot = toroot.substr(0, toroot.length-3);
2903 }
2904 } while (toroot != "" && toroot != "/");
2905 return file.substr(1);
2906 }
2907}
2908
2909function find_page(url, data)
2910{
2911 var nodes = data;
2912 var result = null;
2913 for (var i in nodes) {
2914 var d = nodes[i];
2915 if (d[1] == url) {
2916 return new Array(i);
2917 }
2918 else if (d[2] != null) {
2919 result = find_page(url, d[2]);
2920 if (result != null) {
2921 return (new Array(i).concat(result));
2922 }
2923 }
2924 }
2925 return null;
2926}
2927
Scott Mainf5089842012-08-14 16:31:07 -07002928function init_default_navtree(toroot) {
Scott Main25e73002013-03-27 15:24:06 -07002929 // load json file for navtree data
2930 $.getScript(toRoot + 'navtree_data.js', function(data, textStatus, jqxhr) {
2931 // when the file is loaded, initialize the tree
2932 if(jqxhr.status === 200) {
2933 init_navtree("tree-list", toroot, NAVTREE_DATA);
2934 }
2935 });
Scott Main3b90aff2013-08-01 18:09:35 -07002936
Scott Mainf5089842012-08-14 16:31:07 -07002937 // perform api level toggling because because the whole tree is new to the DOM
2938 var selectedLevel = $("#apiLevelSelector option:selected").val();
2939 toggleVisisbleApis(selectedLevel, "#side-nav");
2940}
2941
2942function init_navtree(navtree_id, toroot, root_nodes)
2943{
2944 var me = new Object();
2945 me.toroot = toroot;
2946 me.node = new Object();
2947
2948 me.node.li = document.getElementById(navtree_id);
2949 me.node.children_data = root_nodes;
2950 me.node.children = new Array();
2951 me.node.children_ul = document.createElement("ul");
2952 me.node.get_children_ul = function() { return me.node.children_ul; };
2953 //me.node.children_ul.className = "children_ul";
2954 me.node.li.appendChild(me.node.children_ul);
2955 me.node.depth = 0;
2956
2957 get_node(me, me.node);
2958
2959 me.this_page = this_page_relative(toroot);
2960 me.breadcrumbs = find_page(me.this_page, root_nodes);
2961 if (me.breadcrumbs != null && me.breadcrumbs.length != 0) {
2962 var mom = me.node;
2963 for (var i in me.breadcrumbs) {
2964 var j = me.breadcrumbs[i];
2965 mom = mom.children[j];
2966 expand_node(me, mom);
2967 }
2968 mom.label_div.className = mom.label_div.className + " selected";
2969 addLoadEvent(function() {
2970 scrollIntoView("nav-tree");
2971 });
2972 }
2973}
2974
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07002975
2976
2977
2978
2979
2980
2981
Robert Lyd2dd6e52012-11-29 21:28:48 -08002982/* TODO: eliminate redundancy with non-google functions */
2983function init_google_navtree(navtree_id, toroot, root_nodes)
2984{
2985 var me = new Object();
2986 me.toroot = toroot;
2987 me.node = new Object();
2988
2989 me.node.li = document.getElementById(navtree_id);
2990 me.node.children_data = root_nodes;
2991 me.node.children = new Array();
2992 me.node.children_ul = document.createElement("ul");
2993 me.node.get_children_ul = function() { return me.node.children_ul; };
2994 //me.node.children_ul.className = "children_ul";
2995 me.node.li.appendChild(me.node.children_ul);
2996 me.node.depth = 0;
2997
2998 get_google_node(me, me.node);
Robert Lyd2dd6e52012-11-29 21:28:48 -08002999}
3000
3001function new_google_node(me, mom, text, link, children_data, api_level)
3002{
3003 var node = new Object();
3004 var child;
3005 node.children = Array();
3006 node.children_data = children_data;
3007 node.depth = mom.depth + 1;
3008 node.get_children_ul = function() {
3009 if (!node.children_ul) {
Scott Main3b90aff2013-08-01 18:09:35 -07003010 node.children_ul = document.createElement("ul");
3011 node.children_ul.className = "tree-list-children";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003012 node.li.appendChild(node.children_ul);
3013 }
3014 return node.children_ul;
3015 };
3016 node.li = document.createElement("li");
3017
3018 mom.get_children_ul().appendChild(node.li);
Scott Main3b90aff2013-08-01 18:09:35 -07003019
3020
Robert Lyd2dd6e52012-11-29 21:28:48 -08003021 if(link) {
3022 child = document.createElement("a");
3023
3024 }
3025 else {
3026 child = document.createElement("span");
Scott Mainac71b2b2012-11-30 14:40:58 -08003027 child.className = "tree-list-subtitle";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003028
3029 }
3030 if (children_data != null) {
3031 node.li.className="nav-section";
3032 node.label_div = document.createElement("div");
Scott Main3b90aff2013-08-01 18:09:35 -07003033 node.label_div.className = "nav-section-header-ref";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003034 node.li.appendChild(node.label_div);
3035 get_google_node(me, node);
3036 node.label_div.appendChild(child);
3037 }
3038 else {
3039 node.li.appendChild(child);
3040 }
3041 if(link) {
3042 child.href = me.toroot + link;
3043 }
3044 node.label = document.createTextNode(text);
3045 child.appendChild(node.label);
3046
3047 node.children_ul = null;
3048
3049 return node;
3050}
3051
3052function get_google_node(me, mom)
3053{
3054 mom.children_visited = true;
3055 var linkText;
3056 for (var i in mom.children_data) {
3057 var node_data = mom.children_data[i];
3058 linkText = node_data[0];
3059
3060 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3061 linkText = linkText.substr(19, linkText.length);
3062 }
3063 mom.children[i] = new_google_node(me, mom, linkText, node_data[1],
3064 node_data[2], node_data[3]);
3065 }
3066}
Scott Mainad08f072013-08-20 16:49:57 -07003067
3068
3069
3070
3071
3072
3073/****** NEW version of script to build google and sample navs dynamically ******/
3074// TODO: update Google reference docs to tolerate this new implementation
3075
Scott Maine624b3f2013-09-12 12:56:41 -07003076var NODE_NAME = 0;
3077var NODE_HREF = 1;
3078var NODE_GROUP = 2;
3079var NODE_TAGS = 3;
3080var NODE_CHILDREN = 4;
3081
Scott Mainad08f072013-08-20 16:49:57 -07003082function init_google_navtree2(navtree_id, data)
3083{
3084 var $containerUl = $("#"+navtree_id);
Scott Mainad08f072013-08-20 16:49:57 -07003085 for (var i in data) {
3086 var node_data = data[i];
3087 $containerUl.append(new_google_node2(node_data));
3088 }
3089
Scott Main70557ee2013-10-30 14:47:40 -07003090 // Make all third-generation list items 'sticky' to prevent them from collapsing
3091 $containerUl.find('li li li.nav-section').addClass('sticky');
3092
Scott Mainad08f072013-08-20 16:49:57 -07003093 initExpandableNavItems("#"+navtree_id);
3094}
3095
3096function new_google_node2(node_data)
3097{
Scott Maine624b3f2013-09-12 12:56:41 -07003098 var linkText = node_data[NODE_NAME];
Scott Mainad08f072013-08-20 16:49:57 -07003099 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3100 linkText = linkText.substr(19, linkText.length);
3101 }
3102 var $li = $('<li>');
3103 var $a;
Scott Maine624b3f2013-09-12 12:56:41 -07003104 if (node_data[NODE_HREF] != null) {
Scott Main70557ee2013-10-30 14:47:40 -07003105 $a = $('<a href="' + toRoot + node_data[NODE_HREF] + '" title="' + linkText + '" >'
3106 + linkText + '</a>');
Scott Mainad08f072013-08-20 16:49:57 -07003107 } else {
Scott Main70557ee2013-10-30 14:47:40 -07003108 $a = $('<a href="#" onclick="return false;" title="' + linkText + '" >'
3109 + linkText + '/</a>');
Scott Mainad08f072013-08-20 16:49:57 -07003110 }
3111 var $childUl = $('<ul>');
Scott Maine624b3f2013-09-12 12:56:41 -07003112 if (node_data[NODE_CHILDREN] != null) {
Scott Mainad08f072013-08-20 16:49:57 -07003113 $li.addClass("nav-section");
3114 $a = $('<div class="nav-section-header">').append($a);
Scott Maine624b3f2013-09-12 12:56:41 -07003115 if (node_data[NODE_HREF] == null) $a.addClass('empty');
Scott Mainad08f072013-08-20 16:49:57 -07003116
Scott Maine624b3f2013-09-12 12:56:41 -07003117 for (var i in node_data[NODE_CHILDREN]) {
3118 var child_node_data = node_data[NODE_CHILDREN][i];
Scott Mainad08f072013-08-20 16:49:57 -07003119 $childUl.append(new_google_node2(child_node_data));
3120 }
3121 $li.append($childUl);
3122 }
3123 $li.prepend($a);
3124
3125 return $li;
3126}
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
Robert Lyd2dd6e52012-11-29 21:28:48 -08003138function showGoogleRefTree() {
3139 init_default_google_navtree(toRoot);
3140 init_default_gcm_navtree(toRoot);
Robert Lyd2dd6e52012-11-29 21:28:48 -08003141}
3142
3143function init_default_google_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07003144 // load json file for navtree data
3145 $.getScript(toRoot + 'gms_navtree_data.js', function(data, textStatus, jqxhr) {
3146 // when the file is loaded, initialize the tree
3147 if(jqxhr.status === 200) {
3148 init_google_navtree("gms-tree-list", toroot, GMS_NAVTREE_DATA);
3149 highlightSidenav();
3150 resizeNav();
3151 }
3152 });
Robert Lyd2dd6e52012-11-29 21:28:48 -08003153}
3154
3155function init_default_gcm_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07003156 // load json file for navtree data
3157 $.getScript(toRoot + 'gcm_navtree_data.js', function(data, textStatus, jqxhr) {
3158 // when the file is loaded, initialize the tree
3159 if(jqxhr.status === 200) {
3160 init_google_navtree("gcm-tree-list", toroot, GCM_NAVTREE_DATA);
3161 highlightSidenav();
3162 resizeNav();
3163 }
3164 });
Robert Lyd2dd6e52012-11-29 21:28:48 -08003165}
3166
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003167function showSamplesRefTree() {
3168 init_default_samples_navtree(toRoot);
3169}
3170
3171function init_default_samples_navtree(toroot) {
3172 // load json file for navtree data
3173 $.getScript(toRoot + 'samples_navtree_data.js', function(data, textStatus, jqxhr) {
3174 // when the file is loaded, initialize the tree
3175 if(jqxhr.status === 200) {
Scott Mainf1435b72013-10-30 16:27:38 -07003176 // hack to remove the "about the samples" link then put it back in
3177 // after we nuke the list to remove the dummy static list of samples
3178 var $firstLi = $("#nav.samples-nav > li:first-child").clone();
3179 $("#nav.samples-nav").empty();
3180 $("#nav.samples-nav").append($firstLi);
3181
Scott Mainad08f072013-08-20 16:49:57 -07003182 init_google_navtree2("nav.samples-nav", SAMPLES_NAVTREE_DATA);
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003183 highlightSidenav();
3184 resizeNav();
Scott Main03aca9a2013-10-31 07:20:55 -07003185 if ($("#jd-content #samples").length) {
3186 showSamples();
3187 }
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003188 }
3189 });
3190}
3191
Scott Mainf5089842012-08-14 16:31:07 -07003192/* TOGGLE INHERITED MEMBERS */
3193
3194/* Toggle an inherited class (arrow toggle)
3195 * @param linkObj The link that was clicked.
3196 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3197 * 'null' to simply toggle.
3198 */
3199function toggleInherited(linkObj, expand) {
3200 var base = linkObj.getAttribute("id");
3201 var list = document.getElementById(base + "-list");
3202 var summary = document.getElementById(base + "-summary");
3203 var trigger = document.getElementById(base + "-trigger");
3204 var a = $(linkObj);
3205 if ( (expand == null && a.hasClass("closed")) || expand ) {
3206 list.style.display = "none";
3207 summary.style.display = "block";
3208 trigger.src = toRoot + "assets/images/triangle-opened.png";
3209 a.removeClass("closed");
3210 a.addClass("opened");
3211 } else if ( (expand == null && a.hasClass("opened")) || (expand == false) ) {
3212 list.style.display = "block";
3213 summary.style.display = "none";
3214 trigger.src = toRoot + "assets/images/triangle-closed.png";
3215 a.removeClass("opened");
3216 a.addClass("closed");
3217 }
3218 return false;
3219}
3220
3221/* Toggle all inherited classes in a single table (e.g. all inherited methods)
3222 * @param linkObj The link that was clicked.
3223 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3224 * 'null' to simply toggle.
3225 */
3226function toggleAllInherited(linkObj, expand) {
3227 var a = $(linkObj);
3228 var table = $(a.parent().parent().parent()); // ugly way to get table/tbody
3229 var expandos = $(".jd-expando-trigger", table);
3230 if ( (expand == null && a.text() == "[Expand]") || expand ) {
3231 expandos.each(function(i) {
3232 toggleInherited(this, true);
3233 });
3234 a.text("[Collapse]");
3235 } else if ( (expand == null && a.text() == "[Collapse]") || (expand == false) ) {
3236 expandos.each(function(i) {
3237 toggleInherited(this, false);
3238 });
3239 a.text("[Expand]");
3240 }
3241 return false;
3242}
3243
3244/* Toggle all inherited members in the class (link in the class title)
3245 */
3246function toggleAllClassInherited() {
3247 var a = $("#toggleAllClassInherited"); // get toggle link from class title
3248 var toggles = $(".toggle-all", $("#body-content"));
3249 if (a.text() == "[Expand All]") {
3250 toggles.each(function(i) {
3251 toggleAllInherited(this, true);
3252 });
3253 a.text("[Collapse All]");
3254 } else {
3255 toggles.each(function(i) {
3256 toggleAllInherited(this, false);
3257 });
3258 a.text("[Expand All]");
3259 }
3260 return false;
3261}
3262
3263/* Expand all inherited members in the class. Used when initiating page search */
3264function ensureAllInheritedExpanded() {
3265 var toggles = $(".toggle-all", $("#body-content"));
3266 toggles.each(function(i) {
3267 toggleAllInherited(this, true);
3268 });
3269 $("#toggleAllClassInherited").text("[Collapse All]");
3270}
3271
3272
3273/* HANDLE KEY EVENTS
3274 * - Listen for Ctrl+F (Cmd on Mac) and expand all inherited members (to aid page search)
3275 */
3276var agent = navigator['userAgent'].toLowerCase();
3277var mac = agent.indexOf("macintosh") != -1;
3278
3279$(document).keydown( function(e) {
3280var control = mac ? e.metaKey && !e.ctrlKey : e.ctrlKey; // get ctrl key
3281 if (control && e.which == 70) { // 70 is "F"
3282 ensureAllInheritedExpanded();
3283 }
3284});
Scott Main498d7102013-08-21 15:47:38 -07003285
3286
3287
3288
3289
3290
3291/* On-demand functions */
3292
3293/** Move sample code line numbers out of PRE block and into non-copyable column */
3294function initCodeLineNumbers() {
3295 var numbers = $("#codesample-block a.number");
3296 if (numbers.length) {
3297 $("#codesample-line-numbers").removeClass("hidden").append(numbers);
3298 }
3299
3300 $(document).ready(function() {
3301 // select entire line when clicked
3302 $("span.code-line").click(function() {
3303 if (!shifted) {
3304 selectText(this);
3305 }
3306 });
3307 // invoke line link on double click
3308 $(".code-line").dblclick(function() {
3309 document.location.hash = $(this).attr('id');
3310 });
3311 // highlight the line when hovering on the number
3312 $("#codesample-line-numbers a.number").mouseover(function() {
3313 var id = $(this).attr('href');
3314 $(id).css('background','#e7e7e7');
3315 });
3316 $("#codesample-line-numbers a.number").mouseout(function() {
3317 var id = $(this).attr('href');
3318 $(id).css('background','none');
3319 });
3320 });
3321}
3322
3323// create SHIFT key binder to avoid the selectText method when selecting multiple lines
3324var shifted = false;
3325$(document).bind('keyup keydown', function(e){shifted = e.shiftKey; return true;} );
3326
3327// courtesy of jasonedelman.com
3328function selectText(element) {
3329 var doc = document
3330 , range, selection
3331 ;
3332 if (doc.body.createTextRange) { //ms
3333 range = doc.body.createTextRange();
3334 range.moveToElementText(element);
3335 range.select();
3336 } else if (window.getSelection) { //all others
Scott Main70557ee2013-10-30 14:47:40 -07003337 selection = window.getSelection();
Scott Main498d7102013-08-21 15:47:38 -07003338 range = doc.createRange();
3339 range.selectNodeContents(element);
3340 selection.removeAllRanges();
3341 selection.addRange(range);
3342 }
Scott Main285f0772013-08-22 23:22:09 +00003343}
Scott Main03aca9a2013-10-31 07:20:55 -07003344
3345
3346
3347
3348/** Display links and other information about samples that match the
3349 group specified by the URL */
3350function showSamples() {
3351 var group = $("#samples").attr('class');
3352 $("#samples").html("<p>Here are some samples for <b>" + group + "</b> apps:</p>");
3353
3354 var $ul = $("<ul>");
3355 $selectedLi = $("#nav li.selected");
3356
3357 $selectedLi.children("ul").children("li").each(function() {
3358 var $li = $("<li>").append($(this).find("a").first().clone());
3359 $ul.append($li);
3360 });
3361
3362 $("#samples").append($ul);
3363
3364}
Dirk Doughertyc3921652014-05-13 16:55:26 -07003365
3366
3367
3368/* ########################################################## */
3369/* ################### RESOURCE CARDS ##################### */
3370/* ########################################################## */
3371
3372/** Handle resource queries, collections, and grids (sections). Requires
3373 jd_tag_helpers.js and the *_unified_data.js to be loaded. */
3374
3375(function() {
3376 // Prevent the same resource from being loaded more than once per page.
3377 var addedPageResources = {};
3378
3379 $(document).ready(function() {
3380 $('.resource-widget').each(function() {
3381 initResourceWidget(this);
3382 });
3383
3384 /* Pass the line height to ellipsisfade() to adjust the height of the
3385 text container to show the max number of lines possible, without
3386 showing lines that are cut off. This works with the css ellipsis
3387 classes to fade last text line and apply an ellipsis char. */
3388
Scott Mainb16376f2014-05-21 20:35:47 -07003389 //card text currently uses 15px line height.
Dirk Doughertyc3921652014-05-13 16:55:26 -07003390 var lineHeight = 15;
3391 $('.card-info .text').ellipsisfade(lineHeight);
3392 });
3393
3394 /*
3395 Three types of resource layouts:
3396 Flow - Uses a fixed row-height flow using float left style.
3397 Carousel - Single card slideshow all same dimension absolute.
3398 Stack - Uses fixed columns and flexible element height.
3399 */
3400 function initResourceWidget(widget) {
3401 var $widget = $(widget);
3402 var isFlow = $widget.hasClass('resource-flow-layout'),
3403 isCarousel = $widget.hasClass('resource-carousel-layout'),
3404 isStack = $widget.hasClass('resource-stack-layout');
3405
3406 // find size of widget by pulling out its class name
3407 var sizeCols = 1;
3408 var m = $widget.get(0).className.match(/\bcol-(\d+)\b/);
3409 if (m) {
3410 sizeCols = parseInt(m[1], 10);
3411 }
3412
3413 var opts = {
3414 cardSizes: ($widget.data('cardsizes') || '').split(','),
3415 maxResults: parseInt($widget.data('maxresults') || '100', 10),
3416 itemsPerPage: $widget.data('itemsperpage'),
3417 sortOrder: $widget.data('sortorder'),
3418 query: $widget.data('query'),
3419 section: $widget.data('section'),
3420 sizeCols: sizeCols
3421 };
3422
3423 // run the search for the set of resources to show
3424
3425 var resources = buildResourceList(opts);
3426
3427 if (isFlow) {
3428 drawResourcesFlowWidget($widget, opts, resources);
3429 } else if (isCarousel) {
3430 drawResourcesCarouselWidget($widget, opts, resources);
3431 } else if (isStack) {
3432 var sections = buildSectionList(opts);
3433 opts['numStacks'] = $widget.data('numstacks');
3434 drawResourcesStackWidget($widget, opts, resources, sections);
3435 }
3436 }
3437
3438 /* Initializes a Resource Carousel Widget */
3439 function drawResourcesCarouselWidget($widget, opts, resources) {
3440 $widget.empty();
3441 var plusone = true; //always show plusone on carousel
3442
3443 $widget.addClass('resource-card slideshow-container')
3444 .append($('<a>').addClass('slideshow-prev').text('Prev'))
3445 .append($('<a>').addClass('slideshow-next').text('Next'));
3446
3447 var css = { 'width': $widget.width() + 'px',
3448 'height': $widget.height() + 'px' };
3449
3450 var $ul = $('<ul>');
3451
3452 for (var i = 0; i < resources.length; ++i) {
3453 //keep url clean for matching and offline mode handling
3454 var urlPrefix = resources[i].url.indexOf("//") > -1 ? "" : toRoot;
3455 var $card = $('<a>')
3456 .attr('href', urlPrefix + resources[i].url)
3457 .decorateResourceCard(resources[i],plusone);
3458
3459 $('<li>').css(css)
3460 .append($card)
3461 .appendTo($ul);
3462 }
3463
3464 $('<div>').addClass('frame')
3465 .append($ul)
3466 .appendTo($widget);
3467
3468 $widget.dacSlideshow({
3469 auto: true,
3470 btnPrev: '.slideshow-prev',
3471 btnNext: '.slideshow-next'
3472 });
3473 };
3474
3475 /* Initializes a Resource Card Stack Widget (column-based layout) */
3476 function drawResourcesStackWidget($widget, opts, resources, sections) {
3477 // Don't empty widget, grab all items inside since they will be the first
3478 // items stacked, followed by the resource query
3479 var plusone = true; //by default show plusone on section cards
3480 var cards = $widget.find('.resource-card').detach().toArray();
3481 var numStacks = opts.numStacks || 1;
3482 var $stacks = [];
3483 var urlString;
3484
3485 for (var i = 0; i < numStacks; ++i) {
3486 $stacks[i] = $('<div>').addClass('resource-card-stack')
3487 .appendTo($widget);
3488 }
3489
3490 var sectionResources = [];
3491
3492 // Extract any subsections that are actually resource cards
3493 for (var i = 0; i < sections.length; ++i) {
3494 if (!sections[i].sections || !sections[i].sections.length) {
3495 //keep url clean for matching and offline mode handling
3496 urlPrefix = sections[i].url.indexOf("//") > -1 ? "" : toRoot;
3497 // Render it as a resource card
3498
3499 sectionResources.push(
3500 $('<a>')
3501 .addClass('resource-card section-card')
3502 .attr('href', urlPrefix + sections[i].resource.url)
3503 .decorateResourceCard(sections[i].resource,plusone)[0]
3504 );
3505
3506 } else {
3507 cards.push(
3508 $('<div>')
3509 .addClass('resource-card section-card-menu')
3510 .decorateResourceSection(sections[i],plusone)[0]
3511 );
3512 }
3513 }
3514
3515 cards = cards.concat(sectionResources);
3516
3517 for (var i = 0; i < resources.length; ++i) {
3518 //keep url clean for matching and offline mode handling
3519 urlPrefix = resources[i].url.indexOf("//") > -1 ? "" : toRoot;
3520 var $card = $('<a>')
3521 .addClass('resource-card related-card')
3522 .attr('href', urlPrefix + resources[i].url)
3523 .decorateResourceCard(resources[i],plusone);
3524
3525 cards.push($card[0]);
3526 }
3527
3528 for (var i = 0; i < cards.length; ++i) {
3529 // Find the stack with the shortest height, but give preference to
3530 // left to right order.
3531 var minHeight = $stacks[0].height();
3532 var minIndex = 0;
3533
3534 for (var j = 1; j < numStacks; ++j) {
3535 var height = $stacks[j].height();
3536 if (height < minHeight - 45) {
3537 minHeight = height;
3538 minIndex = j;
3539 }
3540 }
3541
3542 $stacks[minIndex].append($(cards[i]));
3543 }
3544
3545 };
3546
3547 /* Initializes a flow widget, see distribute.scss for generating accompanying css */
3548 function drawResourcesFlowWidget($widget, opts, resources) {
3549 $widget.empty();
3550 var cardSizes = opts.cardSizes || ['6x6'];
3551 var i = 0, j = 0;
3552 var plusone = true; // by default show plusone on resource cards
3553
3554 while (i < resources.length) {
3555 var cardSize = cardSizes[j++ % cardSizes.length];
3556 cardSize = cardSize.replace(/^\s+|\s+$/,'');
Dirk Doughertyc3921652014-05-13 16:55:26 -07003557 // Some card sizes do not get a plusone button, such as where space is constrained
3558 // or for cards commonly embedded in docs (to improve overall page speed).
3559 plusone = !((cardSize == "6x2") || (cardSize == "6x3") ||
3560 (cardSize == "9x2") || (cardSize == "9x3") ||
3561 (cardSize == "12x2") || (cardSize == "12x3"));
3562
3563 // A stack has a third dimension which is the number of stacked items
3564 var isStack = cardSize.match(/(\d+)x(\d+)x(\d+)/);
3565 var stackCount = 0;
3566 var $stackDiv = null;
3567
3568 if (isStack) {
3569 // Create a stack container which should have the dimensions defined
3570 // by the product of the items inside.
3571 $stackDiv = $('<div>').addClass('resource-card-stack resource-card-' + isStack[1]
3572 + 'x' + isStack[2] * isStack[3]) .appendTo($widget);
3573 }
3574
3575 // Build each stack item or just a single item
3576 do {
3577 var resource = resources[i];
3578 //keep url clean for matching and offline mode handling
3579 urlPrefix = resource.url.indexOf("//") > -1 ? "" : toRoot;
3580 var $card = $('<a>')
3581 .addClass('resource-card resource-card-' + cardSize + ' resource-card-' + resource.type)
3582 .attr('href', urlPrefix + resource.url);
3583
3584 if (isStack) {
3585 $card.addClass('resource-card-' + isStack[1] + 'x' + isStack[2]);
3586 if (++stackCount == parseInt(isStack[3])) {
3587 $card.addClass('resource-card-row-stack-last');
3588 stackCount = 0;
3589 }
3590 } else {
3591 stackCount = 0;
3592 }
3593
3594 $card.decorateResourceCard(resource,plusone)
3595 .appendTo($stackDiv || $widget);
3596
3597 } while (++i < resources.length && stackCount > 0);
3598 }
3599 }
3600
3601 /* Build a site map of resources using a section as a root. */
3602 function buildSectionList(opts) {
3603 if (opts.section && SECTION_BY_ID[opts.section]) {
3604 return SECTION_BY_ID[opts.section].sections || [];
3605 }
3606 return [];
3607 }
3608
3609 function buildResourceList(opts) {
3610 var maxResults = opts.maxResults || 100;
3611
3612 var query = opts.query || '';
3613 var expressions = parseResourceQuery(query);
3614 var addedResourceIndices = {};
3615 var results = [];
3616
3617 for (var i = 0; i < expressions.length; i++) {
3618 var clauses = expressions[i];
3619
3620 // build initial set of resources from first clause
3621 var firstClause = clauses[0];
3622 var resources = [];
3623 switch (firstClause.attr) {
3624 case 'type':
3625 resources = ALL_RESOURCES_BY_TYPE[firstClause.value];
3626 break;
3627 case 'lang':
3628 resources = ALL_RESOURCES_BY_LANG[firstClause.value];
3629 break;
3630 case 'tag':
3631 resources = ALL_RESOURCES_BY_TAG[firstClause.value];
3632 break;
3633 case 'collection':
3634 var urls = RESOURCE_COLLECTIONS[firstClause.value].resources || [];
3635 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3636 break;
3637 case 'section':
3638 var urls = SITE_MAP[firstClause.value].sections || [];
3639 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3640 break;
3641 }
3642 // console.log(firstClause.attr + ':' + firstClause.value);
3643 resources = resources || [];
3644
3645 // use additional clauses to filter corpus
3646 if (clauses.length > 1) {
3647 var otherClauses = clauses.slice(1);
3648 resources = resources.filter(getResourceMatchesClausesFilter(otherClauses));
3649 }
3650
3651 // filter out resources already added
3652 if (i > 1) {
3653 resources = resources.filter(getResourceNotAlreadyAddedFilter(addedResourceIndices));
3654 }
3655
3656 // add to list of already added indices
3657 for (var j = 0; j < resources.length; j++) {
3658 // console.log(resources[j].title);
3659 addedResourceIndices[resources[j].index] = 1;
3660 }
3661
3662 // concat to final results list
3663 results = results.concat(resources);
3664 }
3665
3666 if (opts.sortOrder && results.length) {
3667 var attr = opts.sortOrder;
3668
3669 if (opts.sortOrder == 'random') {
3670 var i = results.length, j, temp;
3671 while (--i) {
3672 j = Math.floor(Math.random() * (i + 1));
3673 temp = results[i];
3674 results[i] = results[j];
3675 results[j] = temp;
3676 }
3677 } else {
3678 var desc = attr.charAt(0) == '-';
3679 if (desc) {
3680 attr = attr.substring(1);
3681 }
3682 results = results.sort(function(x,y) {
3683 return (desc ? -1 : 1) * (parseInt(x[attr], 10) - parseInt(y[attr], 10));
3684 });
3685 }
3686 }
3687
3688 results = results.filter(getResourceNotAlreadyAddedFilter(addedPageResources));
3689 results = results.slice(0, maxResults);
3690
3691 for (var j = 0; j < results.length; ++j) {
3692 addedPageResources[results[j].index] = 1;
3693 }
3694
3695 return results;
3696 }
3697
3698
3699 function getResourceNotAlreadyAddedFilter(addedResourceIndices) {
3700 return function(resource) {
3701 return !addedResourceIndices[resource.index];
3702 };
3703 }
3704
3705
3706 function getResourceMatchesClausesFilter(clauses) {
3707 return function(resource) {
3708 return doesResourceMatchClauses(resource, clauses);
3709 };
3710 }
3711
3712
3713 function doesResourceMatchClauses(resource, clauses) {
3714 for (var i = 0; i < clauses.length; i++) {
3715 var map;
3716 switch (clauses[i].attr) {
3717 case 'type':
3718 map = IS_RESOURCE_OF_TYPE[clauses[i].value];
3719 break;
3720 case 'lang':
3721 map = IS_RESOURCE_IN_LANG[clauses[i].value];
3722 break;
3723 case 'tag':
3724 map = IS_RESOURCE_TAGGED[clauses[i].value];
3725 break;
3726 }
3727
3728 if (!map || (!!clauses[i].negative ? map[resource.index] : !map[resource.index])) {
3729 return clauses[i].negative;
3730 }
3731 }
3732 return true;
3733 }
3734
3735
3736 function parseResourceQuery(query) {
3737 // Parse query into array of expressions (expression e.g. 'tag:foo + type:video')
3738 var expressions = [];
3739 var expressionStrs = query.split(',') || [];
3740 for (var i = 0; i < expressionStrs.length; i++) {
3741 var expr = expressionStrs[i] || '';
3742
3743 // Break expression into clauses (clause e.g. 'tag:foo')
3744 var clauses = [];
3745 var clauseStrs = expr.split(/(?=[\+\-])/);
3746 for (var j = 0; j < clauseStrs.length; j++) {
3747 var clauseStr = clauseStrs[j] || '';
3748
3749 // Get attribute and value from clause (e.g. attribute='tag', value='foo')
3750 var parts = clauseStr.split(':');
3751 var clause = {};
3752
3753 clause.attr = parts[0].replace(/^\s+|\s+$/g,'');
3754 if (clause.attr) {
3755 if (clause.attr.charAt(0) == '+') {
3756 clause.attr = clause.attr.substring(1);
3757 } else if (clause.attr.charAt(0) == '-') {
3758 clause.negative = true;
3759 clause.attr = clause.attr.substring(1);
3760 }
3761 }
3762
3763 if (parts.length > 1) {
3764 clause.value = parts[1].replace(/^\s+|\s+$/g,'');
3765 }
3766
3767 clauses.push(clause);
3768 }
3769
3770 if (!clauses.length) {
3771 continue;
3772 }
3773
3774 expressions.push(clauses);
3775 }
3776
3777 return expressions;
3778 }
3779})();
3780
3781(function($) {
3782 /* Simple jquery function to create dom for a standard resource card */
3783 $.fn.decorateResourceCard = function(resource,plusone) {
3784 var section = resource.group || resource.type;
3785 var imgUrl;
3786 if (resource.image) {
3787 //keep url clean for matching and offline mode handling
3788 var urlPrefix = resource.image.indexOf("//") > -1 ? "" : toRoot;
3789 imgUrl = urlPrefix + resource.image;
3790 }
3791 //add linkout logic here. check url or type, assign a class, map to css :after
3792 $('<div>')
3793 .addClass('card-bg')
3794 .css('background-image', 'url(' + (imgUrl || toRoot + 'assets/images/resource-card-default-android.jpg') + ')')
3795 .appendTo(this);
3796 if (!plusone) {
3797 $('<div>').addClass('card-info' + (!resource.summary ? ' empty-desc' : ''))
3798 .append($('<div>').addClass('section').text(section))
3799 .append($('<div>').addClass('title').html(resource.title))
3800 .append($('<div>').addClass('description ellipsis')
3801 .append($('<div>').addClass('text').html(resource.summary))
3802 .append($('<div>').addClass('util')))
3803 .appendTo(this);
3804 } else {
Dirk Dougherty518ed142014-05-30 21:24:42 -07003805 var plusurl = resource.url.indexOf("//") > -1 ? resource.url : "//developer.android.com/" + resource.url;
Dirk Doughertyc3921652014-05-13 16:55:26 -07003806 $('<div>').addClass('card-info' + (!resource.summary ? ' empty-desc' : ''))
3807 .append($('<div>').addClass('section').text(section))
3808 .append($('<div>').addClass('title').html(resource.title))
3809 .append($('<div>').addClass('description ellipsis')
3810 .append($('<div>').addClass('text').html(resource.summary))
3811 .append($('<div>').addClass('util')
3812 .append($('<div>').addClass('g-plusone')
3813 .attr('data-size', 'small')
3814 .attr('data-align', 'right')
Dirk Dougherty518ed142014-05-30 21:24:42 -07003815 .attr('data-href', plusurl))))
Dirk Doughertyc3921652014-05-13 16:55:26 -07003816 .appendTo(this);
3817 }
3818
3819 return this;
3820 };
3821
3822 /* Simple jquery function to create dom for a resource section card (menu) */
3823 $.fn.decorateResourceSection = function(section,plusone) {
3824 var resource = section.resource;
3825 //keep url clean for matching and offline mode handling
3826 var urlPrefix = resource.image.indexOf("//") > -1 ? "" : toRoot;
3827 var $base = $('<a>')
3828 .addClass('card-bg')
3829 .attr('href', resource.url)
3830 .append($('<div>').addClass('card-section-icon')
3831 .append($('<div>').addClass('icon'))
3832 .append($('<div>').addClass('section').html(resource.title)))
3833 .appendTo(this);
3834
3835 var $cardInfo = $('<div>').addClass('card-info').appendTo(this);
3836
3837 if (section.sections && section.sections.length) {
3838 // Recurse the section sub-tree to find a resource image.
3839 var stack = [section];
3840
3841 while (stack.length) {
3842 if (stack[0].resource.image) {
3843 $base.css('background-image', 'url(' + urlPrefix + stack[0].resource.image + ')');
3844 break;
3845 }
3846
3847 if (stack[0].sections) {
3848 stack = stack.concat(stack[0].sections);
3849 }
3850
3851 stack.shift();
3852 }
3853
3854 var $ul = $('<ul>')
3855 .appendTo($cardInfo);
3856
3857 var max = section.sections.length > 3 ? 3 : section.sections.length;
3858
3859 for (var i = 0; i < max; ++i) {
3860
3861 var subResource = section.sections[i];
3862 if (!plusone) {
3863 $('<li>')
3864 .append($('<a>').attr('href', subResource.url)
3865 .append($('<div>').addClass('title').html(subResource.title))
3866 .append($('<div>').addClass('description ellipsis')
3867 .append($('<div>').addClass('text').html(subResource.summary))
3868 .append($('<div>').addClass('util'))))
3869 .appendTo($ul);
3870 } else {
3871 $('<li>')
3872 .append($('<a>').attr('href', subResource.url)
3873 .append($('<div>').addClass('title').html(subResource.title))
3874 .append($('<div>').addClass('description ellipsis')
3875 .append($('<div>').addClass('text').html(subResource.summary))
3876 .append($('<div>').addClass('util')
3877 .append($('<div>').addClass('g-plusone')
3878 .attr('data-size', 'small')
3879 .attr('data-align', 'right')
3880 .attr('data-href', resource.url)))))
3881 .appendTo($ul);
3882 }
3883 }
3884
3885 // Add a more row
3886 if (max < section.sections.length) {
3887 $('<li>')
3888 .append($('<a>').attr('href', resource.url)
3889 .append($('<div>')
3890 .addClass('title')
3891 .text('More')))
3892 .appendTo($ul);
3893 }
3894 } else {
3895 // No sub-resources, just render description?
3896 }
3897
3898 return this;
3899 };
3900})(jQuery);
3901/* Calculate the vertical area remaining */
3902(function($) {
3903 $.fn.ellipsisfade= function(lineHeight) {
3904 this.each(function() {
3905 // get element text
3906 var $this = $(this);
3907 var remainingHeight = $this.parent().parent().height();
3908 $this.parent().siblings().each(function ()
3909 {
3910 var h = $(this).height();
3911 remainingHeight = remainingHeight - h;
3912 });
3913
3914 adjustedRemainingHeight = ((remainingHeight)/lineHeight>>0)*lineHeight
3915 $this.parent().css({'height': adjustedRemainingHeight});
3916 $this.css({'height': "auto"});
3917 });
3918
3919 return this;
3920 };
3921}) (jQuery);