blob: 66c72a84159cdf41439b187262ace9e59aca9fec [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
22var navBarIsFixed = false;
23$(document).ready(function() {
Scott Main7e447ed2013-02-19 17:22:37 -080024
Scott Main0e76e7e2013-03-12 10:24:07 -070025 // load json file for JD doc search suggestions
Scott Main719acb42013-12-05 16:05:09 -080026 $.getScript(toRoot + 'jd_lists_unified.js');
Scott Main7e447ed2013-02-19 17:22:37 -080027 // load json file for Android API search suggestions
28 $.getScript(toRoot + 'reference/lists.js');
29 // load json files for Google services API suggestions
Scott Main9f2971d2013-02-26 13:07:41 -080030 $.getScript(toRoot + 'reference/gcm_lists.js', function(data, textStatus, jqxhr) {
Scott Main7e447ed2013-02-19 17:22:37 -080031 // once the GCM json (GCM_DATA) is loaded, load the GMS json (GMS_DATA) and merge the data
32 if(jqxhr.status === 200) {
Scott Main9f2971d2013-02-26 13:07:41 -080033 $.getScript(toRoot + 'reference/gms_lists.js', function(data, textStatus, jqxhr) {
Scott Main7e447ed2013-02-19 17:22:37 -080034 if(jqxhr.status === 200) {
35 // combine GCM and GMS data
36 GOOGLE_DATA = GMS_DATA;
37 var start = GOOGLE_DATA.length;
38 for (var i=0; i<GCM_DATA.length; i++) {
39 GOOGLE_DATA.push({id:start+i, label:GCM_DATA[i].label,
40 link:GCM_DATA[i].link, type:GCM_DATA[i].type});
41 }
42 }
43 });
44 }
45 });
46
Scott Main0e76e7e2013-03-12 10:24:07 -070047 // setup keyboard listener for search shortcut
48 $('body').keyup(function(event) {
49 if (event.which == 191) {
50 $('#search_autocomplete').focus();
51 }
52 });
Scott Main015d6162013-01-29 09:01:52 -080053
Scott Maine4d8f1b2012-06-21 18:03:05 -070054 // init the fullscreen toggle click event
55 $('#nav-swap .fullscreen').click(function(){
56 if ($(this).hasClass('disabled')) {
57 toggleFullscreen(true);
58 } else {
59 toggleFullscreen(false);
60 }
61 });
Scott Main3b90aff2013-08-01 18:09:35 -070062
Scott Maine4d8f1b2012-06-21 18:03:05 -070063 // initialize the divs with custom scrollbars
64 $('.scroll-pane').jScrollPane( {verticalGutter:0} );
Scott Main3b90aff2013-08-01 18:09:35 -070065
Scott Maine4d8f1b2012-06-21 18:03:05 -070066 // add HRs below all H2s (except for a few other h2 variants)
Scott Maindb3678b2012-10-23 14:13:41 -070067 $('h2').not('#qv h2').not('#tb h2').not('.sidebox h2').not('#devdoc-nav h2').not('h2.norule').css({marginBottom:0}).after('<hr/>');
Scott Maine4d8f1b2012-06-21 18:03:05 -070068
69 // set up the search close button
70 $('.search .close').click(function() {
71 $searchInput = $('#search_autocomplete');
72 $searchInput.attr('value', '');
73 $(this).addClass("hide");
74 $("#search-container").removeClass('active');
75 $("#search_autocomplete").blur();
Scott Main0e76e7e2013-03-12 10:24:07 -070076 search_focus_changed($searchInput.get(), false);
77 hideResults();
Scott Maine4d8f1b2012-06-21 18:03:05 -070078 });
79
80 // Set up quicknav
Scott Main3b90aff2013-08-01 18:09:35 -070081 var quicknav_open = false;
Scott Maine4d8f1b2012-06-21 18:03:05 -070082 $("#btn-quicknav").click(function() {
83 if (quicknav_open) {
84 $(this).removeClass('active');
85 quicknav_open = false;
86 collapse();
87 } else {
88 $(this).addClass('active');
89 quicknav_open = true;
90 expand();
91 }
92 })
Scott Main3b90aff2013-08-01 18:09:35 -070093
Scott Maine4d8f1b2012-06-21 18:03:05 -070094 var expand = function() {
95 $('#header-wrap').addClass('quicknav');
96 $('#quicknav').stop().show().animate({opacity:'1'});
97 }
Scott Main3b90aff2013-08-01 18:09:35 -070098
Scott Maine4d8f1b2012-06-21 18:03:05 -070099 var collapse = function() {
100 $('#quicknav').stop().animate({opacity:'0'}, 100, function() {
101 $(this).hide();
102 $('#header-wrap').removeClass('quicknav');
103 });
104 }
Scott Main3b90aff2013-08-01 18:09:35 -0700105
106
Scott Maine4d8f1b2012-06-21 18:03:05 -0700107 //Set up search
108 $("#search_autocomplete").focus(function() {
109 $("#search-container").addClass('active');
110 })
111 $("#search-container").mouseover(function() {
112 $("#search-container").addClass('active');
113 $("#search_autocomplete").focus();
114 })
115 $("#search-container").mouseout(function() {
116 if ($("#search_autocomplete").is(":focus")) return;
117 if ($("#search_autocomplete").val() == '') {
118 setTimeout(function(){
119 $("#search-container").removeClass('active');
120 $("#search_autocomplete").blur();
121 },250);
122 }
123 })
124 $("#search_autocomplete").blur(function() {
125 if ($("#search_autocomplete").val() == '') {
126 $("#search-container").removeClass('active');
127 }
128 })
129
Scott Main3b90aff2013-08-01 18:09:35 -0700130
Scott Maine4d8f1b2012-06-21 18:03:05 -0700131 // prep nav expandos
132 var pagePath = document.location.pathname;
133 // account for intl docs by removing the intl/*/ path
134 if (pagePath.indexOf("/intl/") == 0) {
135 pagePath = pagePath.substr(pagePath.indexOf("/",6)); // start after intl/ to get last /
136 }
Scott Mainac2aef52013-02-12 14:15:23 -0800137
Scott Maine4d8f1b2012-06-21 18:03:05 -0700138 if (pagePath.indexOf(SITE_ROOT) == 0) {
139 if (pagePath == '' || pagePath.charAt(pagePath.length - 1) == '/') {
140 pagePath += 'index.html';
141 }
142 }
143
Scott Main01a25452013-02-12 17:32:27 -0800144 // Need a copy of the pagePath before it gets changed in the next block;
145 // it's needed to perform proper tab highlighting in offline docs (see rootDir below)
146 var pagePathOriginal = pagePath;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700147 if (SITE_ROOT.match(/\.\.\//) || SITE_ROOT == '') {
148 // If running locally, SITE_ROOT will be a relative path, so account for that by
149 // finding the relative URL to this page. This will allow us to find links on the page
150 // leading back to this page.
151 var pathParts = pagePath.split('/');
152 var relativePagePathParts = [];
153 var upDirs = (SITE_ROOT.match(/(\.\.\/)+/) || [''])[0].length / 3;
154 for (var i = 0; i < upDirs; i++) {
155 relativePagePathParts.push('..');
156 }
157 for (var i = 0; i < upDirs; i++) {
158 relativePagePathParts.push(pathParts[pathParts.length - (upDirs - i) - 1]);
159 }
160 relativePagePathParts.push(pathParts[pathParts.length - 1]);
161 pagePath = relativePagePathParts.join('/');
162 } else {
163 // Otherwise the page path is already an absolute URL
164 }
165
Scott Mainac2aef52013-02-12 14:15:23 -0800166 // Highlight the header tabs...
167 // highlight Design tab
168 if ($("body").hasClass("design")) {
169 $("#header li.design a").addClass("selected");
Dirk Doughertyc3921652014-05-13 16:55:26 -0700170 $("#sticky-header").addClass("design");
Scott Mainac2aef52013-02-12 14:15:23 -0800171
172 // highlight Develop tab
173 } else if ($("body").hasClass("develop") || $("body").hasClass("google")) {
174 $("#header li.develop a").addClass("selected");
Dirk Doughertyc3921652014-05-13 16:55:26 -0700175 $("#sticky-header").addClass("develop");
Scott Mainac2aef52013-02-12 14:15:23 -0800176 // In Develop docs, also highlight appropriate sub-tab
Scott Main01a25452013-02-12 17:32:27 -0800177 var rootDir = pagePathOriginal.substring(1,pagePathOriginal.indexOf('/', 1));
Scott Mainac2aef52013-02-12 14:15:23 -0800178 if (rootDir == "training") {
179 $("#nav-x li.training a").addClass("selected");
180 } else if (rootDir == "guide") {
181 $("#nav-x li.guide a").addClass("selected");
182 } else if (rootDir == "reference") {
183 // If the root is reference, but page is also part of Google Services, select Google
184 if ($("body").hasClass("google")) {
185 $("#nav-x li.google a").addClass("selected");
186 } else {
187 $("#nav-x li.reference a").addClass("selected");
188 }
189 } else if ((rootDir == "tools") || (rootDir == "sdk")) {
190 $("#nav-x li.tools a").addClass("selected");
191 } else if ($("body").hasClass("google")) {
192 $("#nav-x li.google a").addClass("selected");
Dirk Dougherty4f7e5152010-09-16 10:43:40 -0700193 } else if ($("body").hasClass("samples")) {
194 $("#nav-x li.samples a").addClass("selected");
Scott Mainac2aef52013-02-12 14:15:23 -0800195 }
196
197 // highlight Distribute tab
198 } else if ($("body").hasClass("distribute")) {
199 $("#header li.distribute a").addClass("selected");
Dirk Doughertyc3921652014-05-13 16:55:26 -0700200 $("#sticky-header").addClass("distribute");
201
202 var baseFrag = pagePathOriginal.indexOf('/', 1) + 1;
203 var secondFrag = pagePathOriginal.substring(baseFrag, pagePathOriginal.indexOf('/', baseFrag));
204 if (secondFrag == "users") {
205 $("#nav-x li.users a").addClass("selected");
206 } else if (secondFrag == "engage") {
207 $("#nav-x li.engage a").addClass("selected");
208 } else if (secondFrag == "monetize") {
209 $("#nav-x li.monetize a").addClass("selected");
210 } else if (secondFrag == "tools") {
211 $("#nav-x li.disttools a").addClass("selected");
212 } else if (secondFrag == "stories") {
213 $("#nav-x li.stories a").addClass("selected");
214 } else if (secondFrag == "essentials") {
215 $("#nav-x li.essentials a").addClass("selected");
216 } else if (secondFrag == "googleplay") {
217 $("#nav-x li.googleplay a").addClass("selected");
218 }
219 } else if ($("body").hasClass("about")) {
220 $("#sticky-header").addClass("about");
Scott Mainb16376f2014-05-21 20:35:47 -0700221 }
Scott Mainac2aef52013-02-12 14:15:23 -0800222
Scott Mainf6145542013-04-01 16:38:11 -0700223 // set global variable so we can highlight the sidenav a bit later (such as for google reference)
224 // and highlight the sidenav
225 mPagePath = pagePath;
226 highlightSidenav();
Dirk Doughertyc3921652014-05-13 16:55:26 -0700227 buildBreadcrumbs();
Scott Mainac2aef52013-02-12 14:15:23 -0800228
Scott Mainf6145542013-04-01 16:38:11 -0700229 // set up prev/next links if they exist
Scott Maine4d8f1b2012-06-21 18:03:05 -0700230 var $selNavLink = $('#nav').find('a[href="' + pagePath + '"]');
Scott Main5a1123e2012-09-26 12:51:28 -0700231 var $selListItem;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700232 if ($selNavLink.length) {
Scott Mainac2aef52013-02-12 14:15:23 -0800233 $selListItem = $selNavLink.closest('li');
Scott Maine4d8f1b2012-06-21 18:03:05 -0700234
235 // set up prev links
236 var $prevLink = [];
237 var $prevListItem = $selListItem.prev('li');
Scott Main3b90aff2013-08-01 18:09:35 -0700238
Scott Maine4d8f1b2012-06-21 18:03:05 -0700239 var crossBoundaries = ($("body.design").length > 0) || ($("body.guide").length > 0) ? true :
240false; // navigate across topic boundaries only in design docs
241 if ($prevListItem.length) {
242 if ($prevListItem.hasClass('nav-section')) {
Scott Main5a1123e2012-09-26 12:51:28 -0700243 // jump to last topic of previous section
244 $prevLink = $prevListItem.find('a:last');
245 } else if (!$selListItem.hasClass('nav-section')) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700246 // jump to previous topic in this section
247 $prevLink = $prevListItem.find('a:eq(0)');
248 }
249 } else {
250 // jump to this section's index page (if it exists)
251 var $parentListItem = $selListItem.parents('li');
252 $prevLink = $selListItem.parents('li').find('a');
Scott Main3b90aff2013-08-01 18:09:35 -0700253
Scott Maine4d8f1b2012-06-21 18:03:05 -0700254 // except if cross boundaries aren't allowed, and we're at the top of a section already
255 // (and there's another parent)
Scott Main3b90aff2013-08-01 18:09:35 -0700256 if (!crossBoundaries && $parentListItem.hasClass('nav-section')
Scott Maine4d8f1b2012-06-21 18:03:05 -0700257 && $selListItem.hasClass('nav-section')) {
258 $prevLink = [];
259 }
260 }
261
Scott Maine4d8f1b2012-06-21 18:03:05 -0700262 // set up next links
263 var $nextLink = [];
Scott Maine4d8f1b2012-06-21 18:03:05 -0700264 var startClass = false;
265 var training = $(".next-class-link").length; // decides whether to provide "next class" link
266 var isCrossingBoundary = false;
Scott Main3b90aff2013-08-01 18:09:35 -0700267
Scott Main1a00f7f2013-10-29 11:11:19 -0700268 if ($selListItem.hasClass('nav-section') && $selListItem.children('div.empty').length == 0) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700269 // we're on an index page, jump to the first topic
Scott Mainb505ca62012-07-26 18:00:14 -0700270 $nextLink = $selListItem.find('ul:eq(0)').find('a:eq(0)');
Scott Maine4d8f1b2012-06-21 18:03:05 -0700271
272 // if there aren't any children, go to the next section (required for About pages)
273 if($nextLink.length == 0) {
274 $nextLink = $selListItem.next('li').find('a');
Scott Mainb505ca62012-07-26 18:00:14 -0700275 } else if ($('.topic-start-link').length) {
276 // as long as there's a child link and there is a "topic start link" (we're on a landing)
277 // then set the landing page "start link" text to be the first doc title
278 $('.topic-start-link').text($nextLink.text().toUpperCase());
Scott Maine4d8f1b2012-06-21 18:03:05 -0700279 }
Scott Main3b90aff2013-08-01 18:09:35 -0700280
Scott Main5a1123e2012-09-26 12:51:28 -0700281 // If the selected page has a description, then it's a class or article homepage
282 if ($selListItem.find('a[description]').length) {
283 // this means we're on a class landing page
Scott Maine4d8f1b2012-06-21 18:03:05 -0700284 startClass = true;
285 }
286 } else {
287 // jump to the next topic in this section (if it exists)
288 $nextLink = $selListItem.next('li').find('a:eq(0)');
Scott Main1a00f7f2013-10-29 11:11:19 -0700289 if ($nextLink.length == 0) {
Scott Main5a1123e2012-09-26 12:51:28 -0700290 isCrossingBoundary = true;
291 // no more topics in this section, jump to the first topic in the next section
292 $nextLink = $selListItem.parents('li:eq(0)').next('li.nav-section').find('a:eq(0)');
293 if (!$nextLink.length) { // Go up another layer to look for next page (lesson > class > course)
294 $nextLink = $selListItem.parents('li:eq(1)').next('li.nav-section').find('a:eq(0)');
Scott Main1a00f7f2013-10-29 11:11:19 -0700295 if ($nextLink.length == 0) {
296 // if that doesn't work, we're at the end of the list, so disable NEXT link
297 $('.next-page-link').attr('href','').addClass("disabled")
298 .click(function() { return false; });
299 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700300 }
301 }
302 }
Scott Main5a1123e2012-09-26 12:51:28 -0700303
304 if (startClass) {
305 $('.start-class-link').attr('href', $nextLink.attr('href')).removeClass("hide");
306
Scott Main3b90aff2013-08-01 18:09:35 -0700307 // if there's no training bar (below the start button),
Scott Main5a1123e2012-09-26 12:51:28 -0700308 // then we need to add a bottom border to button
309 if (!$("#tb").length) {
310 $('.start-class-link').css({'border-bottom':'1px solid #DADADA'});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700311 }
Scott Main5a1123e2012-09-26 12:51:28 -0700312 } else if (isCrossingBoundary && !$('body.design').length) { // Design always crosses boundaries
313 $('.content-footer.next-class').show();
314 $('.next-page-link').attr('href','')
315 .removeClass("hide").addClass("disabled")
316 .click(function() { return false; });
Scott Main1a00f7f2013-10-29 11:11:19 -0700317 if ($nextLink.length) {
318 $('.next-class-link').attr('href',$nextLink.attr('href'))
319 .removeClass("hide").append($nextLink.html());
320 $('.next-class-link').find('.new').empty();
321 }
Scott Main5a1123e2012-09-26 12:51:28 -0700322 } else {
323 $('.next-page-link').attr('href', $nextLink.attr('href')).removeClass("hide");
324 }
325
326 if (!startClass && $prevLink.length) {
327 var prevHref = $prevLink.attr('href');
328 if (prevHref == SITE_ROOT + 'index.html') {
329 // Don't show Previous when it leads to the homepage
330 } else {
331 $('.prev-page-link').attr('href', $prevLink.attr('href')).removeClass("hide");
332 }
Scott Main3b90aff2013-08-01 18:09:35 -0700333 }
Scott Main5a1123e2012-09-26 12:51:28 -0700334
335 // If this is a training 'article', there should be no prev/next nav
336 // ... if the grandparent is the "nav" ... and it has no child list items...
337 if (training && $selListItem.parents('ul').eq(1).is('[id="nav"]') &&
338 !$selListItem.find('li').length) {
339 $('.next-page-link,.prev-page-link').attr('href','').addClass("disabled")
340 .click(function() { return false; });
Scott Maine4d8f1b2012-06-21 18:03:05 -0700341 }
Scott Main3b90aff2013-08-01 18:09:35 -0700342
Scott Maine4d8f1b2012-06-21 18:03:05 -0700343 }
Scott Main3b90aff2013-08-01 18:09:35 -0700344
345
346
Scott Main5a1123e2012-09-26 12:51:28 -0700347 // Set up the course landing pages for Training with class names and descriptions
348 if ($('body.trainingcourse').length) {
349 var $classLinks = $selListItem.find('ul li a').not('#nav .nav-section .nav-section ul a');
Scott Maine7d75352014-05-22 15:50:56 -0700350
351 // create an array for all the class descriptions
352 var $classDescriptions = new Array($classLinks.length);
353 var lang = getLangPref();
354 $classLinks.each(function(index) {
355 var langDescr = $(this).attr(lang + "-description");
356 if (typeof langDescr !== 'undefined' && langDescr !== false) {
357 // if there's a class description in the selected language, use that
358 $classDescriptions[index] = langDescr;
359 } else {
360 // otherwise, use the default english description
361 $classDescriptions[index] = $(this).attr("description");
362 }
363 });
Scott Main3b90aff2013-08-01 18:09:35 -0700364
Scott Main5a1123e2012-09-26 12:51:28 -0700365 var $olClasses = $('<ol class="class-list"></ol>');
366 var $liClass;
367 var $imgIcon;
368 var $h2Title;
369 var $pSummary;
370 var $olLessons;
371 var $liLesson;
372 $classLinks.each(function(index) {
373 $liClass = $('<li></li>');
374 $h2Title = $('<a class="title" href="'+$(this).attr('href')+'"><h2>' + $(this).html()+'</h2><span></span></a>');
Scott Maine7d75352014-05-22 15:50:56 -0700375 $pSummary = $('<p class="description">' + $classDescriptions[index] + '</p>');
Scott Main3b90aff2013-08-01 18:09:35 -0700376
Scott Main5a1123e2012-09-26 12:51:28 -0700377 $olLessons = $('<ol class="lesson-list"></ol>');
Scott Main3b90aff2013-08-01 18:09:35 -0700378
Scott Main5a1123e2012-09-26 12:51:28 -0700379 $lessons = $(this).closest('li').find('ul li a');
Scott Main3b90aff2013-08-01 18:09:35 -0700380
Scott Main5a1123e2012-09-26 12:51:28 -0700381 if ($lessons.length) {
Scott Main3b90aff2013-08-01 18:09:35 -0700382 $imgIcon = $('<img src="'+toRoot+'assets/images/resource-tutorial.png" '
383 + ' width="64" height="64" alt=""/>');
Scott Main5a1123e2012-09-26 12:51:28 -0700384 $lessons.each(function(index) {
385 $olLessons.append('<li><a href="'+$(this).attr('href')+'">' + $(this).html()+'</a></li>');
386 });
387 } else {
Scott Main3b90aff2013-08-01 18:09:35 -0700388 $imgIcon = $('<img src="'+toRoot+'assets/images/resource-article.png" '
389 + ' width="64" height="64" alt=""/>');
Scott Main5a1123e2012-09-26 12:51:28 -0700390 $pSummary.addClass('article');
391 }
392
393 $liClass.append($h2Title).append($imgIcon).append($pSummary).append($olLessons);
394 $olClasses.append($liClass);
395 });
396 $('.jd-descr').append($olClasses);
397 }
398
Scott Maine4d8f1b2012-06-21 18:03:05 -0700399 // Set up expand/collapse behavior
Scott Mainad08f072013-08-20 16:49:57 -0700400 initExpandableNavItems("#nav");
Scott Main3b90aff2013-08-01 18:09:35 -0700401
Scott Main3b90aff2013-08-01 18:09:35 -0700402
Scott Maine4d8f1b2012-06-21 18:03:05 -0700403 $(".scroll-pane").scroll(function(event) {
404 event.preventDefault();
405 return false;
406 });
407
408 /* Resize nav height when window height changes */
409 $(window).resize(function() {
410 if ($('#side-nav').length == 0) return;
411 var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
412 setNavBarLeftPos(); // do this even if sidenav isn't fixed because it could become fixed
413 // make sidenav behave when resizing the window and side-scolling is a concern
414 if (navBarIsFixed) {
415 if ((stylesheet.attr("disabled") == "disabled") || stylesheet.length == 0) {
416 updateSideNavPosition();
417 } else {
418 updateSidenavFullscreenWidth();
419 }
420 }
421 resizeNav();
422 });
423
424
Scott Maine4d8f1b2012-06-21 18:03:05 -0700425 var navBarLeftPos;
426 if ($('#devdoc-nav').length) {
427 setNavBarLeftPos();
428 }
429
430
Scott Maine4d8f1b2012-06-21 18:03:05 -0700431 // Set up play-on-hover <video> tags.
432 $('video.play-on-hover').bind('click', function(){
433 $(this).get(0).load(); // in case the video isn't seekable
434 $(this).get(0).play();
435 });
436
437 // Set up tooltips
438 var TOOLTIP_MARGIN = 10;
Scott Maindb3678b2012-10-23 14:13:41 -0700439 $('acronym,.tooltip-link').each(function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700440 var $target = $(this);
441 var $tooltip = $('<div>')
442 .addClass('tooltip-box')
Scott Maindb3678b2012-10-23 14:13:41 -0700443 .append($target.attr('title'))
Scott Maine4d8f1b2012-06-21 18:03:05 -0700444 .hide()
445 .appendTo('body');
446 $target.removeAttr('title');
447
448 $target.hover(function() {
449 // in
450 var targetRect = $target.offset();
451 targetRect.width = $target.width();
452 targetRect.height = $target.height();
453
454 $tooltip.css({
455 left: targetRect.left,
456 top: targetRect.top + targetRect.height + TOOLTIP_MARGIN
457 });
458 $tooltip.addClass('below');
459 $tooltip.show();
460 }, function() {
461 // out
462 $tooltip.hide();
463 });
464 });
465
466 // Set up <h2> deeplinks
467 $('h2').click(function() {
468 var id = $(this).attr('id');
469 if (id) {
470 document.location.hash = id;
471 }
472 });
473
474 //Loads the +1 button
475 var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
476 po.src = 'https://apis.google.com/js/plusone.js';
477 var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
478
479
Scott Main3b90aff2013-08-01 18:09:35 -0700480 // Revise the sidenav widths to make room for the scrollbar
Scott Maine4d8f1b2012-06-21 18:03:05 -0700481 // which avoids the visible width from changing each time the bar appears
482 var $sidenav = $("#side-nav");
483 var sidenav_width = parseInt($sidenav.innerWidth());
Scott Main3b90aff2013-08-01 18:09:35 -0700484
Scott Maine4d8f1b2012-06-21 18:03:05 -0700485 $("#devdoc-nav #nav").css("width", sidenav_width - 4 + "px"); // 4px is scrollbar width
486
487
488 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
Scott Main3b90aff2013-08-01 18:09:35 -0700489
Scott Maine4d8f1b2012-06-21 18:03:05 -0700490 if ($(".scroll-pane").length > 1) {
491 // Check if there's a user preference for the panel heights
492 var cookieHeight = readCookie("reference_height");
493 if (cookieHeight) {
494 restoreHeight(cookieHeight);
495 }
496 }
Scott Main3b90aff2013-08-01 18:09:35 -0700497
Scott Maine4d8f1b2012-06-21 18:03:05 -0700498 resizeNav();
499
Scott Main015d6162013-01-29 09:01:52 -0800500 /* init the language selector based on user cookie for lang */
501 loadLangPref();
502 changeNavLang(getLangPref());
503
504 /* setup event handlers to ensure the overflow menu is visible while picking lang */
505 $("#language select")
506 .mousedown(function() {
507 $("div.morehover").addClass("hover"); })
508 .blur(function() {
509 $("div.morehover").removeClass("hover"); });
510
511 /* some global variable setup */
512 resizePackagesNav = $("#resize-packages-nav");
513 classesNav = $("#classes-nav");
514 devdocNav = $("#devdoc-nav");
515
516 var cookiePath = "";
517 if (location.href.indexOf("/reference/") != -1) {
518 cookiePath = "reference_";
519 } else if (location.href.indexOf("/guide/") != -1) {
520 cookiePath = "guide_";
521 } else if (location.href.indexOf("/tools/") != -1) {
522 cookiePath = "tools_";
523 } else if (location.href.indexOf("/training/") != -1) {
524 cookiePath = "training_";
525 } else if (location.href.indexOf("/design/") != -1) {
526 cookiePath = "design_";
527 } else if (location.href.indexOf("/distribute/") != -1) {
528 cookiePath = "distribute_";
529 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700530
531});
Scott Main7e447ed2013-02-19 17:22:37 -0800532// END of the onload event
Scott Maine4d8f1b2012-06-21 18:03:05 -0700533
534
Scott Mainad08f072013-08-20 16:49:57 -0700535function initExpandableNavItems(rootTag) {
536 $(rootTag + ' li.nav-section .nav-section-header').click(function() {
537 var section = $(this).closest('li.nav-section');
538 if (section.hasClass('expanded')) {
Scott Mainf0093852013-08-22 11:37:11 -0700539 /* hide me and descendants */
540 section.find('ul').slideUp(250, function() {
541 // remove 'expanded' class from my section and any children
Scott Mainad08f072013-08-20 16:49:57 -0700542 section.closest('li').removeClass('expanded');
Scott Mainf0093852013-08-22 11:37:11 -0700543 $('li.nav-section', section).removeClass('expanded');
Scott Mainad08f072013-08-20 16:49:57 -0700544 resizeNav();
545 });
546 } else {
547 /* show me */
548 // first hide all other siblings
Scott Main70557ee2013-10-30 14:47:40 -0700549 var $others = $('li.nav-section.expanded', $(this).closest('ul')).not('.sticky');
Scott Mainad08f072013-08-20 16:49:57 -0700550 $others.removeClass('expanded').children('ul').slideUp(250);
551
552 // now expand me
553 section.closest('li').addClass('expanded');
554 section.children('ul').slideDown(250, function() {
555 resizeNav();
556 });
557 }
558 });
Scott Mainf0093852013-08-22 11:37:11 -0700559
560 // Stop expand/collapse behavior when clicking on nav section links
561 // (since we're navigating away from the page)
562 // This selector captures the first instance of <a>, but not those with "#" as the href.
563 $('.nav-section-header').find('a:eq(0)').not('a[href="#"]').click(function(evt) {
564 window.location.href = $(this).attr('href');
565 return false;
566 });
Scott Mainad08f072013-08-20 16:49:57 -0700567}
568
Dirk Doughertyc3921652014-05-13 16:55:26 -0700569
570/** Create the list of breadcrumb links in the sticky header */
571function buildBreadcrumbs() {
572 var $breadcrumbUl = $("#sticky-header ul.breadcrumb");
573 // Add the secondary horizontal nav item, if provided
574 var $selectedSecondNav = $("div#nav-x ul.nav-x a.selected").clone().removeClass("selected");
575 if ($selectedSecondNav.length) {
576 $breadcrumbUl.prepend($("<li>").append($selectedSecondNav))
577 }
578 // Add the primary horizontal nav
579 var $selectedFirstNav = $("div#header-wrap ul.nav-x a.selected").clone().removeClass("selected");
580 // If there's no header nav item, use the logo link and title from alt text
581 if ($selectedFirstNav.length < 1) {
582 $selectedFirstNav = $("<a>")
583 .attr('href', $("div#header .logo a").attr('href'))
584 .text($("div#header .logo img").attr('alt'));
585 }
586 $breadcrumbUl.prepend($("<li>").append($selectedFirstNav));
587}
588
589
590
Scott Maine624b3f2013-09-12 12:56:41 -0700591/** Highlight the current page in sidenav, expanding children as appropriate */
Scott Mainf6145542013-04-01 16:38:11 -0700592function highlightSidenav() {
Scott Maine624b3f2013-09-12 12:56:41 -0700593 // if something is already highlighted, undo it. This is for dynamic navigation (Samples index)
594 if ($("ul#nav li.selected").length) {
595 unHighlightSidenav();
596 }
597 // look for URL in sidenav, including the hash
598 var $selNavLink = $('#nav').find('a[href="' + mPagePath + location.hash + '"]');
599
600 // If the selNavLink is still empty, look for it without the hash
601 if ($selNavLink.length == 0) {
602 $selNavLink = $('#nav').find('a[href="' + mPagePath + '"]');
603 }
604
Scott Mainf6145542013-04-01 16:38:11 -0700605 var $selListItem;
606 if ($selNavLink.length) {
Scott Mainf6145542013-04-01 16:38:11 -0700607 // Find this page's <li> in sidenav and set selected
608 $selListItem = $selNavLink.closest('li');
609 $selListItem.addClass('selected');
Scott Main3b90aff2013-08-01 18:09:35 -0700610
Scott Mainf6145542013-04-01 16:38:11 -0700611 // Traverse up the tree and expand all parent nav-sections
612 $selNavLink.parents('li.nav-section').each(function() {
613 $(this).addClass('expanded');
614 $(this).children('ul').show();
615 });
616 }
617}
618
Scott Maine624b3f2013-09-12 12:56:41 -0700619function unHighlightSidenav() {
620 $("ul#nav li.selected").removeClass("selected");
621 $('ul#nav li.nav-section.expanded').removeClass('expanded').children('ul').hide();
622}
Scott Maine4d8f1b2012-06-21 18:03:05 -0700623
624function toggleFullscreen(enable) {
625 var delay = 20;
626 var enabled = true;
627 var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
628 if (enable) {
629 // Currently NOT USING fullscreen; enable fullscreen
630 stylesheet.removeAttr('disabled');
631 $('#nav-swap .fullscreen').removeClass('disabled');
632 $('#devdoc-nav').css({left:''});
633 setTimeout(updateSidenavFullscreenWidth,delay); // need to wait a moment for css to switch
634 enabled = true;
635 } else {
636 // Currently USING fullscreen; disable fullscreen
637 stylesheet.attr('disabled', 'disabled');
638 $('#nav-swap .fullscreen').addClass('disabled');
639 setTimeout(updateSidenavFixedWidth,delay); // need to wait a moment for css to switch
640 enabled = false;
641 }
642 writeCookie("fullscreen", enabled, null, null);
643 setNavBarLeftPos();
644 resizeNav(delay);
645 updateSideNavPosition();
646 setTimeout(initSidenavHeightResize,delay);
647}
648
649
650function setNavBarLeftPos() {
651 navBarLeftPos = $('#body-content').offset().left;
652}
653
654
655function updateSideNavPosition() {
656 var newLeft = $(window).scrollLeft() - navBarLeftPos;
657 $('#devdoc-nav').css({left: -newLeft});
658 $('#devdoc-nav .totop').css({left: -(newLeft - parseInt($('#side-nav').css('margin-left')))});
659}
Scott Main3b90aff2013-08-01 18:09:35 -0700660
Scott Maine4d8f1b2012-06-21 18:03:05 -0700661// TODO: use $(document).ready instead
662function addLoadEvent(newfun) {
663 var current = window.onload;
664 if (typeof window.onload != 'function') {
665 window.onload = newfun;
666 } else {
667 window.onload = function() {
668 current();
669 newfun();
670 }
671 }
672}
673
674var agent = navigator['userAgent'].toLowerCase();
675// If a mobile phone, set flag and do mobile setup
676if ((agent.indexOf("mobile") != -1) || // android, iphone, ipod
677 (agent.indexOf("blackberry") != -1) ||
678 (agent.indexOf("webos") != -1) ||
679 (agent.indexOf("mini") != -1)) { // opera mini browsers
680 isMobile = true;
681}
682
683
Scott Main498d7102013-08-21 15:47:38 -0700684$(document).ready(function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700685 $("pre:not(.no-pretty-print)").addClass("prettyprint");
686 prettyPrint();
Scott Main498d7102013-08-21 15:47:38 -0700687});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700688
Scott Maine4d8f1b2012-06-21 18:03:05 -0700689
690
691
692/* ######### RESIZE THE SIDENAV HEIGHT ########## */
693
694function resizeNav(delay) {
695 var $nav = $("#devdoc-nav");
696 var $window = $(window);
697 var navHeight;
Scott Main3b90aff2013-08-01 18:09:35 -0700698
Scott Maine4d8f1b2012-06-21 18:03:05 -0700699 // Get the height of entire window and the total header height.
700 // Then figure out based on scroll position whether the header is visible
701 var windowHeight = $window.height();
702 var scrollTop = $window.scrollTop();
Dirk Doughertyc3921652014-05-13 16:55:26 -0700703 var headerHeight = $('#header-wrapper').outerHeight();
704 var headerVisible = scrollTop < stickyTop;
Scott Main3b90aff2013-08-01 18:09:35 -0700705
706 // get the height of space between nav and top of window.
Scott Maine4d8f1b2012-06-21 18:03:05 -0700707 // Could be either margin or top position, depending on whether the nav is fixed.
Scott Main3b90aff2013-08-01 18:09:35 -0700708 var topMargin = (parseInt($nav.css('margin-top')) || parseInt($nav.css('top'))) + 1;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700709 // add 1 for the #side-nav bottom margin
Scott Main3b90aff2013-08-01 18:09:35 -0700710
Scott Maine4d8f1b2012-06-21 18:03:05 -0700711 // Depending on whether the header is visible, set the side nav's height.
712 if (headerVisible) {
713 // The sidenav height grows as the header goes off screen
Dirk Doughertyc3921652014-05-13 16:55:26 -0700714 navHeight = windowHeight - (headerHeight - scrollTop) - topMargin;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700715 } else {
716 // Once header is off screen, the nav height is almost full window height
717 navHeight = windowHeight - topMargin;
718 }
Scott Main3b90aff2013-08-01 18:09:35 -0700719
720
721
Scott Maine4d8f1b2012-06-21 18:03:05 -0700722 $scrollPanes = $(".scroll-pane");
723 if ($scrollPanes.length > 1) {
724 // subtract the height of the api level widget and nav swapper from the available nav height
725 navHeight -= ($('#api-nav-header').outerHeight(true) + $('#nav-swap').outerHeight(true));
Scott Main3b90aff2013-08-01 18:09:35 -0700726
Scott Maine4d8f1b2012-06-21 18:03:05 -0700727 $("#swapper").css({height:navHeight + "px"});
728 if ($("#nav-tree").is(":visible")) {
729 $("#nav-tree").css({height:navHeight});
730 }
Scott Main3b90aff2013-08-01 18:09:35 -0700731
732 var classesHeight = navHeight - parseInt($("#resize-packages-nav").css("height")) - 10 + "px";
Scott Maine4d8f1b2012-06-21 18:03:05 -0700733 //subtract 10px to account for drag bar
Scott Main3b90aff2013-08-01 18:09:35 -0700734
735 // if the window becomes small enough to make the class panel height 0,
Scott Maine4d8f1b2012-06-21 18:03:05 -0700736 // then the package panel should begin to shrink
737 if (parseInt(classesHeight) <= 0) {
738 $("#resize-packages-nav").css({height:navHeight - 10}); //subtract 10px for drag bar
739 $("#packages-nav").css({height:navHeight - 10});
740 }
Scott Main3b90aff2013-08-01 18:09:35 -0700741
Scott Maine4d8f1b2012-06-21 18:03:05 -0700742 $("#classes-nav").css({'height':classesHeight, 'margin-top':'10px'});
743 $("#classes-nav .jspContainer").css({height:classesHeight});
Scott Main3b90aff2013-08-01 18:09:35 -0700744
745
Scott Maine4d8f1b2012-06-21 18:03:05 -0700746 } else {
747 $nav.height(navHeight);
748 }
Scott Main3b90aff2013-08-01 18:09:35 -0700749
Scott Maine4d8f1b2012-06-21 18:03:05 -0700750 if (delay) {
751 updateFromResize = true;
752 delayedReInitScrollbars(delay);
753 } else {
754 reInitScrollbars();
755 }
Scott Main3b90aff2013-08-01 18:09:35 -0700756
Scott Maine4d8f1b2012-06-21 18:03:05 -0700757}
758
759var updateScrollbars = false;
760var updateFromResize = false;
761
762/* Re-initialize the scrollbars to account for changed nav size.
763 * This method postpones the actual update by a 1/4 second in order to optimize the
764 * scroll performance while the header is still visible, because re-initializing the
765 * scroll panes is an intensive process.
766 */
767function delayedReInitScrollbars(delay) {
768 // If we're scheduled for an update, but have received another resize request
769 // before the scheduled resize has occured, just ignore the new request
770 // (and wait for the scheduled one).
771 if (updateScrollbars && updateFromResize) {
772 updateFromResize = false;
773 return;
774 }
Scott Main3b90aff2013-08-01 18:09:35 -0700775
Scott Maine4d8f1b2012-06-21 18:03:05 -0700776 // We're scheduled for an update and the update request came from this method's setTimeout
777 if (updateScrollbars && !updateFromResize) {
778 reInitScrollbars();
779 updateScrollbars = false;
780 } else {
781 updateScrollbars = true;
782 updateFromResize = false;
783 setTimeout('delayedReInitScrollbars()',delay);
784 }
785}
786
787/* Re-initialize the scrollbars to account for changed nav size. */
788function reInitScrollbars() {
789 var pane = $(".scroll-pane").each(function(){
790 var api = $(this).data('jsp');
791 if (!api) { setTimeout(reInitScrollbars,300); return;}
792 api.reinitialise( {verticalGutter:0} );
Scott Main3b90aff2013-08-01 18:09:35 -0700793 });
Scott Maine4d8f1b2012-06-21 18:03:05 -0700794 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
795}
796
797
798/* Resize the height of the nav panels in the reference,
799 * and save the new size to a cookie */
800function saveNavPanels() {
801 var basePath = getBaseUri(location.pathname);
802 var section = basePath.substring(1,basePath.indexOf("/",1));
803 writeCookie("height", resizePackagesNav.css("height"), section, null);
804}
805
806
807
808function restoreHeight(packageHeight) {
809 $("#resize-packages-nav").height(packageHeight);
810 $("#packages-nav").height(packageHeight);
811 // var classesHeight = navHeight - packageHeight;
812 // $("#classes-nav").css({height:classesHeight});
813 // $("#classes-nav .jspContainer").css({height:classesHeight});
814}
815
816
817
818/* ######### END RESIZE THE SIDENAV HEIGHT ########## */
819
820
821
822
823
Scott Main3b90aff2013-08-01 18:09:35 -0700824/** Scroll the jScrollPane to make the currently selected item visible
Scott Maine4d8f1b2012-06-21 18:03:05 -0700825 This is called when the page finished loading. */
826function scrollIntoView(nav) {
827 var $nav = $("#"+nav);
828 var element = $nav.jScrollPane({/* ...settings... */});
829 var api = element.data('jsp');
830
831 if ($nav.is(':visible')) {
832 var $selected = $(".selected", $nav);
Scott Mainbc729572013-07-30 18:00:51 -0700833 if ($selected.length == 0) {
834 // If no selected item found, exit
835 return;
836 }
Scott Main52dd2062013-08-15 12:22:28 -0700837 // get the selected item's offset from its container nav by measuring the item's offset
838 // relative to the document then subtract the container nav's offset relative to the document
839 var selectedOffset = $selected.offset().top - $nav.offset().top;
840 if (selectedOffset > $nav.height() * .8) { // multiply nav height by .8 so we move up the item
841 // if it's more than 80% down the nav
842 // scroll the item up by an amount equal to 80% the container nav's height
843 api.scrollTo(0, selectedOffset - ($nav.height() * .8), false);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700844 }
845 }
846}
847
848
849
850
851
852
853/* Show popup dialogs */
854function showDialog(id) {
855 $dialog = $("#"+id);
856 $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>');
857 $dialog.wrapInner('<div/>');
858 $dialog.removeClass("hide");
859}
860
861
862
863
864
865/* ######### COOKIES! ########## */
866
867function readCookie(cookie) {
868 var myCookie = cookie_namespace+"_"+cookie+"=";
869 if (document.cookie) {
870 var index = document.cookie.indexOf(myCookie);
871 if (index != -1) {
872 var valStart = index + myCookie.length;
873 var valEnd = document.cookie.indexOf(";", valStart);
874 if (valEnd == -1) {
875 valEnd = document.cookie.length;
876 }
877 var val = document.cookie.substring(valStart, valEnd);
878 return val;
879 }
880 }
881 return 0;
882}
883
884function writeCookie(cookie, val, section, expiration) {
885 if (val==undefined) return;
886 section = section == null ? "_" : "_"+section+"_";
887 if (expiration == null) {
888 var date = new Date();
889 date.setTime(date.getTime()+(10*365*24*60*60*1000)); // default expiration is one week
890 expiration = date.toGMTString();
891 }
Scott Main3b90aff2013-08-01 18:09:35 -0700892 var cookieValue = cookie_namespace + section + cookie + "=" + val
Scott Maine4d8f1b2012-06-21 18:03:05 -0700893 + "; expires=" + expiration+"; path=/";
894 document.cookie = cookieValue;
895}
896
897/* ######### END COOKIES! ########## */
898
899
Dirk Doughertyca1230c2014-05-14 20:00:03 -0700900var sticky = false;
Dirk Doughertyc3921652014-05-13 16:55:26 -0700901var stickyTop;
Dirk Doughertyca1230c2014-05-14 20:00:03 -0700902var prevScrollLeft = 0; // used to compare current position to previous position of horiz scroll
Dirk Doughertyc3921652014-05-13 16:55:26 -0700903/* Sets the vertical scoll position at which the sticky bar should appear.
904 This method is called to reset the position when search results appear or hide */
905function setStickyTop() {
906 stickyTop = $('#header-wrapper').outerHeight() - $('#sticky-header').outerHeight();
907}
Scott Maine4d8f1b2012-06-21 18:03:05 -0700908
Dirk Doughertyca1230c2014-05-14 20:00:03 -0700909/*
Scott Mainb16376f2014-05-21 20:35:47 -0700910 * Displays sticky nav bar on pages when dac header scrolls out of view
Dirk Doughertyc3921652014-05-13 16:55:26 -0700911 */
Dirk Doughertyca1230c2014-05-14 20:00:03 -0700912$(window).scroll(function(event) {
913
914 setStickyTop();
915 var hiding = false;
916 var $stickyEl = $('#sticky-header');
917 var $menuEl = $('.menu-container');
918 // Exit if there's no sidenav
919 if ($('#side-nav').length == 0) return;
920 // Exit if the mouse target is a DIV, because that means the event is coming
921 // from a scrollable div and so there's no need to make adjustments to our layout
922 if ($(event.target).nodeName == "DIV") {
923 return;
924 }
925
926 var top = $(window).scrollTop();
927 // we set the navbar fixed when the scroll position is beyond the height of the site header...
928 var shouldBeSticky = top >= stickyTop;
929 // ... except if the document content is shorter than the sidenav height.
930 // (this is necessary to avoid crazy behavior on OSX Lion due to overscroll bouncing)
931 if ($("#doc-col").height() < $("#side-nav").height()) {
932 shouldBeSticky = false;
933 }
934
935 // Don't continue if the header is sufficently far away
936 // (to avoid intensive resizing that slows scrolling)
937 if (sticky == shouldBeSticky) {
938 return;
939 }
940 // Account for horizontal scroll
941 var scrollLeft = $(window).scrollLeft();
942 // When the sidenav is fixed and user scrolls horizontally, reposition the sidenav to match
943 if (navBarIsFixed && (scrollLeft != prevScrollLeft)) {
944 updateSideNavPosition();
945 prevScrollLeft = scrollLeft;
946 }
947
948 // If sticky header visible and position is now near top, hide sticky
949 if (sticky && !shouldBeSticky) {
950 sticky = false;
951 hiding = true;
952 // make the sidenav static again
953 $('#devdoc-nav')
954 .removeClass('fixed')
955 .css({'width':'auto','margin':''})
956 .prependTo('#side-nav');
957 // delay hide the sticky
958 $menuEl.removeClass('sticky-menu');
959 $stickyEl.fadeOut(250);
960 hiding = false;
961
962 // update the sidenaav position for side scrolling
963 updateSideNavPosition();
964 } else if (!sticky && shouldBeSticky) {
965 sticky = true;
966 $stickyEl.fadeIn(10);
967 $menuEl.addClass('sticky-menu');
968
969 // make the sidenav fixed
970 var width = $('#devdoc-nav').width();
971 $('#devdoc-nav')
972 .addClass('fixed')
973 .css({'width':width+'px'})
974 .prependTo('#body-content');
975
976 // update the sidenaav position for side scrolling
977 updateSideNavPosition();
978
979 } else if (hiding && top < 15) {
980 $menuEl.removeClass('sticky-menu');
981 $stickyEl.hide();
982 hiding = false;
983 }
984 resizeNav(250); // pass true in order to delay the scrollbar re-initialization for performance
985});
986
987/*
988 * Manages secion card states and nav resize to conclude loading
989 */
Dirk Doughertyc3921652014-05-13 16:55:26 -0700990(function() {
991 $(document).ready(function() {
992
Dirk Doughertyc3921652014-05-13 16:55:26 -0700993 // Stack hover states
994 $('.section-card-menu').each(function(index, el) {
995 var height = $(el).height();
996 $(el).css({height:height+'px', position:'relative'});
997 var $cardInfo = $(el).find('.card-info');
998
999 $cardInfo.css({position: 'absolute', bottom:'0px', left:'0px', right:'0px', overflow:'visible'});
1000 });
1001
Scott Mainb16376f2014-05-21 20:35:47 -07001002 // Resize once loading is finished
1003 resizeNav();
1004 // Check if there's an anchor that we need to scroll into view
1005 offsetScrollForSticky();
Dirk Doughertyc3921652014-05-13 16:55:26 -07001006 });
1007
1008})();
1009
Scott Maine4d8f1b2012-06-21 18:03:05 -07001010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
Scott Maind7026f72013-06-17 15:08:49 -07001023/* MISC LIBRARY FUNCTIONS */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001024
1025
1026
1027
1028
1029function toggle(obj, slide) {
1030 var ul = $("ul:first", obj);
1031 var li = ul.parent();
1032 if (li.hasClass("closed")) {
1033 if (slide) {
1034 ul.slideDown("fast");
1035 } else {
1036 ul.show();
1037 }
1038 li.removeClass("closed");
1039 li.addClass("open");
1040 $(".toggle-img", li).attr("title", "hide pages");
1041 } else {
1042 ul.slideUp("fast");
1043 li.removeClass("open");
1044 li.addClass("closed");
1045 $(".toggle-img", li).attr("title", "show pages");
1046 }
1047}
1048
1049
Scott Maine4d8f1b2012-06-21 18:03:05 -07001050function buildToggleLists() {
1051 $(".toggle-list").each(
1052 function(i) {
1053 $("div:first", this).append("<a class='toggle-img' href='#' title='show pages' onClick='toggle(this.parentNode.parentNode, true); return false;'></a>");
1054 $(this).addClass("closed");
1055 });
1056}
1057
1058
1059
Scott Maind7026f72013-06-17 15:08:49 -07001060function hideNestedItems(list, toggle) {
1061 $list = $(list);
1062 // hide nested lists
1063 if($list.hasClass('showing')) {
1064 $("li ol", $list).hide('fast');
1065 $list.removeClass('showing');
1066 // show nested lists
1067 } else {
1068 $("li ol", $list).show('fast');
1069 $list.addClass('showing');
1070 }
1071 $(".more,.less",$(toggle)).toggle();
1072}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101/* REFERENCE NAV SWAP */
1102
1103
1104function getNavPref() {
1105 var v = readCookie('reference_nav');
1106 if (v != NAV_PREF_TREE) {
1107 v = NAV_PREF_PANELS;
1108 }
1109 return v;
1110}
1111
1112function chooseDefaultNav() {
1113 nav_pref = getNavPref();
1114 if (nav_pref == NAV_PREF_TREE) {
1115 $("#nav-panels").toggle();
1116 $("#panel-link").toggle();
1117 $("#nav-tree").toggle();
1118 $("#tree-link").toggle();
1119 }
1120}
1121
1122function swapNav() {
1123 if (nav_pref == NAV_PREF_TREE) {
1124 nav_pref = NAV_PREF_PANELS;
1125 } else {
1126 nav_pref = NAV_PREF_TREE;
1127 init_default_navtree(toRoot);
1128 }
1129 var date = new Date();
1130 date.setTime(date.getTime()+(10*365*24*60*60*1000)); // keep this for 10 years
1131 writeCookie("nav", nav_pref, "reference", date.toGMTString());
1132
1133 $("#nav-panels").toggle();
1134 $("#panel-link").toggle();
1135 $("#nav-tree").toggle();
1136 $("#tree-link").toggle();
Scott Main3b90aff2013-08-01 18:09:35 -07001137
Scott Maine4d8f1b2012-06-21 18:03:05 -07001138 resizeNav();
1139
1140 // Gross nasty hack to make tree view show up upon first swap by setting height manually
1141 $("#nav-tree .jspContainer:visible")
1142 .css({'height':$("#nav-tree .jspContainer .jspPane").height() +'px'});
1143 // Another nasty hack to make the scrollbar appear now that we have height
1144 resizeNav();
Scott Main3b90aff2013-08-01 18:09:35 -07001145
Scott Maine4d8f1b2012-06-21 18:03:05 -07001146 if ($("#nav-tree").is(':visible')) {
1147 scrollIntoView("nav-tree");
1148 } else {
1149 scrollIntoView("packages-nav");
1150 scrollIntoView("classes-nav");
1151 }
1152}
1153
1154
1155
Scott Mainf5089842012-08-14 16:31:07 -07001156/* ############################################ */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001157/* ########## LOCALIZATION ############ */
Scott Mainf5089842012-08-14 16:31:07 -07001158/* ############################################ */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001159
1160function getBaseUri(uri) {
1161 var intlUrl = (uri.substring(0,6) == "/intl/");
1162 if (intlUrl) {
1163 base = uri.substring(uri.indexOf('intl/')+5,uri.length);
1164 base = base.substring(base.indexOf('/')+1, base.length);
1165 //alert("intl, returning base url: /" + base);
1166 return ("/" + base);
1167 } else {
1168 //alert("not intl, returning uri as found.");
1169 return uri;
1170 }
1171}
1172
1173function requestAppendHL(uri) {
1174//append "?hl=<lang> to an outgoing request (such as to blog)
1175 var lang = getLangPref();
1176 if (lang) {
1177 var q = 'hl=' + lang;
1178 uri += '?' + q;
1179 window.location = uri;
1180 return false;
1181 } else {
1182 return true;
1183 }
1184}
1185
1186
Scott Maine4d8f1b2012-06-21 18:03:05 -07001187function changeNavLang(lang) {
Scott Main6eb95f12012-10-02 17:12:23 -07001188 var $links = $("#devdoc-nav,#header,#nav-x,.training-nav-top,.content-footer").find("a["+lang+"-lang]");
1189 $links.each(function(i){ // for each link with a translation
1190 var $link = $(this);
1191 if (lang != "en") { // No need to worry about English, because a language change invokes new request
1192 // put the desired language from the attribute as the text
1193 $link.text($link.attr(lang+"-lang"))
Scott Maine4d8f1b2012-06-21 18:03:05 -07001194 }
Scott Main6eb95f12012-10-02 17:12:23 -07001195 });
Scott Maine4d8f1b2012-06-21 18:03:05 -07001196}
1197
Scott Main015d6162013-01-29 09:01:52 -08001198function changeLangPref(lang, submit) {
Scott Maine4d8f1b2012-06-21 18:03:05 -07001199 var date = new Date();
Scott Main3b90aff2013-08-01 18:09:35 -07001200 expires = date.toGMTString(date.setTime(date.getTime()+(10*365*24*60*60*1000)));
Scott Maine4d8f1b2012-06-21 18:03:05 -07001201 // keep this for 50 years
1202 //alert("expires: " + expires)
1203 writeCookie("pref_lang", lang, null, expires);
Scott Main015d6162013-01-29 09:01:52 -08001204
1205 // ####### TODO: Remove this condition once we're stable on devsite #######
1206 // This condition is only needed if we still need to support legacy GAE server
1207 if (devsite) {
1208 // Switch language when on Devsite server
1209 if (submit) {
1210 $("#setlang").submit();
1211 }
1212 } else {
1213 // Switch language when on legacy GAE server
Scott Main015d6162013-01-29 09:01:52 -08001214 if (submit) {
1215 window.location = getBaseUri(location.pathname);
1216 }
Scott Maine4d8f1b2012-06-21 18:03:05 -07001217 }
1218}
1219
1220function loadLangPref() {
1221 var lang = readCookie("pref_lang");
1222 if (lang != 0) {
1223 $("#language").find("option[value='"+lang+"']").attr("selected",true);
1224 }
1225}
1226
1227function getLangPref() {
1228 var lang = $("#language").find(":selected").attr("value");
1229 if (!lang) {
1230 lang = readCookie("pref_lang");
1231 }
1232 return (lang != 0) ? lang : 'en';
1233}
1234
1235/* ########## END LOCALIZATION ############ */
1236
1237
1238
1239
1240
1241
1242/* Used to hide and reveal supplemental content, such as long code samples.
1243 See the companion CSS in android-developer-docs.css */
1244function toggleContent(obj) {
Scott Maindc63dda2013-08-22 16:03:21 -07001245 var div = $(obj).closest(".toggle-content");
1246 var toggleMe = $(".toggle-content-toggleme:eq(0)",div);
Scott Maine4d8f1b2012-06-21 18:03:05 -07001247 if (div.hasClass("closed")) { // if it's closed, open it
1248 toggleMe.slideDown();
Scott Maindc63dda2013-08-22 16:03:21 -07001249 $(".toggle-content-text:eq(0)", obj).toggle();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001250 div.removeClass("closed").addClass("open");
Scott Maindc63dda2013-08-22 16:03:21 -07001251 $(".toggle-content-img:eq(0)", div).attr("title", "hide").attr("src", toRoot
Scott Maine4d8f1b2012-06-21 18:03:05 -07001252 + "assets/images/triangle-opened.png");
1253 } else { // if it's open, close it
1254 toggleMe.slideUp('fast', function() { // Wait until the animation is done before closing arrow
Scott Maindc63dda2013-08-22 16:03:21 -07001255 $(".toggle-content-text:eq(0)", obj).toggle();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001256 div.removeClass("open").addClass("closed");
Scott Maindc63dda2013-08-22 16:03:21 -07001257 div.find(".toggle-content").removeClass("open").addClass("closed")
1258 .find(".toggle-content-toggleme").hide();
Scott Main3b90aff2013-08-01 18:09:35 -07001259 $(".toggle-content-img", div).attr("title", "show").attr("src", toRoot
Scott Maine4d8f1b2012-06-21 18:03:05 -07001260 + "assets/images/triangle-closed.png");
1261 });
1262 }
1263 return false;
1264}
Scott Mainf5089842012-08-14 16:31:07 -07001265
1266
Scott Maindb3678b2012-10-23 14:13:41 -07001267/* New version of expandable content */
1268function toggleExpandable(link,id) {
1269 if($(id).is(':visible')) {
1270 $(id).slideUp();
1271 $(link).removeClass('expanded');
1272 } else {
1273 $(id).slideDown();
1274 $(link).addClass('expanded');
1275 }
1276}
1277
1278function hideExpandable(ids) {
1279 $(ids).slideUp();
Scott Main55d99832012-11-12 23:03:59 -08001280 $(ids).prev('h4').find('a.expandable').removeClass('expanded');
Scott Maindb3678b2012-10-23 14:13:41 -07001281}
1282
Scott Mainf5089842012-08-14 16:31:07 -07001283
1284
1285
1286
Scott Main3b90aff2013-08-01 18:09:35 -07001287/*
Scott Mainf5089842012-08-14 16:31:07 -07001288 * Slideshow 1.0
1289 * Used on /index.html and /develop/index.html for carousel
1290 *
1291 * Sample usage:
1292 * HTML -
1293 * <div class="slideshow-container">
1294 * <a href="" class="slideshow-prev">Prev</a>
1295 * <a href="" class="slideshow-next">Next</a>
1296 * <ul>
1297 * <li class="item"><img src="images/marquee1.jpg"></li>
1298 * <li class="item"><img src="images/marquee2.jpg"></li>
1299 * <li class="item"><img src="images/marquee3.jpg"></li>
1300 * <li class="item"><img src="images/marquee4.jpg"></li>
1301 * </ul>
1302 * </div>
1303 *
1304 * <script type="text/javascript">
1305 * $('.slideshow-container').dacSlideshow({
1306 * auto: true,
1307 * btnPrev: '.slideshow-prev',
1308 * btnNext: '.slideshow-next'
1309 * });
1310 * </script>
1311 *
1312 * Options:
1313 * btnPrev: optional identifier for previous button
1314 * btnNext: optional identifier for next button
Scott Maineb410352013-01-14 19:03:40 -08001315 * btnPause: optional identifier for pause button
Scott Mainf5089842012-08-14 16:31:07 -07001316 * auto: whether or not to auto-proceed
1317 * speed: animation speed
1318 * autoTime: time between auto-rotation
1319 * easing: easing function for transition
1320 * start: item to select by default
1321 * scroll: direction to scroll in
1322 * pagination: whether or not to include dotted pagination
1323 *
1324 */
1325
1326 (function($) {
1327 $.fn.dacSlideshow = function(o) {
Scott Main3b90aff2013-08-01 18:09:35 -07001328
Scott Mainf5089842012-08-14 16:31:07 -07001329 //Options - see above
1330 o = $.extend({
1331 btnPrev: null,
1332 btnNext: null,
Scott Maineb410352013-01-14 19:03:40 -08001333 btnPause: null,
Scott Mainf5089842012-08-14 16:31:07 -07001334 auto: true,
1335 speed: 500,
1336 autoTime: 12000,
1337 easing: null,
1338 start: 0,
1339 scroll: 1,
1340 pagination: true
1341
1342 }, o || {});
Scott Main3b90aff2013-08-01 18:09:35 -07001343
1344 //Set up a carousel for each
Scott Mainf5089842012-08-14 16:31:07 -07001345 return this.each(function() {
1346
1347 var running = false;
1348 var animCss = o.vertical ? "top" : "left";
1349 var sizeCss = o.vertical ? "height" : "width";
1350 var div = $(this);
1351 var ul = $("ul", div);
1352 var tLi = $("li", ul);
Scott Main3b90aff2013-08-01 18:09:35 -07001353 var tl = tLi.size();
Scott Mainf5089842012-08-14 16:31:07 -07001354 var timer = null;
1355
1356 var li = $("li", ul);
1357 var itemLength = li.size();
1358 var curr = o.start;
1359
1360 li.css({float: o.vertical ? "none" : "left"});
1361 ul.css({margin: "0", padding: "0", position: "relative", "list-style-type": "none", "z-index": "1"});
1362 div.css({position: "relative", "z-index": "2", left: "0px"});
1363
1364 var liSize = o.vertical ? height(li) : width(li);
1365 var ulSize = liSize * itemLength;
1366 var divSize = liSize;
1367
1368 li.css({width: li.width(), height: li.height()});
1369 ul.css(sizeCss, ulSize+"px").css(animCss, -(curr*liSize));
1370
1371 div.css(sizeCss, divSize+"px");
Scott Main3b90aff2013-08-01 18:09:35 -07001372
Scott Mainf5089842012-08-14 16:31:07 -07001373 //Pagination
1374 if (o.pagination) {
1375 var pagination = $("<div class='pagination'></div>");
1376 var pag_ul = $("<ul></ul>");
1377 if (tl > 1) {
1378 for (var i=0;i<tl;i++) {
1379 var li = $("<li>"+i+"</li>");
1380 pag_ul.append(li);
1381 if (i==o.start) li.addClass('active');
1382 li.click(function() {
1383 go(parseInt($(this).text()));
1384 })
1385 }
1386 pagination.append(pag_ul);
1387 div.append(pagination);
1388 }
1389 }
Scott Main3b90aff2013-08-01 18:09:35 -07001390
Scott Mainf5089842012-08-14 16:31:07 -07001391 //Previous button
1392 if(o.btnPrev)
1393 $(o.btnPrev).click(function(e) {
1394 e.preventDefault();
1395 return go(curr-o.scroll);
1396 });
1397
1398 //Next button
1399 if(o.btnNext)
1400 $(o.btnNext).click(function(e) {
1401 e.preventDefault();
1402 return go(curr+o.scroll);
1403 });
Scott Maineb410352013-01-14 19:03:40 -08001404
1405 //Pause button
1406 if(o.btnPause)
1407 $(o.btnPause).click(function(e) {
1408 e.preventDefault();
1409 if ($(this).hasClass('paused')) {
1410 startRotateTimer();
1411 } else {
1412 pauseRotateTimer();
1413 }
1414 });
Scott Main3b90aff2013-08-01 18:09:35 -07001415
Scott Mainf5089842012-08-14 16:31:07 -07001416 //Auto rotation
1417 if(o.auto) startRotateTimer();
Scott Main3b90aff2013-08-01 18:09:35 -07001418
Scott Mainf5089842012-08-14 16:31:07 -07001419 function startRotateTimer() {
1420 clearInterval(timer);
1421 timer = setInterval(function() {
1422 if (curr == tl-1) {
1423 go(0);
1424 } else {
Scott Main3b90aff2013-08-01 18:09:35 -07001425 go(curr+o.scroll);
1426 }
Scott Mainf5089842012-08-14 16:31:07 -07001427 }, o.autoTime);
Scott Maineb410352013-01-14 19:03:40 -08001428 $(o.btnPause).removeClass('paused');
1429 }
1430
1431 function pauseRotateTimer() {
1432 clearInterval(timer);
1433 $(o.btnPause).addClass('paused');
Scott Mainf5089842012-08-14 16:31:07 -07001434 }
1435
1436 //Go to an item
1437 function go(to) {
1438 if(!running) {
1439
1440 if(to<0) {
1441 to = itemLength-1;
1442 } else if (to>itemLength-1) {
1443 to = 0;
1444 }
1445 curr = to;
1446
1447 running = true;
1448
1449 ul.animate(
1450 animCss == "left" ? { left: -(curr*liSize) } : { top: -(curr*liSize) } , o.speed, o.easing,
1451 function() {
1452 running = false;
1453 }
1454 );
1455
1456 $(o.btnPrev + "," + o.btnNext).removeClass("disabled");
1457 $( (curr-o.scroll<0 && o.btnPrev)
1458 ||
1459 (curr+o.scroll > itemLength && o.btnNext)
1460 ||
1461 []
1462 ).addClass("disabled");
1463
Scott Main3b90aff2013-08-01 18:09:35 -07001464
Scott Mainf5089842012-08-14 16:31:07 -07001465 var nav_items = $('li', pagination);
1466 nav_items.removeClass('active');
1467 nav_items.eq(to).addClass('active');
Scott Main3b90aff2013-08-01 18:09:35 -07001468
Scott Mainf5089842012-08-14 16:31:07 -07001469
1470 }
1471 if(o.auto) startRotateTimer();
1472 return false;
1473 };
1474 });
1475 };
1476
1477 function css(el, prop) {
1478 return parseInt($.css(el[0], prop)) || 0;
1479 };
1480 function width(el) {
1481 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1482 };
1483 function height(el) {
1484 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1485 };
1486
1487 })(jQuery);
1488
1489
Scott Main3b90aff2013-08-01 18:09:35 -07001490/*
Scott Mainf5089842012-08-14 16:31:07 -07001491 * dacSlideshow 1.0
1492 * Used on develop/index.html for side-sliding tabs
1493 *
1494 * Sample usage:
1495 * HTML -
1496 * <div class="slideshow-container">
1497 * <a href="" class="slideshow-prev">Prev</a>
1498 * <a href="" class="slideshow-next">Next</a>
1499 * <ul>
1500 * <li class="item"><img src="images/marquee1.jpg"></li>
1501 * <li class="item"><img src="images/marquee2.jpg"></li>
1502 * <li class="item"><img src="images/marquee3.jpg"></li>
1503 * <li class="item"><img src="images/marquee4.jpg"></li>
1504 * </ul>
1505 * </div>
1506 *
1507 * <script type="text/javascript">
1508 * $('.slideshow-container').dacSlideshow({
1509 * auto: true,
1510 * btnPrev: '.slideshow-prev',
1511 * btnNext: '.slideshow-next'
1512 * });
1513 * </script>
1514 *
1515 * Options:
1516 * btnPrev: optional identifier for previous button
1517 * btnNext: optional identifier for next button
1518 * auto: whether or not to auto-proceed
1519 * speed: animation speed
1520 * autoTime: time between auto-rotation
1521 * easing: easing function for transition
1522 * start: item to select by default
1523 * scroll: direction to scroll in
1524 * pagination: whether or not to include dotted pagination
1525 *
1526 */
1527 (function($) {
1528 $.fn.dacTabbedList = function(o) {
Scott Main3b90aff2013-08-01 18:09:35 -07001529
Scott Mainf5089842012-08-14 16:31:07 -07001530 //Options - see above
1531 o = $.extend({
1532 speed : 250,
1533 easing: null,
1534 nav_id: null,
1535 frame_id: null
1536 }, o || {});
Scott Main3b90aff2013-08-01 18:09:35 -07001537
1538 //Set up a carousel for each
Scott Mainf5089842012-08-14 16:31:07 -07001539 return this.each(function() {
1540
1541 var curr = 0;
1542 var running = false;
1543 var animCss = "margin-left";
1544 var sizeCss = "width";
1545 var div = $(this);
Scott Main3b90aff2013-08-01 18:09:35 -07001546
Scott Mainf5089842012-08-14 16:31:07 -07001547 var nav = $(o.nav_id, div);
1548 var nav_li = $("li", nav);
Scott Main3b90aff2013-08-01 18:09:35 -07001549 var nav_size = nav_li.size();
Scott Mainf5089842012-08-14 16:31:07 -07001550 var frame = div.find(o.frame_id);
1551 var content_width = $(frame).find('ul').width();
1552 //Buttons
1553 $(nav_li).click(function(e) {
1554 go($(nav_li).index($(this)));
1555 })
Scott Main3b90aff2013-08-01 18:09:35 -07001556
Scott Mainf5089842012-08-14 16:31:07 -07001557 //Go to an item
1558 function go(to) {
1559 if(!running) {
1560 curr = to;
1561 running = true;
1562
1563 frame.animate({ 'margin-left' : -(curr*content_width) }, o.speed, o.easing,
1564 function() {
1565 running = false;
1566 }
1567 );
1568
Scott Main3b90aff2013-08-01 18:09:35 -07001569
Scott Mainf5089842012-08-14 16:31:07 -07001570 nav_li.removeClass('active');
1571 nav_li.eq(to).addClass('active');
Scott Main3b90aff2013-08-01 18:09:35 -07001572
Scott Mainf5089842012-08-14 16:31:07 -07001573
1574 }
1575 return false;
1576 };
1577 });
1578 };
1579
1580 function css(el, prop) {
1581 return parseInt($.css(el[0], prop)) || 0;
1582 };
1583 function width(el) {
1584 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1585 };
1586 function height(el) {
1587 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1588 };
1589
1590 })(jQuery);
1591
1592
1593
1594
1595
1596/* ######################################################## */
1597/* ################ SEARCH SUGGESTIONS ################## */
1598/* ######################################################## */
1599
1600
Scott Main7e447ed2013-02-19 17:22:37 -08001601
Scott Main0e76e7e2013-03-12 10:24:07 -07001602var gSelectedIndex = -1; // the index position of currently highlighted suggestion
1603var gSelectedColumn = -1; // which column of suggestion lists is currently focused
1604
Scott Mainf5089842012-08-14 16:31:07 -07001605var gMatches = new Array();
1606var gLastText = "";
Scott Mainf5089842012-08-14 16:31:07 -07001607var gInitialized = false;
Scott Main7e447ed2013-02-19 17:22:37 -08001608var ROW_COUNT_FRAMEWORK = 20; // max number of results in list
1609var gListLength = 0;
1610
1611
1612var gGoogleMatches = new Array();
1613var ROW_COUNT_GOOGLE = 15; // max number of results in list
1614var gGoogleListLength = 0;
Scott Mainf5089842012-08-14 16:31:07 -07001615
Scott Main0e76e7e2013-03-12 10:24:07 -07001616var gDocsMatches = new Array();
1617var ROW_COUNT_DOCS = 100; // max number of results in list
1618var gDocsListLength = 0;
1619
Scott Mainde295272013-03-25 15:48:35 -07001620function onSuggestionClick(link) {
1621 // When user clicks a suggested document, track it
1622 _gaq.push(['_trackEvent', 'Suggestion Click', 'clicked: ' + $(link).text(),
1623 'from: ' + $("#search_autocomplete").val()]);
1624}
1625
Scott Mainf5089842012-08-14 16:31:07 -07001626function set_item_selected($li, selected)
1627{
1628 if (selected) {
1629 $li.attr('class','jd-autocomplete jd-selected');
1630 } else {
1631 $li.attr('class','jd-autocomplete');
1632 }
1633}
1634
1635function set_item_values(toroot, $li, match)
1636{
1637 var $link = $('a',$li);
1638 $link.html(match.__hilabel || match.label);
1639 $link.attr('href',toroot + match.link);
1640}
1641
Scott Main719acb42013-12-05 16:05:09 -08001642function set_item_values_jd(toroot, $li, match)
1643{
1644 var $link = $('a',$li);
1645 $link.html(match.title);
1646 $link.attr('href',toroot + match.url);
1647}
1648
Scott Main0e76e7e2013-03-12 10:24:07 -07001649function new_suggestion($list) {
Scott Main7e447ed2013-02-19 17:22:37 -08001650 var $li = $("<li class='jd-autocomplete'></li>");
1651 $list.append($li);
1652
1653 $li.mousedown(function() {
1654 window.location = this.firstChild.getAttribute("href");
1655 });
1656 $li.mouseover(function() {
Scott Main0e76e7e2013-03-12 10:24:07 -07001657 $('.search_filtered_wrapper li').removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001658 $(this).addClass('jd-selected');
Scott Main0e76e7e2013-03-12 10:24:07 -07001659 gSelectedColumn = $(".search_filtered:visible").index($(this).closest('.search_filtered'));
1660 gSelectedIndex = $("li", $(".search_filtered:visible")[gSelectedColumn]).index(this);
Scott Main7e447ed2013-02-19 17:22:37 -08001661 });
Scott Mainde295272013-03-25 15:48:35 -07001662 $li.append("<a onclick='onSuggestionClick(this)'></a>");
Scott Main7e447ed2013-02-19 17:22:37 -08001663 $li.attr('class','show-item');
1664 return $li;
1665}
1666
Scott Mainf5089842012-08-14 16:31:07 -07001667function sync_selection_table(toroot)
1668{
Scott Mainf5089842012-08-14 16:31:07 -07001669 var $li; //list item jquery object
1670 var i; //list item iterator
Scott Main7e447ed2013-02-19 17:22:37 -08001671
Scott Main0e76e7e2013-03-12 10:24:07 -07001672 // if there are NO results at all, hide all columns
1673 if (!(gMatches.length > 0) && !(gGoogleMatches.length > 0) && !(gDocsMatches.length > 0)) {
1674 $('.suggest-card').hide(300);
1675 return;
1676 }
1677
1678 // if there are api results
Scott Main7e447ed2013-02-19 17:22:37 -08001679 if ((gMatches.length > 0) || (gGoogleMatches.length > 0)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001680 // reveal suggestion list
1681 $('.suggest-card.dummy').show();
1682 $('.suggest-card.reference').show();
1683 var listIndex = 0; // list index position
Scott Main7e447ed2013-02-19 17:22:37 -08001684
Scott Main0e76e7e2013-03-12 10:24:07 -07001685 // reset the lists
1686 $(".search_filtered_wrapper.reference li").remove();
Scott Main7e447ed2013-02-19 17:22:37 -08001687
Scott Main0e76e7e2013-03-12 10:24:07 -07001688 // ########### ANDROID RESULTS #############
1689 if (gMatches.length > 0) {
Scott Main7e447ed2013-02-19 17:22:37 -08001690
Scott Main0e76e7e2013-03-12 10:24:07 -07001691 // determine android results to show
1692 gListLength = gMatches.length < ROW_COUNT_FRAMEWORK ?
1693 gMatches.length : ROW_COUNT_FRAMEWORK;
1694 for (i=0; i<gListLength; i++) {
1695 var $li = new_suggestion($(".suggest-card.reference ul"));
1696 set_item_values(toroot, $li, gMatches[i]);
1697 set_item_selected($li, i == gSelectedIndex);
1698 }
1699 }
Scott Main7e447ed2013-02-19 17:22:37 -08001700
Scott Main0e76e7e2013-03-12 10:24:07 -07001701 // ########### GOOGLE RESULTS #############
1702 if (gGoogleMatches.length > 0) {
1703 // show header for list
1704 $(".suggest-card.reference ul").append("<li class='header'>in Google Services:</li>");
Scott Main7e447ed2013-02-19 17:22:37 -08001705
Scott Main0e76e7e2013-03-12 10:24:07 -07001706 // determine google results to show
1707 gGoogleListLength = gGoogleMatches.length < ROW_COUNT_GOOGLE ? gGoogleMatches.length : ROW_COUNT_GOOGLE;
1708 for (i=0; i<gGoogleListLength; i++) {
1709 var $li = new_suggestion($(".suggest-card.reference ul"));
1710 set_item_values(toroot, $li, gGoogleMatches[i]);
1711 set_item_selected($li, i == gSelectedIndex);
1712 }
1713 }
Scott Mainf5089842012-08-14 16:31:07 -07001714 } else {
Scott Main0e76e7e2013-03-12 10:24:07 -07001715 $('.suggest-card.reference').hide();
1716 $('.suggest-card.dummy').hide();
1717 }
1718
1719 // ########### JD DOC RESULTS #############
1720 if (gDocsMatches.length > 0) {
1721 // reset the lists
1722 $(".search_filtered_wrapper.docs li").remove();
1723
1724 // determine google results to show
Scott Main719acb42013-12-05 16:05:09 -08001725 // NOTE: The order of the conditions below for the sugg.type MUST BE SPECIFIC:
1726 // The order must match the reverse order that each section appears as a card in
1727 // the suggestion UI... this may be only for the "develop" grouped items though.
Scott Main0e76e7e2013-03-12 10:24:07 -07001728 gDocsListLength = gDocsMatches.length < ROW_COUNT_DOCS ? gDocsMatches.length : ROW_COUNT_DOCS;
1729 for (i=0; i<gDocsListLength; i++) {
1730 var sugg = gDocsMatches[i];
1731 var $li;
1732 if (sugg.type == "design") {
1733 $li = new_suggestion($(".suggest-card.design ul"));
1734 } else
1735 if (sugg.type == "distribute") {
1736 $li = new_suggestion($(".suggest-card.distribute ul"));
1737 } else
Scott Main719acb42013-12-05 16:05:09 -08001738 if (sugg.type == "samples") {
1739 $li = new_suggestion($(".suggest-card.develop .child-card.samples"));
1740 } else
Scott Main0e76e7e2013-03-12 10:24:07 -07001741 if (sugg.type == "training") {
1742 $li = new_suggestion($(".suggest-card.develop .child-card.training"));
1743 } else
Scott Main719acb42013-12-05 16:05:09 -08001744 if (sugg.type == "about"||"guide"||"tools"||"google") {
Scott Main0e76e7e2013-03-12 10:24:07 -07001745 $li = new_suggestion($(".suggest-card.develop .child-card.guides"));
1746 } else {
1747 continue;
1748 }
1749
Scott Main719acb42013-12-05 16:05:09 -08001750 set_item_values_jd(toroot, $li, sugg);
Scott Main0e76e7e2013-03-12 10:24:07 -07001751 set_item_selected($li, i == gSelectedIndex);
1752 }
1753
1754 // add heading and show or hide card
1755 if ($(".suggest-card.design li").length > 0) {
1756 $(".suggest-card.design ul").prepend("<li class='header'>Design:</li>");
1757 $(".suggest-card.design").show(300);
1758 } else {
1759 $('.suggest-card.design').hide(300);
1760 }
1761 if ($(".suggest-card.distribute li").length > 0) {
1762 $(".suggest-card.distribute ul").prepend("<li class='header'>Distribute:</li>");
1763 $(".suggest-card.distribute").show(300);
1764 } else {
1765 $('.suggest-card.distribute').hide(300);
1766 }
1767 if ($(".child-card.guides li").length > 0) {
1768 $(".child-card.guides").prepend("<li class='header'>Guides:</li>");
1769 $(".child-card.guides li").appendTo(".suggest-card.develop ul");
1770 }
1771 if ($(".child-card.training li").length > 0) {
1772 $(".child-card.training").prepend("<li class='header'>Training:</li>");
1773 $(".child-card.training li").appendTo(".suggest-card.develop ul");
1774 }
Scott Main719acb42013-12-05 16:05:09 -08001775 if ($(".child-card.samples li").length > 0) {
1776 $(".child-card.samples").prepend("<li class='header'>Samples:</li>");
1777 $(".child-card.samples li").appendTo(".suggest-card.develop ul");
1778 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001779
1780 if ($(".suggest-card.develop li").length > 0) {
1781 $(".suggest-card.develop").show(300);
1782 } else {
1783 $('.suggest-card.develop').hide(300);
1784 }
1785
1786 } else {
1787 $('.search_filtered_wrapper.docs .suggest-card:not(.dummy)').hide(300);
Scott Mainf5089842012-08-14 16:31:07 -07001788 }
1789}
1790
Scott Main0e76e7e2013-03-12 10:24:07 -07001791/** Called by the search input's onkeydown and onkeyup events.
1792 * Handles navigation with keyboard arrows, Enter key to invoke search,
1793 * otherwise invokes search suggestions on key-up event.
1794 * @param e The JS event
1795 * @param kd True if the event is key-down
Scott Main3b90aff2013-08-01 18:09:35 -07001796 * @param toroot A string for the site's root path
Scott Main0e76e7e2013-03-12 10:24:07 -07001797 * @returns True if the event should bubble up
1798 */
Scott Mainf5089842012-08-14 16:31:07 -07001799function search_changed(e, kd, toroot)
1800{
Scott Main719acb42013-12-05 16:05:09 -08001801 var currentLang = getLangPref();
Scott Mainf5089842012-08-14 16:31:07 -07001802 var search = document.getElementById("search_autocomplete");
1803 var text = search.value.replace(/(^ +)|( +$)/g, '');
Scott Main0e76e7e2013-03-12 10:24:07 -07001804 // get the ul hosting the currently selected item
1805 gSelectedColumn = gSelectedColumn >= 0 ? gSelectedColumn : 0;
1806 var $columns = $(".search_filtered_wrapper").find(".search_filtered:visible");
1807 var $selectedUl = $columns[gSelectedColumn];
1808
Scott Mainf5089842012-08-14 16:31:07 -07001809 // show/hide the close button
1810 if (text != '') {
1811 $(".search .close").removeClass("hide");
1812 } else {
1813 $(".search .close").addClass("hide");
1814 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001815 // 27 = esc
1816 if (e.keyCode == 27) {
1817 // close all search results
1818 if (kd) $('.search .close').trigger('click');
1819 return true;
1820 }
Scott Mainf5089842012-08-14 16:31:07 -07001821 // 13 = enter
Scott Main0e76e7e2013-03-12 10:24:07 -07001822 else if (e.keyCode == 13) {
1823 if (gSelectedIndex < 0) {
1824 $('.suggest-card').hide();
Scott Main7e447ed2013-02-19 17:22:37 -08001825 if ($("#searchResults").is(":hidden") && (search.value != "")) {
1826 // if results aren't showing (and text not empty), return true to allow search to execute
Dirk Doughertyc3921652014-05-13 16:55:26 -07001827 $('body,html').animate({scrollTop:0}, '500', 'swing');
Scott Mainf5089842012-08-14 16:31:07 -07001828 return true;
1829 } else {
1830 // otherwise, results are already showing, so allow ajax to auto refresh the results
1831 // and ignore this Enter press to avoid the reload.
1832 return false;
1833 }
1834 } else if (kd && gSelectedIndex >= 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001835 // click the link corresponding to selected item
1836 $("a",$("li",$selectedUl)[gSelectedIndex]).get()[0].click();
Scott Mainf5089842012-08-14 16:31:07 -07001837 return false;
1838 }
1839 }
Scott Mainb16376f2014-05-21 20:35:47 -07001840 // If Google results are showing, return true to allow ajax search to execute
Scott Main0e76e7e2013-03-12 10:24:07 -07001841 else if ($("#searchResults").is(":visible")) {
Scott Mainb16376f2014-05-21 20:35:47 -07001842 // Also, if search_results is scrolled out of view, scroll to top to make results visible
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001843 if ((sticky ) && (search.value != "")) {
1844 $('body,html').animate({scrollTop:0}, '500', 'swing');
1845 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001846 return true;
1847 }
1848 // 38 UP ARROW
Scott Mainf5089842012-08-14 16:31:07 -07001849 else if (kd && (e.keyCode == 38)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001850 // if the next item is a header, skip it
1851 if ($($("li", $selectedUl)[gSelectedIndex-1]).hasClass("header")) {
Scott Mainf5089842012-08-14 16:31:07 -07001852 gSelectedIndex--;
Scott Main7e447ed2013-02-19 17:22:37 -08001853 }
1854 if (gSelectedIndex >= 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001855 $('li', $selectedUl).removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001856 gSelectedIndex--;
Scott Main0e76e7e2013-03-12 10:24:07 -07001857 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
1858 // If user reaches top, reset selected column
1859 if (gSelectedIndex < 0) {
1860 gSelectedColumn = -1;
1861 }
Scott Mainf5089842012-08-14 16:31:07 -07001862 }
1863 return false;
1864 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001865 // 40 DOWN ARROW
Scott Mainf5089842012-08-14 16:31:07 -07001866 else if (kd && (e.keyCode == 40)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001867 // if the next item is a header, skip it
1868 if ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header")) {
Scott Mainf5089842012-08-14 16:31:07 -07001869 gSelectedIndex++;
Scott Main7e447ed2013-02-19 17:22:37 -08001870 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001871 if ((gSelectedIndex < $("li", $selectedUl).length-1) ||
1872 ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header"))) {
1873 $('li', $selectedUl).removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001874 gSelectedIndex++;
Scott Main0e76e7e2013-03-12 10:24:07 -07001875 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
Scott Mainf5089842012-08-14 16:31:07 -07001876 }
1877 return false;
1878 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001879 // Consider left/right arrow navigation
1880 // NOTE: Order of suggest columns are reverse order (index position 0 is on right)
1881 else if (kd && $columns.length > 1 && gSelectedColumn >= 0) {
1882 // 37 LEFT ARROW
1883 // go left only if current column is not left-most column (last column)
1884 if (e.keyCode == 37 && gSelectedColumn < $columns.length - 1) {
1885 $('li', $selectedUl).removeClass('jd-selected');
1886 gSelectedColumn++;
1887 $selectedUl = $columns[gSelectedColumn];
1888 // keep or reset the selected item to last item as appropriate
1889 gSelectedIndex = gSelectedIndex >
1890 $("li", $selectedUl).length-1 ?
1891 $("li", $selectedUl).length-1 : gSelectedIndex;
1892 // if the corresponding item is a header, move down
1893 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
1894 gSelectedIndex++;
1895 }
Scott Main3b90aff2013-08-01 18:09:35 -07001896 // set item selected
Scott Main0e76e7e2013-03-12 10:24:07 -07001897 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
1898 return false;
1899 }
1900 // 39 RIGHT ARROW
1901 // go right only if current column is not the right-most column (first column)
1902 else if (e.keyCode == 39 && gSelectedColumn > 0) {
1903 $('li', $selectedUl).removeClass('jd-selected');
1904 gSelectedColumn--;
1905 $selectedUl = $columns[gSelectedColumn];
1906 // keep or reset the selected item to last item as appropriate
1907 gSelectedIndex = gSelectedIndex >
1908 $("li", $selectedUl).length-1 ?
1909 $("li", $selectedUl).length-1 : gSelectedIndex;
1910 // if the corresponding item is a header, move down
1911 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
1912 gSelectedIndex++;
1913 }
Scott Main3b90aff2013-08-01 18:09:35 -07001914 // set item selected
Scott Main0e76e7e2013-03-12 10:24:07 -07001915 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
1916 return false;
1917 }
1918 }
1919
Scott Main719acb42013-12-05 16:05:09 -08001920 // if key-up event and not arrow down/up/left/right,
1921 // read the search query and add suggestions to gMatches
Scott Main0e76e7e2013-03-12 10:24:07 -07001922 else if (!kd && (e.keyCode != 40)
1923 && (e.keyCode != 38)
1924 && (e.keyCode != 37)
1925 && (e.keyCode != 39)) {
1926 gSelectedIndex = -1;
Scott Mainf5089842012-08-14 16:31:07 -07001927 gMatches = new Array();
1928 matchedCount = 0;
Scott Main7e447ed2013-02-19 17:22:37 -08001929 gGoogleMatches = new Array();
1930 matchedCountGoogle = 0;
Scott Main0e76e7e2013-03-12 10:24:07 -07001931 gDocsMatches = new Array();
1932 matchedCountDocs = 0;
Scott Main7e447ed2013-02-19 17:22:37 -08001933
1934 // Search for Android matches
Scott Mainf5089842012-08-14 16:31:07 -07001935 for (var i=0; i<DATA.length; i++) {
1936 var s = DATA[i];
1937 if (text.length != 0 &&
1938 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
1939 gMatches[matchedCount] = s;
1940 matchedCount++;
1941 }
1942 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001943 rank_autocomplete_api_results(text, gMatches);
Scott Mainf5089842012-08-14 16:31:07 -07001944 for (var i=0; i<gMatches.length; i++) {
1945 var s = gMatches[i];
Scott Main7e447ed2013-02-19 17:22:37 -08001946 }
1947
1948
1949 // Search for Google matches
1950 for (var i=0; i<GOOGLE_DATA.length; i++) {
1951 var s = GOOGLE_DATA[i];
1952 if (text.length != 0 &&
1953 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
1954 gGoogleMatches[matchedCountGoogle] = s;
1955 matchedCountGoogle++;
Scott Mainf5089842012-08-14 16:31:07 -07001956 }
1957 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001958 rank_autocomplete_api_results(text, gGoogleMatches);
Scott Main7e447ed2013-02-19 17:22:37 -08001959 for (var i=0; i<gGoogleMatches.length; i++) {
1960 var s = gGoogleMatches[i];
1961 }
1962
Scott Mainf5089842012-08-14 16:31:07 -07001963 highlight_autocomplete_result_labels(text);
Scott Main0e76e7e2013-03-12 10:24:07 -07001964
1965
1966
Scott Main719acb42013-12-05 16:05:09 -08001967 // Search for matching JD docs
Scott Main0e76e7e2013-03-12 10:24:07 -07001968 if (text.length >= 3) {
Scott Main719acb42013-12-05 16:05:09 -08001969 // Regex to match only the beginning of a word
1970 var textRegex = new RegExp("\\b" + text.toLowerCase(), "g");
1971
1972
1973 // Search for Training classes
1974 for (var i=0; i<TRAINING_RESOURCES.length; i++) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001975 // current search comparison, with counters for tag and title,
1976 // used later to improve ranking
Scott Main719acb42013-12-05 16:05:09 -08001977 var s = TRAINING_RESOURCES[i];
Scott Main0e76e7e2013-03-12 10:24:07 -07001978 s.matched_tag = 0;
1979 s.matched_title = 0;
1980 var matched = false;
1981
1982 // Check if query matches any tags; work backwards toward 1 to assist ranking
Scott Main719acb42013-12-05 16:05:09 -08001983 for (var j = s.keywords.length - 1; j >= 0; j--) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001984 // it matches a tag
Scott Main719acb42013-12-05 16:05:09 -08001985 if (s.keywords[j].toLowerCase().match(textRegex)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001986 matched = true;
1987 s.matched_tag = j + 1; // add 1 to index position
1988 }
1989 }
Scott Main719acb42013-12-05 16:05:09 -08001990 // Don't consider doc title for lessons (only for class landing pages),
1991 // unless the lesson has a tag that already matches
1992 if ((s.lang == currentLang) &&
1993 (!(s.type == "training" && s.url.indexOf("index.html") == -1) || matched)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001994 // it matches the doc title
Scott Main719acb42013-12-05 16:05:09 -08001995 if (s.title.toLowerCase().match(textRegex)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001996 matched = true;
1997 s.matched_title = 1;
1998 }
1999 }
2000 if (matched) {
2001 gDocsMatches[matchedCountDocs] = s;
2002 matchedCountDocs++;
2003 }
2004 }
Scott Main719acb42013-12-05 16:05:09 -08002005
2006
2007 // Search for API Guides
2008 for (var i=0; i<GUIDE_RESOURCES.length; i++) {
2009 // current search comparison, with counters for tag and title,
2010 // used later to improve ranking
2011 var s = GUIDE_RESOURCES[i];
2012 s.matched_tag = 0;
2013 s.matched_title = 0;
2014 var matched = false;
2015
2016 // Check if query matches any tags; work backwards toward 1 to assist ranking
2017 for (var j = s.keywords.length - 1; j >= 0; j--) {
2018 // it matches a tag
2019 if (s.keywords[j].toLowerCase().match(textRegex)) {
2020 matched = true;
2021 s.matched_tag = j + 1; // add 1 to index position
2022 }
2023 }
2024 // Check if query matches the doc title, but only for current language
2025 if (s.lang == currentLang) {
2026 // if query matches the doc title
2027 if (s.title.toLowerCase().match(textRegex)) {
2028 matched = true;
2029 s.matched_title = 1;
2030 }
2031 }
2032 if (matched) {
2033 gDocsMatches[matchedCountDocs] = s;
2034 matchedCountDocs++;
2035 }
2036 }
2037
2038
2039 // Search for Tools Guides
2040 for (var i=0; i<TOOLS_RESOURCES.length; i++) {
2041 // current search comparison, with counters for tag and title,
2042 // used later to improve ranking
2043 var s = TOOLS_RESOURCES[i];
2044 s.matched_tag = 0;
2045 s.matched_title = 0;
2046 var matched = false;
2047
2048 // Check if query matches any tags; work backwards toward 1 to assist ranking
2049 for (var j = s.keywords.length - 1; j >= 0; j--) {
2050 // it matches a tag
2051 if (s.keywords[j].toLowerCase().match(textRegex)) {
2052 matched = true;
2053 s.matched_tag = j + 1; // add 1 to index position
2054 }
2055 }
2056 // Check if query matches the doc title, but only for current language
2057 if (s.lang == currentLang) {
2058 // if query matches the doc title
2059 if (s.title.toLowerCase().match(textRegex)) {
2060 matched = true;
2061 s.matched_title = 1;
2062 }
2063 }
2064 if (matched) {
2065 gDocsMatches[matchedCountDocs] = s;
2066 matchedCountDocs++;
2067 }
2068 }
2069
2070
2071 // Search for About docs
2072 for (var i=0; i<ABOUT_RESOURCES.length; i++) {
2073 // current search comparison, with counters for tag and title,
2074 // used later to improve ranking
2075 var s = ABOUT_RESOURCES[i];
2076 s.matched_tag = 0;
2077 s.matched_title = 0;
2078 var matched = false;
2079
2080 // Check if query matches any tags; work backwards toward 1 to assist ranking
2081 for (var j = s.keywords.length - 1; j >= 0; j--) {
2082 // it matches a tag
2083 if (s.keywords[j].toLowerCase().match(textRegex)) {
2084 matched = true;
2085 s.matched_tag = j + 1; // add 1 to index position
2086 }
2087 }
2088 // Check if query matches the doc title, but only for current language
2089 if (s.lang == currentLang) {
2090 // if query matches the doc title
2091 if (s.title.toLowerCase().match(textRegex)) {
2092 matched = true;
2093 s.matched_title = 1;
2094 }
2095 }
2096 if (matched) {
2097 gDocsMatches[matchedCountDocs] = s;
2098 matchedCountDocs++;
2099 }
2100 }
2101
2102
2103 // Search for Design guides
2104 for (var i=0; i<DESIGN_RESOURCES.length; i++) {
2105 // current search comparison, with counters for tag and title,
2106 // used later to improve ranking
2107 var s = DESIGN_RESOURCES[i];
2108 s.matched_tag = 0;
2109 s.matched_title = 0;
2110 var matched = false;
2111
2112 // Check if query matches any tags; work backwards toward 1 to assist ranking
2113 for (var j = s.keywords.length - 1; j >= 0; j--) {
2114 // it matches a tag
2115 if (s.keywords[j].toLowerCase().match(textRegex)) {
2116 matched = true;
2117 s.matched_tag = j + 1; // add 1 to index position
2118 }
2119 }
2120 // Check if query matches the doc title, but only for current language
2121 if (s.lang == currentLang) {
2122 // if query matches the doc title
2123 if (s.title.toLowerCase().match(textRegex)) {
2124 matched = true;
2125 s.matched_title = 1;
2126 }
2127 }
2128 if (matched) {
2129 gDocsMatches[matchedCountDocs] = s;
2130 matchedCountDocs++;
2131 }
2132 }
2133
2134
2135 // Search for Distribute guides
2136 for (var i=0; i<DISTRIBUTE_RESOURCES.length; i++) {
2137 // current search comparison, with counters for tag and title,
2138 // used later to improve ranking
2139 var s = DISTRIBUTE_RESOURCES[i];
2140 s.matched_tag = 0;
2141 s.matched_title = 0;
2142 var matched = false;
2143
2144 // Check if query matches any tags; work backwards toward 1 to assist ranking
2145 for (var j = s.keywords.length - 1; j >= 0; j--) {
2146 // it matches a tag
2147 if (s.keywords[j].toLowerCase().match(textRegex)) {
2148 matched = true;
2149 s.matched_tag = j + 1; // add 1 to index position
2150 }
2151 }
2152 // Check if query matches the doc title, but only for current language
2153 if (s.lang == currentLang) {
2154 // if query matches the doc title
2155 if (s.title.toLowerCase().match(textRegex)) {
2156 matched = true;
2157 s.matched_title = 1;
2158 }
2159 }
2160 if (matched) {
2161 gDocsMatches[matchedCountDocs] = s;
2162 matchedCountDocs++;
2163 }
2164 }
2165
2166
2167 // Search for Google guides
2168 for (var i=0; i<GOOGLE_RESOURCES.length; i++) {
2169 // current search comparison, with counters for tag and title,
2170 // used later to improve ranking
2171 var s = GOOGLE_RESOURCES[i];
2172 s.matched_tag = 0;
2173 s.matched_title = 0;
2174 var matched = false;
2175
2176 // Check if query matches any tags; work backwards toward 1 to assist ranking
2177 for (var j = s.keywords.length - 1; j >= 0; j--) {
2178 // it matches a tag
2179 if (s.keywords[j].toLowerCase().match(textRegex)) {
2180 matched = true;
2181 s.matched_tag = j + 1; // add 1 to index position
2182 }
2183 }
2184 // Check if query matches the doc title, but only for current language
2185 if (s.lang == currentLang) {
2186 // if query matches the doc title
2187 if (s.title.toLowerCase().match(textRegex)) {
2188 matched = true;
2189 s.matched_title = 1;
2190 }
2191 }
2192 if (matched) {
2193 gDocsMatches[matchedCountDocs] = s;
2194 matchedCountDocs++;
2195 }
2196 }
2197
2198
2199 // Search for Samples
2200 for (var i=0; i<SAMPLES_RESOURCES.length; i++) {
2201 // current search comparison, with counters for tag and title,
2202 // used later to improve ranking
2203 var s = SAMPLES_RESOURCES[i];
2204 s.matched_tag = 0;
2205 s.matched_title = 0;
2206 var matched = false;
2207 // Check if query matches any tags; work backwards toward 1 to assist ranking
2208 for (var j = s.keywords.length - 1; j >= 0; j--) {
2209 // it matches a tag
2210 if (s.keywords[j].toLowerCase().match(textRegex)) {
2211 matched = true;
2212 s.matched_tag = j + 1; // add 1 to index position
2213 }
2214 }
2215 // Check if query matches the doc title, but only for current language
2216 if (s.lang == currentLang) {
2217 // if query matches the doc title.t
2218 if (s.title.toLowerCase().match(textRegex)) {
2219 matched = true;
2220 s.matched_title = 1;
2221 }
2222 }
2223 if (matched) {
2224 gDocsMatches[matchedCountDocs] = s;
2225 matchedCountDocs++;
2226 }
2227 }
2228
2229 // Rank/sort all the matched pages
Scott Main0e76e7e2013-03-12 10:24:07 -07002230 rank_autocomplete_doc_results(text, gDocsMatches);
2231 }
2232
2233 // draw the suggestions
Scott Mainf5089842012-08-14 16:31:07 -07002234 sync_selection_table(toroot);
2235 return true; // allow the event to bubble up to the search api
2236 }
2237}
2238
Scott Main0e76e7e2013-03-12 10:24:07 -07002239/* Order the jd doc result list based on match quality */
2240function rank_autocomplete_doc_results(query, matches) {
2241 query = query || '';
2242 if (!matches || !matches.length)
2243 return;
2244
2245 var _resultScoreFn = function(match) {
2246 var score = 1.0;
2247
2248 // if the query matched a tag
2249 if (match.matched_tag > 0) {
2250 // multiply score by factor relative to position in tags list (max of 3)
2251 score *= 3 / match.matched_tag;
2252
2253 // if it also matched the title
2254 if (match.matched_title > 0) {
2255 score *= 2;
2256 }
2257 } else if (match.matched_title > 0) {
2258 score *= 3;
2259 }
2260
2261 return score;
2262 };
2263
2264 for (var i=0; i<matches.length; i++) {
2265 matches[i].__resultScore = _resultScoreFn(matches[i]);
2266 }
2267
2268 matches.sort(function(a,b){
2269 var n = b.__resultScore - a.__resultScore;
2270 if (n == 0) // lexicographical sort if scores are the same
2271 n = (a.label < b.label) ? -1 : 1;
2272 return n;
2273 });
2274}
2275
Scott Main7e447ed2013-02-19 17:22:37 -08002276/* Order the result list based on match quality */
Scott Main0e76e7e2013-03-12 10:24:07 -07002277function rank_autocomplete_api_results(query, matches) {
Scott Mainf5089842012-08-14 16:31:07 -07002278 query = query || '';
Scott Main7e447ed2013-02-19 17:22:37 -08002279 if (!matches || !matches.length)
Scott Mainf5089842012-08-14 16:31:07 -07002280 return;
2281
2282 // helper function that gets the last occurence index of the given regex
2283 // in the given string, or -1 if not found
2284 var _lastSearch = function(s, re) {
2285 if (s == '')
2286 return -1;
2287 var l = -1;
2288 var tmp;
2289 while ((tmp = s.search(re)) >= 0) {
2290 if (l < 0) l = 0;
2291 l += tmp;
2292 s = s.substr(tmp + 1);
2293 }
2294 return l;
2295 };
2296
2297 // helper function that counts the occurrences of a given character in
2298 // a given string
2299 var _countChar = function(s, c) {
2300 var n = 0;
2301 for (var i=0; i<s.length; i++)
2302 if (s.charAt(i) == c) ++n;
2303 return n;
2304 };
2305
2306 var queryLower = query.toLowerCase();
2307 var queryAlnum = (queryLower.match(/\w+/) || [''])[0];
2308 var partPrefixAlnumRE = new RegExp('\\b' + queryAlnum);
2309 var partExactAlnumRE = new RegExp('\\b' + queryAlnum + '\\b');
2310
2311 var _resultScoreFn = function(result) {
2312 // scores are calculated based on exact and prefix matches,
2313 // and then number of path separators (dots) from the last
2314 // match (i.e. favoring classes and deep package names)
2315 var score = 1.0;
2316 var labelLower = result.label.toLowerCase();
2317 var t;
2318 t = _lastSearch(labelLower, partExactAlnumRE);
2319 if (t >= 0) {
2320 // exact part match
2321 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2322 score *= 200 / (partsAfter + 1);
2323 } else {
2324 t = _lastSearch(labelLower, partPrefixAlnumRE);
2325 if (t >= 0) {
2326 // part prefix match
2327 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2328 score *= 20 / (partsAfter + 1);
2329 }
2330 }
2331
2332 return score;
2333 };
2334
Scott Main7e447ed2013-02-19 17:22:37 -08002335 for (var i=0; i<matches.length; i++) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002336 // if the API is deprecated, default score is 0; otherwise, perform scoring
2337 if (matches[i].deprecated == "true") {
2338 matches[i].__resultScore = 0;
2339 } else {
2340 matches[i].__resultScore = _resultScoreFn(matches[i]);
2341 }
Scott Mainf5089842012-08-14 16:31:07 -07002342 }
2343
Scott Main7e447ed2013-02-19 17:22:37 -08002344 matches.sort(function(a,b){
Scott Mainf5089842012-08-14 16:31:07 -07002345 var n = b.__resultScore - a.__resultScore;
2346 if (n == 0) // lexicographical sort if scores are the same
2347 n = (a.label < b.label) ? -1 : 1;
2348 return n;
2349 });
2350}
2351
Scott Main7e447ed2013-02-19 17:22:37 -08002352/* Add emphasis to part of string that matches query */
Scott Mainf5089842012-08-14 16:31:07 -07002353function highlight_autocomplete_result_labels(query) {
2354 query = query || '';
Scott Main7e447ed2013-02-19 17:22:37 -08002355 if ((!gMatches || !gMatches.length) && (!gGoogleMatches || !gGoogleMatches.length))
Scott Mainf5089842012-08-14 16:31:07 -07002356 return;
2357
2358 var queryLower = query.toLowerCase();
2359 var queryAlnumDot = (queryLower.match(/[\w\.]+/) || [''])[0];
2360 var queryRE = new RegExp(
2361 '(' + queryAlnumDot.replace(/\./g, '\\.') + ')', 'ig');
2362 for (var i=0; i<gMatches.length; i++) {
2363 gMatches[i].__hilabel = gMatches[i].label.replace(
2364 queryRE, '<b>$1</b>');
2365 }
Scott Main7e447ed2013-02-19 17:22:37 -08002366 for (var i=0; i<gGoogleMatches.length; i++) {
2367 gGoogleMatches[i].__hilabel = gGoogleMatches[i].label.replace(
2368 queryRE, '<b>$1</b>');
2369 }
Scott Mainf5089842012-08-14 16:31:07 -07002370}
2371
2372function search_focus_changed(obj, focused)
2373{
Scott Main3b90aff2013-08-01 18:09:35 -07002374 if (!focused) {
Scott Mainf5089842012-08-14 16:31:07 -07002375 if(obj.value == ""){
2376 $(".search .close").addClass("hide");
2377 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002378 $(".suggest-card").hide();
Scott Mainf5089842012-08-14 16:31:07 -07002379 }
2380}
2381
2382function submit_search() {
2383 var query = document.getElementById('search_autocomplete').value;
2384 location.hash = 'q=' + query;
2385 loadSearchResults();
Dirk Doughertyc3921652014-05-13 16:55:26 -07002386 $("#searchResults").slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002387 return false;
2388}
2389
2390
2391function hideResults() {
Dirk Doughertyc3921652014-05-13 16:55:26 -07002392 $("#searchResults").slideUp('fast', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002393 $(".search .close").addClass("hide");
2394 location.hash = '';
Scott Main3b90aff2013-08-01 18:09:35 -07002395
Scott Mainf5089842012-08-14 16:31:07 -07002396 $("#search_autocomplete").val("").blur();
Scott Main3b90aff2013-08-01 18:09:35 -07002397
Scott Mainf5089842012-08-14 16:31:07 -07002398 // reset the ajax search callback to nothing, so results don't appear unless ENTER
2399 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {});
Scott Main0e76e7e2013-03-12 10:24:07 -07002400
2401 // forcefully regain key-up event control (previously jacked by search api)
2402 $("#search_autocomplete").keyup(function(event) {
2403 return search_changed(event, false, toRoot);
2404 });
2405
Scott Mainf5089842012-08-14 16:31:07 -07002406 return false;
2407}
2408
2409
2410
2411/* ########################################################## */
2412/* ################ CUSTOM SEARCH ENGINE ################## */
2413/* ########################################################## */
2414
Scott Mainf5089842012-08-14 16:31:07 -07002415var searchControl;
Scott Main0e76e7e2013-03-12 10:24:07 -07002416google.load('search', '1', {"callback" : function() {
2417 searchControl = new google.search.SearchControl();
2418 } });
Scott Mainf5089842012-08-14 16:31:07 -07002419
2420function loadSearchResults() {
2421 document.getElementById("search_autocomplete").style.color = "#000";
2422
Scott Mainf5089842012-08-14 16:31:07 -07002423 searchControl = new google.search.SearchControl();
2424
2425 // use our existing search form and use tabs when multiple searchers are used
2426 drawOptions = new google.search.DrawOptions();
2427 drawOptions.setDrawMode(google.search.SearchControl.DRAW_MODE_TABBED);
2428 drawOptions.setInput(document.getElementById("search_autocomplete"));
2429
2430 // configure search result options
2431 searchOptions = new google.search.SearcherOptions();
2432 searchOptions.setExpandMode(GSearchControl.EXPAND_MODE_OPEN);
2433
2434 // configure each of the searchers, for each tab
2435 devSiteSearcher = new google.search.WebSearch();
2436 devSiteSearcher.setUserDefinedLabel("All");
2437 devSiteSearcher.setSiteRestriction("001482626316274216503:zu90b7s047u");
2438
2439 designSearcher = new google.search.WebSearch();
2440 designSearcher.setUserDefinedLabel("Design");
2441 designSearcher.setSiteRestriction("http://developer.android.com/design/");
2442
2443 trainingSearcher = new google.search.WebSearch();
2444 trainingSearcher.setUserDefinedLabel("Training");
2445 trainingSearcher.setSiteRestriction("http://developer.android.com/training/");
2446
2447 guidesSearcher = new google.search.WebSearch();
2448 guidesSearcher.setUserDefinedLabel("Guides");
2449 guidesSearcher.setSiteRestriction("http://developer.android.com/guide/");
2450
2451 referenceSearcher = new google.search.WebSearch();
2452 referenceSearcher.setUserDefinedLabel("Reference");
2453 referenceSearcher.setSiteRestriction("http://developer.android.com/reference/");
2454
Scott Maindf08ada2012-12-03 08:54:37 -08002455 googleSearcher = new google.search.WebSearch();
2456 googleSearcher.setUserDefinedLabel("Google Services");
2457 googleSearcher.setSiteRestriction("http://developer.android.com/google/");
2458
Scott Mainf5089842012-08-14 16:31:07 -07002459 blogSearcher = new google.search.WebSearch();
2460 blogSearcher.setUserDefinedLabel("Blog");
2461 blogSearcher.setSiteRestriction("http://android-developers.blogspot.com");
2462
2463 // add each searcher to the search control
2464 searchControl.addSearcher(devSiteSearcher, searchOptions);
2465 searchControl.addSearcher(designSearcher, searchOptions);
2466 searchControl.addSearcher(trainingSearcher, searchOptions);
2467 searchControl.addSearcher(guidesSearcher, searchOptions);
2468 searchControl.addSearcher(referenceSearcher, searchOptions);
Scott Maindf08ada2012-12-03 08:54:37 -08002469 searchControl.addSearcher(googleSearcher, searchOptions);
Scott Mainf5089842012-08-14 16:31:07 -07002470 searchControl.addSearcher(blogSearcher, searchOptions);
2471
2472 // configure result options
2473 searchControl.setResultSetSize(google.search.Search.LARGE_RESULTSET);
2474 searchControl.setLinkTarget(google.search.Search.LINK_TARGET_SELF);
2475 searchControl.setTimeoutInterval(google.search.SearchControl.TIMEOUT_SHORT);
2476 searchControl.setNoResultsString(google.search.SearchControl.NO_RESULTS_DEFAULT_STRING);
2477
2478 // upon ajax search, refresh the url and search title
2479 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {
2480 updateResultTitle(query);
2481 var query = document.getElementById('search_autocomplete').value;
2482 location.hash = 'q=' + query;
2483 });
2484
Scott Mainde295272013-03-25 15:48:35 -07002485 // once search results load, set up click listeners
2486 searchControl.setSearchCompleteCallback(this, function(control, searcher, query) {
2487 addResultClickListeners();
2488 });
2489
Scott Mainf5089842012-08-14 16:31:07 -07002490 // draw the search results box
2491 searchControl.draw(document.getElementById("leftSearchControl"), drawOptions);
2492
2493 // get query and execute the search
2494 searchControl.execute(decodeURI(getQuery(location.hash)));
2495
2496 document.getElementById("search_autocomplete").focus();
2497 addTabListeners();
2498}
2499// End of loadSearchResults
2500
2501
2502google.setOnLoadCallback(function(){
2503 if (location.hash.indexOf("q=") == -1) {
2504 // if there's no query in the url, don't search and make sure results are hidden
2505 $('#searchResults').hide();
2506 return;
2507 } else {
2508 // first time loading search results for this page
Dirk Doughertyc3921652014-05-13 16:55:26 -07002509 $('#searchResults').slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002510 $(".search .close").removeClass("hide");
2511 loadSearchResults();
2512 }
2513}, true);
2514
Scott Mainb16376f2014-05-21 20:35:47 -07002515/* Adjust the scroll position to account for sticky header, only if the hash matches an id */
2516function offsetScrollForSticky() {
2517 var hash = location.hash;
2518 var $matchingElement = $(hash);
2519 // If there's no element with the hash as an ID, then look for an <a name=''> with it.
2520 if ($matchingElement.length < 1) {
2521 $matchingElement = $('a[name="' + hash.substr(1) + '"]');
2522 }
2523 // Sanity check that hash is a real hash and that there's an element with that ID on the page
2524 if ((hash.indexOf("#") == 0) && $matchingElement.length) {
2525 // If the position of the target element is near the top of the page (<20px, where we expect it
2526 // to be because we need to move it down 60px to become in view), then move it down 60px
2527 if (Math.abs($matchingElement.offset().top - $(window).scrollTop()) < 20) {
2528 $(window).scrollTop($(window).scrollTop() - 60);
2529 } else {
2530 }
2531 }
2532}
2533
Scott Mainf5089842012-08-14 16:31:07 -07002534// when an event on the browser history occurs (back, forward, load) requery hash and do search
2535$(window).hashchange( function(){
Dirk Doughertyc3921652014-05-13 16:55:26 -07002536 // If the hash isn't a search query or there's an error in the query,
2537 // then adjust the scroll position to account for sticky header, then exit.
Scott Mainf5089842012-08-14 16:31:07 -07002538 if ((location.hash.indexOf("q=") == -1) || (query == "undefined")) {
2539 // If the results pane is open, close it.
2540 if (!$("#searchResults").is(":hidden")) {
2541 hideResults();
2542 }
Scott Mainb16376f2014-05-21 20:35:47 -07002543 offsetScrollForSticky();
Scott Mainf5089842012-08-14 16:31:07 -07002544 return;
2545 }
2546
2547 // Otherwise, we have a search to do
2548 var query = decodeURI(getQuery(location.hash));
2549 searchControl.execute(query);
Dirk Doughertyc3921652014-05-13 16:55:26 -07002550 $('#searchResults').slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002551 $("#search_autocomplete").focus();
2552 $(".search .close").removeClass("hide");
2553
2554 updateResultTitle(query);
2555});
2556
2557function updateResultTitle(query) {
2558 $("#searchTitle").html("Results for <em>" + escapeHTML(query) + "</em>");
2559}
2560
2561// forcefully regain key-up event control (previously jacked by search api)
2562$("#search_autocomplete").keyup(function(event) {
2563 return search_changed(event, false, toRoot);
2564});
2565
2566// add event listeners to each tab so we can track the browser history
2567function addTabListeners() {
2568 var tabHeaders = $(".gsc-tabHeader");
2569 for (var i = 0; i < tabHeaders.length; i++) {
2570 $(tabHeaders[i]).attr("id",i).click(function() {
2571 /*
2572 // make a copy of the page numbers for the search left pane
2573 setTimeout(function() {
2574 // remove any residual page numbers
2575 $('#searchResults .gsc-tabsArea .gsc-cursor-box.gs-bidi-start-align').remove();
Scott Main3b90aff2013-08-01 18:09:35 -07002576 // move the page numbers to the left position; make a clone,
Scott Mainf5089842012-08-14 16:31:07 -07002577 // because the element is drawn to the DOM only once
Scott Main3b90aff2013-08-01 18:09:35 -07002578 // and because we're going to remove it (previous line),
2579 // we need it to be available to move again as the user navigates
Scott Mainf5089842012-08-14 16:31:07 -07002580 $('#searchResults .gsc-webResult .gsc-cursor-box.gs-bidi-start-align:visible')
2581 .clone().appendTo('#searchResults .gsc-tabsArea');
2582 }, 200);
2583 */
2584 });
2585 }
2586 setTimeout(function(){$(tabHeaders[0]).click()},200);
2587}
2588
Scott Mainde295272013-03-25 15:48:35 -07002589// add analytics tracking events to each result link
2590function addResultClickListeners() {
2591 $("#searchResults a.gs-title").each(function(index, link) {
2592 // When user clicks enter for Google search results, track it
2593 $(link).click(function() {
2594 _gaq.push(['_trackEvent', 'Google Click', 'clicked: ' + $(this).text(),
2595 'from: ' + $("#search_autocomplete").val()]);
2596 });
2597 });
2598}
2599
Scott Mainf5089842012-08-14 16:31:07 -07002600
2601function getQuery(hash) {
2602 var queryParts = hash.split('=');
2603 return queryParts[1];
2604}
2605
2606/* returns the given string with all HTML brackets converted to entities
2607 TODO: move this to the site's JS library */
2608function escapeHTML(string) {
2609 return string.replace(/</g,"&lt;")
2610 .replace(/>/g,"&gt;");
2611}
2612
2613
2614
2615
2616
2617
2618
2619/* ######################################################## */
2620/* ################# JAVADOC REFERENCE ################### */
2621/* ######################################################## */
2622
Scott Main65511c02012-09-07 15:51:32 -07002623/* Initialize some droiddoc stuff, but only if we're in the reference */
Scott Main52dd2062013-08-15 12:22:28 -07002624if (location.pathname.indexOf("/reference") == 0) {
2625 if(!(location.pathname.indexOf("/reference-gms/packages.html") == 0)
2626 && !(location.pathname.indexOf("/reference-gcm/packages.html") == 0)
2627 && !(location.pathname.indexOf("/reference/com/google") == 0)) {
Robert Ly67d75f12012-12-03 12:53:42 -08002628 $(document).ready(function() {
2629 // init available apis based on user pref
2630 changeApiLevel();
2631 initSidenavHeightResize()
2632 });
2633 }
Scott Main65511c02012-09-07 15:51:32 -07002634}
Scott Mainf5089842012-08-14 16:31:07 -07002635
2636var API_LEVEL_COOKIE = "api_level";
2637var minLevel = 1;
2638var maxLevel = 1;
2639
2640/******* SIDENAV DIMENSIONS ************/
Scott Main3b90aff2013-08-01 18:09:35 -07002641
Scott Mainf5089842012-08-14 16:31:07 -07002642 function initSidenavHeightResize() {
2643 // Change the drag bar size to nicely fit the scrollbar positions
2644 var $dragBar = $(".ui-resizable-s");
2645 $dragBar.css({'width': $dragBar.parent().width() - 5 + "px"});
Scott Main3b90aff2013-08-01 18:09:35 -07002646
2647 $( "#resize-packages-nav" ).resizable({
Scott Mainf5089842012-08-14 16:31:07 -07002648 containment: "#nav-panels",
2649 handles: "s",
2650 alsoResize: "#packages-nav",
2651 resize: function(event, ui) { resizeNav(); }, /* resize the nav while dragging */
2652 stop: function(event, ui) { saveNavPanels(); } /* once stopped, save the sizes to cookie */
2653 });
Scott Main3b90aff2013-08-01 18:09:35 -07002654
Scott Mainf5089842012-08-14 16:31:07 -07002655 }
Scott Main3b90aff2013-08-01 18:09:35 -07002656
Scott Mainf5089842012-08-14 16:31:07 -07002657function updateSidenavFixedWidth() {
2658 if (!navBarIsFixed) return;
2659 $('#devdoc-nav').css({
2660 'width' : $('#side-nav').css('width'),
2661 'margin' : $('#side-nav').css('margin')
2662 });
2663 $('#devdoc-nav a.totop').css({'display':'block','width':$("#nav").innerWidth()+'px'});
Scott Main3b90aff2013-08-01 18:09:35 -07002664
Scott Mainf5089842012-08-14 16:31:07 -07002665 initSidenavHeightResize();
2666}
2667
2668function updateSidenavFullscreenWidth() {
2669 if (!navBarIsFixed) return;
2670 $('#devdoc-nav').css({
2671 'width' : $('#side-nav').css('width'),
2672 'margin' : $('#side-nav').css('margin')
2673 });
2674 $('#devdoc-nav .totop').css({'left': 'inherit'});
Scott Main3b90aff2013-08-01 18:09:35 -07002675
Scott Mainf5089842012-08-14 16:31:07 -07002676 initSidenavHeightResize();
2677}
2678
2679function buildApiLevelSelector() {
2680 maxLevel = SINCE_DATA.length;
2681 var userApiLevel = parseInt(readCookie(API_LEVEL_COOKIE));
2682 userApiLevel = userApiLevel == 0 ? maxLevel : userApiLevel; // If there's no cookie (zero), use the max by default
2683
2684 minLevel = parseInt($("#doc-api-level").attr("class"));
2685 // Handle provisional api levels; the provisional level will always be the highest possible level
2686 // Provisional api levels will also have a length; other stuff that's just missing a level won't,
2687 // so leave those kinds of entities at the default level of 1 (for example, the R.styleable class)
2688 if (isNaN(minLevel) && minLevel.length) {
2689 minLevel = maxLevel;
2690 }
2691 var select = $("#apiLevelSelector").html("").change(changeApiLevel);
2692 for (var i = maxLevel-1; i >= 0; i--) {
2693 var option = $("<option />").attr("value",""+SINCE_DATA[i]).append(""+SINCE_DATA[i]);
2694 // if (SINCE_DATA[i] < minLevel) option.addClass("absent"); // always false for strings (codenames)
2695 select.append(option);
2696 }
2697
2698 // get the DOM element and use setAttribute cuz IE6 fails when using jquery .attr('selected',true)
2699 var selectedLevelItem = $("#apiLevelSelector option[value='"+userApiLevel+"']").get(0);
2700 selectedLevelItem.setAttribute('selected',true);
2701}
2702
2703function changeApiLevel() {
2704 maxLevel = SINCE_DATA.length;
2705 var selectedLevel = maxLevel;
2706
2707 selectedLevel = parseInt($("#apiLevelSelector option:selected").val());
2708 toggleVisisbleApis(selectedLevel, "body");
2709
2710 var date = new Date();
2711 date.setTime(date.getTime()+(10*365*24*60*60*1000)); // keep this for 10 years
2712 var expiration = date.toGMTString();
2713 writeCookie(API_LEVEL_COOKIE, selectedLevel, null, expiration);
2714
2715 if (selectedLevel < minLevel) {
2716 var thing = ($("#jd-header").html().indexOf("package") != -1) ? "package" : "class";
Scott Main8f24ca82012-11-16 10:34:22 -08002717 $("#naMessage").show().html("<div><p><strong>This " + thing
2718 + " requires API level " + minLevel + " or higher.</strong></p>"
2719 + "<p>This document is hidden because your selected API level for the documentation is "
2720 + selectedLevel + ". You can change the documentation API level with the selector "
2721 + "above the left navigation.</p>"
2722 + "<p>For more information about specifying the API level your app requires, "
2723 + "read <a href='" + toRoot + "training/basics/supporting-devices/platforms.html'"
2724 + ">Supporting Different Platform Versions</a>.</p>"
2725 + "<input type='button' value='OK, make this page visible' "
2726 + "title='Change the API level to " + minLevel + "' "
2727 + "onclick='$(\"#apiLevelSelector\").val(\"" + minLevel + "\");changeApiLevel();' />"
2728 + "</div>");
Scott Mainf5089842012-08-14 16:31:07 -07002729 } else {
2730 $("#naMessage").hide();
2731 }
2732}
2733
2734function toggleVisisbleApis(selectedLevel, context) {
2735 var apis = $(".api",context);
2736 apis.each(function(i) {
2737 var obj = $(this);
2738 var className = obj.attr("class");
2739 var apiLevelIndex = className.lastIndexOf("-")+1;
2740 var apiLevelEndIndex = className.indexOf(" ", apiLevelIndex);
2741 apiLevelEndIndex = apiLevelEndIndex != -1 ? apiLevelEndIndex : className.length;
2742 var apiLevel = className.substring(apiLevelIndex, apiLevelEndIndex);
2743 if (apiLevel.length == 0) { // for odd cases when the since data is actually missing, just bail
2744 return;
2745 }
2746 apiLevel = parseInt(apiLevel);
2747
2748 // Handle provisional api levels; if this item's level is the provisional one, set it to the max
2749 var selectedLevelNum = parseInt(selectedLevel)
2750 var apiLevelNum = parseInt(apiLevel);
2751 if (isNaN(apiLevelNum)) {
2752 apiLevelNum = maxLevel;
2753 }
2754
2755 // Grey things out that aren't available and give a tooltip title
2756 if (apiLevelNum > selectedLevelNum) {
2757 obj.addClass("absent").attr("title","Requires API Level \""
Scott Main641c2c22013-10-31 14:48:45 -07002758 + apiLevel + "\" or higher. To reveal, change the target API level "
2759 + "above the left navigation.");
Scott Main3b90aff2013-08-01 18:09:35 -07002760 }
Scott Mainf5089842012-08-14 16:31:07 -07002761 else obj.removeClass("absent").removeAttr("title");
2762 });
2763}
2764
2765
2766
2767
2768/* ################# SIDENAV TREE VIEW ################### */
2769
2770function new_node(me, mom, text, link, children_data, api_level)
2771{
2772 var node = new Object();
2773 node.children = Array();
2774 node.children_data = children_data;
2775 node.depth = mom.depth + 1;
2776
2777 node.li = document.createElement("li");
2778 mom.get_children_ul().appendChild(node.li);
2779
2780 node.label_div = document.createElement("div");
2781 node.label_div.className = "label";
2782 if (api_level != null) {
2783 $(node.label_div).addClass("api");
2784 $(node.label_div).addClass("api-level-"+api_level);
2785 }
2786 node.li.appendChild(node.label_div);
2787
2788 if (children_data != null) {
2789 node.expand_toggle = document.createElement("a");
2790 node.expand_toggle.href = "javascript:void(0)";
2791 node.expand_toggle.onclick = function() {
2792 if (node.expanded) {
2793 $(node.get_children_ul()).slideUp("fast");
2794 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2795 node.expanded = false;
2796 } else {
2797 expand_node(me, node);
2798 }
2799 };
2800 node.label_div.appendChild(node.expand_toggle);
2801
2802 node.plus_img = document.createElement("img");
2803 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2804 node.plus_img.className = "plus";
2805 node.plus_img.width = "8";
2806 node.plus_img.border = "0";
2807 node.expand_toggle.appendChild(node.plus_img);
2808
2809 node.expanded = false;
2810 }
2811
2812 var a = document.createElement("a");
2813 node.label_div.appendChild(a);
2814 node.label = document.createTextNode(text);
2815 a.appendChild(node.label);
2816 if (link) {
2817 a.href = me.toroot + link;
2818 } else {
2819 if (children_data != null) {
2820 a.className = "nolink";
2821 a.href = "javascript:void(0)";
2822 a.onclick = node.expand_toggle.onclick;
2823 // This next line shouldn't be necessary. I'll buy a beer for the first
2824 // person who figures out how to remove this line and have the link
2825 // toggle shut on the first try. --joeo@android.com
2826 node.expanded = false;
2827 }
2828 }
Scott Main3b90aff2013-08-01 18:09:35 -07002829
Scott Mainf5089842012-08-14 16:31:07 -07002830
2831 node.children_ul = null;
2832 node.get_children_ul = function() {
2833 if (!node.children_ul) {
2834 node.children_ul = document.createElement("ul");
2835 node.children_ul.className = "children_ul";
2836 node.children_ul.style.display = "none";
2837 node.li.appendChild(node.children_ul);
2838 }
2839 return node.children_ul;
2840 };
2841
2842 return node;
2843}
2844
Robert Lyd2dd6e52012-11-29 21:28:48 -08002845
2846
2847
Scott Mainf5089842012-08-14 16:31:07 -07002848function expand_node(me, node)
2849{
2850 if (node.children_data && !node.expanded) {
2851 if (node.children_visited) {
2852 $(node.get_children_ul()).slideDown("fast");
2853 } else {
2854 get_node(me, node);
2855 if ($(node.label_div).hasClass("absent")) {
2856 $(node.get_children_ul()).addClass("absent");
Scott Main3b90aff2013-08-01 18:09:35 -07002857 }
Scott Mainf5089842012-08-14 16:31:07 -07002858 $(node.get_children_ul()).slideDown("fast");
2859 }
2860 node.plus_img.src = me.toroot + "assets/images/triangle-opened-small.png";
2861 node.expanded = true;
2862
2863 // perform api level toggling because new nodes are new to the DOM
2864 var selectedLevel = $("#apiLevelSelector option:selected").val();
2865 toggleVisisbleApis(selectedLevel, "#side-nav");
2866 }
2867}
2868
2869function get_node(me, mom)
2870{
2871 mom.children_visited = true;
2872 for (var i in mom.children_data) {
2873 var node_data = mom.children_data[i];
2874 mom.children[i] = new_node(me, mom, node_data[0], node_data[1],
2875 node_data[2], node_data[3]);
2876 }
2877}
2878
2879function this_page_relative(toroot)
2880{
2881 var full = document.location.pathname;
2882 var file = "";
2883 if (toroot.substr(0, 1) == "/") {
2884 if (full.substr(0, toroot.length) == toroot) {
2885 return full.substr(toroot.length);
2886 } else {
2887 // the file isn't under toroot. Fail.
2888 return null;
2889 }
2890 } else {
2891 if (toroot != "./") {
2892 toroot = "./" + toroot;
2893 }
2894 do {
2895 if (toroot.substr(toroot.length-3, 3) == "../" || toroot == "./") {
2896 var pos = full.lastIndexOf("/");
2897 file = full.substr(pos) + file;
2898 full = full.substr(0, pos);
2899 toroot = toroot.substr(0, toroot.length-3);
2900 }
2901 } while (toroot != "" && toroot != "/");
2902 return file.substr(1);
2903 }
2904}
2905
2906function find_page(url, data)
2907{
2908 var nodes = data;
2909 var result = null;
2910 for (var i in nodes) {
2911 var d = nodes[i];
2912 if (d[1] == url) {
2913 return new Array(i);
2914 }
2915 else if (d[2] != null) {
2916 result = find_page(url, d[2]);
2917 if (result != null) {
2918 return (new Array(i).concat(result));
2919 }
2920 }
2921 }
2922 return null;
2923}
2924
Scott Mainf5089842012-08-14 16:31:07 -07002925function init_default_navtree(toroot) {
Scott Main25e73002013-03-27 15:24:06 -07002926 // load json file for navtree data
2927 $.getScript(toRoot + 'navtree_data.js', function(data, textStatus, jqxhr) {
2928 // when the file is loaded, initialize the tree
2929 if(jqxhr.status === 200) {
2930 init_navtree("tree-list", toroot, NAVTREE_DATA);
2931 }
2932 });
Scott Main3b90aff2013-08-01 18:09:35 -07002933
Scott Mainf5089842012-08-14 16:31:07 -07002934 // perform api level toggling because because the whole tree is new to the DOM
2935 var selectedLevel = $("#apiLevelSelector option:selected").val();
2936 toggleVisisbleApis(selectedLevel, "#side-nav");
2937}
2938
2939function init_navtree(navtree_id, toroot, root_nodes)
2940{
2941 var me = new Object();
2942 me.toroot = toroot;
2943 me.node = new Object();
2944
2945 me.node.li = document.getElementById(navtree_id);
2946 me.node.children_data = root_nodes;
2947 me.node.children = new Array();
2948 me.node.children_ul = document.createElement("ul");
2949 me.node.get_children_ul = function() { return me.node.children_ul; };
2950 //me.node.children_ul.className = "children_ul";
2951 me.node.li.appendChild(me.node.children_ul);
2952 me.node.depth = 0;
2953
2954 get_node(me, me.node);
2955
2956 me.this_page = this_page_relative(toroot);
2957 me.breadcrumbs = find_page(me.this_page, root_nodes);
2958 if (me.breadcrumbs != null && me.breadcrumbs.length != 0) {
2959 var mom = me.node;
2960 for (var i in me.breadcrumbs) {
2961 var j = me.breadcrumbs[i];
2962 mom = mom.children[j];
2963 expand_node(me, mom);
2964 }
2965 mom.label_div.className = mom.label_div.className + " selected";
2966 addLoadEvent(function() {
2967 scrollIntoView("nav-tree");
2968 });
2969 }
2970}
2971
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07002972
2973
2974
2975
2976
2977
2978
Robert Lyd2dd6e52012-11-29 21:28:48 -08002979/* TODO: eliminate redundancy with non-google functions */
2980function init_google_navtree(navtree_id, toroot, root_nodes)
2981{
2982 var me = new Object();
2983 me.toroot = toroot;
2984 me.node = new Object();
2985
2986 me.node.li = document.getElementById(navtree_id);
2987 me.node.children_data = root_nodes;
2988 me.node.children = new Array();
2989 me.node.children_ul = document.createElement("ul");
2990 me.node.get_children_ul = function() { return me.node.children_ul; };
2991 //me.node.children_ul.className = "children_ul";
2992 me.node.li.appendChild(me.node.children_ul);
2993 me.node.depth = 0;
2994
2995 get_google_node(me, me.node);
Robert Lyd2dd6e52012-11-29 21:28:48 -08002996}
2997
2998function new_google_node(me, mom, text, link, children_data, api_level)
2999{
3000 var node = new Object();
3001 var child;
3002 node.children = Array();
3003 node.children_data = children_data;
3004 node.depth = mom.depth + 1;
3005 node.get_children_ul = function() {
3006 if (!node.children_ul) {
Scott Main3b90aff2013-08-01 18:09:35 -07003007 node.children_ul = document.createElement("ul");
3008 node.children_ul.className = "tree-list-children";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003009 node.li.appendChild(node.children_ul);
3010 }
3011 return node.children_ul;
3012 };
3013 node.li = document.createElement("li");
3014
3015 mom.get_children_ul().appendChild(node.li);
Scott Main3b90aff2013-08-01 18:09:35 -07003016
3017
Robert Lyd2dd6e52012-11-29 21:28:48 -08003018 if(link) {
3019 child = document.createElement("a");
3020
3021 }
3022 else {
3023 child = document.createElement("span");
Scott Mainac71b2b2012-11-30 14:40:58 -08003024 child.className = "tree-list-subtitle";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003025
3026 }
3027 if (children_data != null) {
3028 node.li.className="nav-section";
3029 node.label_div = document.createElement("div");
Scott Main3b90aff2013-08-01 18:09:35 -07003030 node.label_div.className = "nav-section-header-ref";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003031 node.li.appendChild(node.label_div);
3032 get_google_node(me, node);
3033 node.label_div.appendChild(child);
3034 }
3035 else {
3036 node.li.appendChild(child);
3037 }
3038 if(link) {
3039 child.href = me.toroot + link;
3040 }
3041 node.label = document.createTextNode(text);
3042 child.appendChild(node.label);
3043
3044 node.children_ul = null;
3045
3046 return node;
3047}
3048
3049function get_google_node(me, mom)
3050{
3051 mom.children_visited = true;
3052 var linkText;
3053 for (var i in mom.children_data) {
3054 var node_data = mom.children_data[i];
3055 linkText = node_data[0];
3056
3057 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3058 linkText = linkText.substr(19, linkText.length);
3059 }
3060 mom.children[i] = new_google_node(me, mom, linkText, node_data[1],
3061 node_data[2], node_data[3]);
3062 }
3063}
Scott Mainad08f072013-08-20 16:49:57 -07003064
3065
3066
3067
3068
3069
3070/****** NEW version of script to build google and sample navs dynamically ******/
3071// TODO: update Google reference docs to tolerate this new implementation
3072
Scott Maine624b3f2013-09-12 12:56:41 -07003073var NODE_NAME = 0;
3074var NODE_HREF = 1;
3075var NODE_GROUP = 2;
3076var NODE_TAGS = 3;
3077var NODE_CHILDREN = 4;
3078
Scott Mainad08f072013-08-20 16:49:57 -07003079function init_google_navtree2(navtree_id, data)
3080{
3081 var $containerUl = $("#"+navtree_id);
Scott Mainad08f072013-08-20 16:49:57 -07003082 for (var i in data) {
3083 var node_data = data[i];
3084 $containerUl.append(new_google_node2(node_data));
3085 }
3086
Scott Main70557ee2013-10-30 14:47:40 -07003087 // Make all third-generation list items 'sticky' to prevent them from collapsing
3088 $containerUl.find('li li li.nav-section').addClass('sticky');
3089
Scott Mainad08f072013-08-20 16:49:57 -07003090 initExpandableNavItems("#"+navtree_id);
3091}
3092
3093function new_google_node2(node_data)
3094{
Scott Maine624b3f2013-09-12 12:56:41 -07003095 var linkText = node_data[NODE_NAME];
Scott Mainad08f072013-08-20 16:49:57 -07003096 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3097 linkText = linkText.substr(19, linkText.length);
3098 }
3099 var $li = $('<li>');
3100 var $a;
Scott Maine624b3f2013-09-12 12:56:41 -07003101 if (node_data[NODE_HREF] != null) {
Scott Main70557ee2013-10-30 14:47:40 -07003102 $a = $('<a href="' + toRoot + node_data[NODE_HREF] + '" title="' + linkText + '" >'
3103 + linkText + '</a>');
Scott Mainad08f072013-08-20 16:49:57 -07003104 } else {
Scott Main70557ee2013-10-30 14:47:40 -07003105 $a = $('<a href="#" onclick="return false;" title="' + linkText + '" >'
3106 + linkText + '/</a>');
Scott Mainad08f072013-08-20 16:49:57 -07003107 }
3108 var $childUl = $('<ul>');
Scott Maine624b3f2013-09-12 12:56:41 -07003109 if (node_data[NODE_CHILDREN] != null) {
Scott Mainad08f072013-08-20 16:49:57 -07003110 $li.addClass("nav-section");
3111 $a = $('<div class="nav-section-header">').append($a);
Scott Maine624b3f2013-09-12 12:56:41 -07003112 if (node_data[NODE_HREF] == null) $a.addClass('empty');
Scott Mainad08f072013-08-20 16:49:57 -07003113
Scott Maine624b3f2013-09-12 12:56:41 -07003114 for (var i in node_data[NODE_CHILDREN]) {
3115 var child_node_data = node_data[NODE_CHILDREN][i];
Scott Mainad08f072013-08-20 16:49:57 -07003116 $childUl.append(new_google_node2(child_node_data));
3117 }
3118 $li.append($childUl);
3119 }
3120 $li.prepend($a);
3121
3122 return $li;
3123}
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
Robert Lyd2dd6e52012-11-29 21:28:48 -08003135function showGoogleRefTree() {
3136 init_default_google_navtree(toRoot);
3137 init_default_gcm_navtree(toRoot);
Robert Lyd2dd6e52012-11-29 21:28:48 -08003138}
3139
3140function init_default_google_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07003141 // load json file for navtree data
3142 $.getScript(toRoot + 'gms_navtree_data.js', function(data, textStatus, jqxhr) {
3143 // when the file is loaded, initialize the tree
3144 if(jqxhr.status === 200) {
3145 init_google_navtree("gms-tree-list", toroot, GMS_NAVTREE_DATA);
3146 highlightSidenav();
3147 resizeNav();
3148 }
3149 });
Robert Lyd2dd6e52012-11-29 21:28:48 -08003150}
3151
3152function init_default_gcm_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07003153 // load json file for navtree data
3154 $.getScript(toRoot + 'gcm_navtree_data.js', function(data, textStatus, jqxhr) {
3155 // when the file is loaded, initialize the tree
3156 if(jqxhr.status === 200) {
3157 init_google_navtree("gcm-tree-list", toroot, GCM_NAVTREE_DATA);
3158 highlightSidenav();
3159 resizeNav();
3160 }
3161 });
Robert Lyd2dd6e52012-11-29 21:28:48 -08003162}
3163
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003164function showSamplesRefTree() {
3165 init_default_samples_navtree(toRoot);
3166}
3167
3168function init_default_samples_navtree(toroot) {
3169 // load json file for navtree data
3170 $.getScript(toRoot + 'samples_navtree_data.js', function(data, textStatus, jqxhr) {
3171 // when the file is loaded, initialize the tree
3172 if(jqxhr.status === 200) {
Scott Mainf1435b72013-10-30 16:27:38 -07003173 // hack to remove the "about the samples" link then put it back in
3174 // after we nuke the list to remove the dummy static list of samples
3175 var $firstLi = $("#nav.samples-nav > li:first-child").clone();
3176 $("#nav.samples-nav").empty();
3177 $("#nav.samples-nav").append($firstLi);
3178
Scott Mainad08f072013-08-20 16:49:57 -07003179 init_google_navtree2("nav.samples-nav", SAMPLES_NAVTREE_DATA);
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003180 highlightSidenav();
3181 resizeNav();
Scott Main03aca9a2013-10-31 07:20:55 -07003182 if ($("#jd-content #samples").length) {
3183 showSamples();
3184 }
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003185 }
3186 });
3187}
3188
Scott Mainf5089842012-08-14 16:31:07 -07003189/* TOGGLE INHERITED MEMBERS */
3190
3191/* Toggle an inherited class (arrow toggle)
3192 * @param linkObj The link that was clicked.
3193 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3194 * 'null' to simply toggle.
3195 */
3196function toggleInherited(linkObj, expand) {
3197 var base = linkObj.getAttribute("id");
3198 var list = document.getElementById(base + "-list");
3199 var summary = document.getElementById(base + "-summary");
3200 var trigger = document.getElementById(base + "-trigger");
3201 var a = $(linkObj);
3202 if ( (expand == null && a.hasClass("closed")) || expand ) {
3203 list.style.display = "none";
3204 summary.style.display = "block";
3205 trigger.src = toRoot + "assets/images/triangle-opened.png";
3206 a.removeClass("closed");
3207 a.addClass("opened");
3208 } else if ( (expand == null && a.hasClass("opened")) || (expand == false) ) {
3209 list.style.display = "block";
3210 summary.style.display = "none";
3211 trigger.src = toRoot + "assets/images/triangle-closed.png";
3212 a.removeClass("opened");
3213 a.addClass("closed");
3214 }
3215 return false;
3216}
3217
3218/* Toggle all inherited classes in a single table (e.g. all inherited methods)
3219 * @param linkObj The link that was clicked.
3220 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3221 * 'null' to simply toggle.
3222 */
3223function toggleAllInherited(linkObj, expand) {
3224 var a = $(linkObj);
3225 var table = $(a.parent().parent().parent()); // ugly way to get table/tbody
3226 var expandos = $(".jd-expando-trigger", table);
3227 if ( (expand == null && a.text() == "[Expand]") || expand ) {
3228 expandos.each(function(i) {
3229 toggleInherited(this, true);
3230 });
3231 a.text("[Collapse]");
3232 } else if ( (expand == null && a.text() == "[Collapse]") || (expand == false) ) {
3233 expandos.each(function(i) {
3234 toggleInherited(this, false);
3235 });
3236 a.text("[Expand]");
3237 }
3238 return false;
3239}
3240
3241/* Toggle all inherited members in the class (link in the class title)
3242 */
3243function toggleAllClassInherited() {
3244 var a = $("#toggleAllClassInherited"); // get toggle link from class title
3245 var toggles = $(".toggle-all", $("#body-content"));
3246 if (a.text() == "[Expand All]") {
3247 toggles.each(function(i) {
3248 toggleAllInherited(this, true);
3249 });
3250 a.text("[Collapse All]");
3251 } else {
3252 toggles.each(function(i) {
3253 toggleAllInherited(this, false);
3254 });
3255 a.text("[Expand All]");
3256 }
3257 return false;
3258}
3259
3260/* Expand all inherited members in the class. Used when initiating page search */
3261function ensureAllInheritedExpanded() {
3262 var toggles = $(".toggle-all", $("#body-content"));
3263 toggles.each(function(i) {
3264 toggleAllInherited(this, true);
3265 });
3266 $("#toggleAllClassInherited").text("[Collapse All]");
3267}
3268
3269
3270/* HANDLE KEY EVENTS
3271 * - Listen for Ctrl+F (Cmd on Mac) and expand all inherited members (to aid page search)
3272 */
3273var agent = navigator['userAgent'].toLowerCase();
3274var mac = agent.indexOf("macintosh") != -1;
3275
3276$(document).keydown( function(e) {
3277var control = mac ? e.metaKey && !e.ctrlKey : e.ctrlKey; // get ctrl key
3278 if (control && e.which == 70) { // 70 is "F"
3279 ensureAllInheritedExpanded();
3280 }
3281});
Scott Main498d7102013-08-21 15:47:38 -07003282
3283
3284
3285
3286
3287
3288/* On-demand functions */
3289
3290/** Move sample code line numbers out of PRE block and into non-copyable column */
3291function initCodeLineNumbers() {
3292 var numbers = $("#codesample-block a.number");
3293 if (numbers.length) {
3294 $("#codesample-line-numbers").removeClass("hidden").append(numbers);
3295 }
3296
3297 $(document).ready(function() {
3298 // select entire line when clicked
3299 $("span.code-line").click(function() {
3300 if (!shifted) {
3301 selectText(this);
3302 }
3303 });
3304 // invoke line link on double click
3305 $(".code-line").dblclick(function() {
3306 document.location.hash = $(this).attr('id');
3307 });
3308 // highlight the line when hovering on the number
3309 $("#codesample-line-numbers a.number").mouseover(function() {
3310 var id = $(this).attr('href');
3311 $(id).css('background','#e7e7e7');
3312 });
3313 $("#codesample-line-numbers a.number").mouseout(function() {
3314 var id = $(this).attr('href');
3315 $(id).css('background','none');
3316 });
3317 });
3318}
3319
3320// create SHIFT key binder to avoid the selectText method when selecting multiple lines
3321var shifted = false;
3322$(document).bind('keyup keydown', function(e){shifted = e.shiftKey; return true;} );
3323
3324// courtesy of jasonedelman.com
3325function selectText(element) {
3326 var doc = document
3327 , range, selection
3328 ;
3329 if (doc.body.createTextRange) { //ms
3330 range = doc.body.createTextRange();
3331 range.moveToElementText(element);
3332 range.select();
3333 } else if (window.getSelection) { //all others
Scott Main70557ee2013-10-30 14:47:40 -07003334 selection = window.getSelection();
Scott Main498d7102013-08-21 15:47:38 -07003335 range = doc.createRange();
3336 range.selectNodeContents(element);
3337 selection.removeAllRanges();
3338 selection.addRange(range);
3339 }
Scott Main285f0772013-08-22 23:22:09 +00003340}
Scott Main03aca9a2013-10-31 07:20:55 -07003341
3342
3343
3344
3345/** Display links and other information about samples that match the
3346 group specified by the URL */
3347function showSamples() {
3348 var group = $("#samples").attr('class');
3349 $("#samples").html("<p>Here are some samples for <b>" + group + "</b> apps:</p>");
3350
3351 var $ul = $("<ul>");
3352 $selectedLi = $("#nav li.selected");
3353
3354 $selectedLi.children("ul").children("li").each(function() {
3355 var $li = $("<li>").append($(this).find("a").first().clone());
3356 $ul.append($li);
3357 });
3358
3359 $("#samples").append($ul);
3360
3361}
Dirk Doughertyc3921652014-05-13 16:55:26 -07003362
3363
3364
3365/* ########################################################## */
3366/* ################### RESOURCE CARDS ##################### */
3367/* ########################################################## */
3368
3369/** Handle resource queries, collections, and grids (sections). Requires
3370 jd_tag_helpers.js and the *_unified_data.js to be loaded. */
3371
3372(function() {
3373 // Prevent the same resource from being loaded more than once per page.
3374 var addedPageResources = {};
3375
3376 $(document).ready(function() {
3377 $('.resource-widget').each(function() {
3378 initResourceWidget(this);
3379 });
3380
3381 /* Pass the line height to ellipsisfade() to adjust the height of the
3382 text container to show the max number of lines possible, without
3383 showing lines that are cut off. This works with the css ellipsis
3384 classes to fade last text line and apply an ellipsis char. */
3385
Scott Mainb16376f2014-05-21 20:35:47 -07003386 //card text currently uses 15px line height.
Dirk Doughertyc3921652014-05-13 16:55:26 -07003387 var lineHeight = 15;
3388 $('.card-info .text').ellipsisfade(lineHeight);
3389 });
3390
3391 /*
3392 Three types of resource layouts:
3393 Flow - Uses a fixed row-height flow using float left style.
3394 Carousel - Single card slideshow all same dimension absolute.
3395 Stack - Uses fixed columns and flexible element height.
3396 */
3397 function initResourceWidget(widget) {
3398 var $widget = $(widget);
3399 var isFlow = $widget.hasClass('resource-flow-layout'),
3400 isCarousel = $widget.hasClass('resource-carousel-layout'),
3401 isStack = $widget.hasClass('resource-stack-layout');
3402
3403 // find size of widget by pulling out its class name
3404 var sizeCols = 1;
3405 var m = $widget.get(0).className.match(/\bcol-(\d+)\b/);
3406 if (m) {
3407 sizeCols = parseInt(m[1], 10);
3408 }
3409
3410 var opts = {
3411 cardSizes: ($widget.data('cardsizes') || '').split(','),
3412 maxResults: parseInt($widget.data('maxresults') || '100', 10),
3413 itemsPerPage: $widget.data('itemsperpage'),
3414 sortOrder: $widget.data('sortorder'),
3415 query: $widget.data('query'),
3416 section: $widget.data('section'),
3417 sizeCols: sizeCols
3418 };
3419
3420 // run the search for the set of resources to show
3421
3422 var resources = buildResourceList(opts);
3423
3424 if (isFlow) {
3425 drawResourcesFlowWidget($widget, opts, resources);
3426 } else if (isCarousel) {
3427 drawResourcesCarouselWidget($widget, opts, resources);
3428 } else if (isStack) {
3429 var sections = buildSectionList(opts);
3430 opts['numStacks'] = $widget.data('numstacks');
3431 drawResourcesStackWidget($widget, opts, resources, sections);
3432 }
3433 }
3434
3435 /* Initializes a Resource Carousel Widget */
3436 function drawResourcesCarouselWidget($widget, opts, resources) {
3437 $widget.empty();
3438 var plusone = true; //always show plusone on carousel
3439
3440 $widget.addClass('resource-card slideshow-container')
3441 .append($('<a>').addClass('slideshow-prev').text('Prev'))
3442 .append($('<a>').addClass('slideshow-next').text('Next'));
3443
3444 var css = { 'width': $widget.width() + 'px',
3445 'height': $widget.height() + 'px' };
3446
3447 var $ul = $('<ul>');
3448
3449 for (var i = 0; i < resources.length; ++i) {
3450 //keep url clean for matching and offline mode handling
3451 var urlPrefix = resources[i].url.indexOf("//") > -1 ? "" : toRoot;
3452 var $card = $('<a>')
3453 .attr('href', urlPrefix + resources[i].url)
3454 .decorateResourceCard(resources[i],plusone);
3455
3456 $('<li>').css(css)
3457 .append($card)
3458 .appendTo($ul);
3459 }
3460
3461 $('<div>').addClass('frame')
3462 .append($ul)
3463 .appendTo($widget);
3464
3465 $widget.dacSlideshow({
3466 auto: true,
3467 btnPrev: '.slideshow-prev',
3468 btnNext: '.slideshow-next'
3469 });
3470 };
3471
3472 /* Initializes a Resource Card Stack Widget (column-based layout) */
3473 function drawResourcesStackWidget($widget, opts, resources, sections) {
3474 // Don't empty widget, grab all items inside since they will be the first
3475 // items stacked, followed by the resource query
3476 var plusone = true; //by default show plusone on section cards
3477 var cards = $widget.find('.resource-card').detach().toArray();
3478 var numStacks = opts.numStacks || 1;
3479 var $stacks = [];
3480 var urlString;
3481
3482 for (var i = 0; i < numStacks; ++i) {
3483 $stacks[i] = $('<div>').addClass('resource-card-stack')
3484 .appendTo($widget);
3485 }
3486
3487 var sectionResources = [];
3488
3489 // Extract any subsections that are actually resource cards
3490 for (var i = 0; i < sections.length; ++i) {
3491 if (!sections[i].sections || !sections[i].sections.length) {
3492 //keep url clean for matching and offline mode handling
3493 urlPrefix = sections[i].url.indexOf("//") > -1 ? "" : toRoot;
3494 // Render it as a resource card
3495
3496 sectionResources.push(
3497 $('<a>')
3498 .addClass('resource-card section-card')
3499 .attr('href', urlPrefix + sections[i].resource.url)
3500 .decorateResourceCard(sections[i].resource,plusone)[0]
3501 );
3502
3503 } else {
3504 cards.push(
3505 $('<div>')
3506 .addClass('resource-card section-card-menu')
3507 .decorateResourceSection(sections[i],plusone)[0]
3508 );
3509 }
3510 }
3511
3512 cards = cards.concat(sectionResources);
3513
3514 for (var i = 0; i < resources.length; ++i) {
3515 //keep url clean for matching and offline mode handling
3516 urlPrefix = resources[i].url.indexOf("//") > -1 ? "" : toRoot;
3517 var $card = $('<a>')
3518 .addClass('resource-card related-card')
3519 .attr('href', urlPrefix + resources[i].url)
3520 .decorateResourceCard(resources[i],plusone);
3521
3522 cards.push($card[0]);
3523 }
3524
3525 for (var i = 0; i < cards.length; ++i) {
3526 // Find the stack with the shortest height, but give preference to
3527 // left to right order.
3528 var minHeight = $stacks[0].height();
3529 var minIndex = 0;
3530
3531 for (var j = 1; j < numStacks; ++j) {
3532 var height = $stacks[j].height();
3533 if (height < minHeight - 45) {
3534 minHeight = height;
3535 minIndex = j;
3536 }
3537 }
3538
3539 $stacks[minIndex].append($(cards[i]));
3540 }
3541
3542 };
3543
3544 /* Initializes a flow widget, see distribute.scss for generating accompanying css */
3545 function drawResourcesFlowWidget($widget, opts, resources) {
3546 $widget.empty();
3547 var cardSizes = opts.cardSizes || ['6x6'];
3548 var i = 0, j = 0;
3549 var plusone = true; // by default show plusone on resource cards
3550
3551 while (i < resources.length) {
3552 var cardSize = cardSizes[j++ % cardSizes.length];
3553 cardSize = cardSize.replace(/^\s+|\s+$/,'');
Dirk Doughertyc3921652014-05-13 16:55:26 -07003554 // Some card sizes do not get a plusone button, such as where space is constrained
3555 // or for cards commonly embedded in docs (to improve overall page speed).
3556 plusone = !((cardSize == "6x2") || (cardSize == "6x3") ||
3557 (cardSize == "9x2") || (cardSize == "9x3") ||
3558 (cardSize == "12x2") || (cardSize == "12x3"));
3559
3560 // A stack has a third dimension which is the number of stacked items
3561 var isStack = cardSize.match(/(\d+)x(\d+)x(\d+)/);
3562 var stackCount = 0;
3563 var $stackDiv = null;
3564
3565 if (isStack) {
3566 // Create a stack container which should have the dimensions defined
3567 // by the product of the items inside.
3568 $stackDiv = $('<div>').addClass('resource-card-stack resource-card-' + isStack[1]
3569 + 'x' + isStack[2] * isStack[3]) .appendTo($widget);
3570 }
3571
3572 // Build each stack item or just a single item
3573 do {
3574 var resource = resources[i];
3575 //keep url clean for matching and offline mode handling
3576 urlPrefix = resource.url.indexOf("//") > -1 ? "" : toRoot;
3577 var $card = $('<a>')
3578 .addClass('resource-card resource-card-' + cardSize + ' resource-card-' + resource.type)
3579 .attr('href', urlPrefix + resource.url);
3580
3581 if (isStack) {
3582 $card.addClass('resource-card-' + isStack[1] + 'x' + isStack[2]);
3583 if (++stackCount == parseInt(isStack[3])) {
3584 $card.addClass('resource-card-row-stack-last');
3585 stackCount = 0;
3586 }
3587 } else {
3588 stackCount = 0;
3589 }
3590
3591 $card.decorateResourceCard(resource,plusone)
3592 .appendTo($stackDiv || $widget);
3593
3594 } while (++i < resources.length && stackCount > 0);
3595 }
3596 }
3597
3598 /* Build a site map of resources using a section as a root. */
3599 function buildSectionList(opts) {
3600 if (opts.section && SECTION_BY_ID[opts.section]) {
3601 return SECTION_BY_ID[opts.section].sections || [];
3602 }
3603 return [];
3604 }
3605
3606 function buildResourceList(opts) {
3607 var maxResults = opts.maxResults || 100;
3608
3609 var query = opts.query || '';
3610 var expressions = parseResourceQuery(query);
3611 var addedResourceIndices = {};
3612 var results = [];
3613
3614 for (var i = 0; i < expressions.length; i++) {
3615 var clauses = expressions[i];
3616
3617 // build initial set of resources from first clause
3618 var firstClause = clauses[0];
3619 var resources = [];
3620 switch (firstClause.attr) {
3621 case 'type':
3622 resources = ALL_RESOURCES_BY_TYPE[firstClause.value];
3623 break;
3624 case 'lang':
3625 resources = ALL_RESOURCES_BY_LANG[firstClause.value];
3626 break;
3627 case 'tag':
3628 resources = ALL_RESOURCES_BY_TAG[firstClause.value];
3629 break;
3630 case 'collection':
3631 var urls = RESOURCE_COLLECTIONS[firstClause.value].resources || [];
3632 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3633 break;
3634 case 'section':
3635 var urls = SITE_MAP[firstClause.value].sections || [];
3636 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3637 break;
3638 }
3639 // console.log(firstClause.attr + ':' + firstClause.value);
3640 resources = resources || [];
3641
3642 // use additional clauses to filter corpus
3643 if (clauses.length > 1) {
3644 var otherClauses = clauses.slice(1);
3645 resources = resources.filter(getResourceMatchesClausesFilter(otherClauses));
3646 }
3647
3648 // filter out resources already added
3649 if (i > 1) {
3650 resources = resources.filter(getResourceNotAlreadyAddedFilter(addedResourceIndices));
3651 }
3652
3653 // add to list of already added indices
3654 for (var j = 0; j < resources.length; j++) {
3655 // console.log(resources[j].title);
3656 addedResourceIndices[resources[j].index] = 1;
3657 }
3658
3659 // concat to final results list
3660 results = results.concat(resources);
3661 }
3662
3663 if (opts.sortOrder && results.length) {
3664 var attr = opts.sortOrder;
3665
3666 if (opts.sortOrder == 'random') {
3667 var i = results.length, j, temp;
3668 while (--i) {
3669 j = Math.floor(Math.random() * (i + 1));
3670 temp = results[i];
3671 results[i] = results[j];
3672 results[j] = temp;
3673 }
3674 } else {
3675 var desc = attr.charAt(0) == '-';
3676 if (desc) {
3677 attr = attr.substring(1);
3678 }
3679 results = results.sort(function(x,y) {
3680 return (desc ? -1 : 1) * (parseInt(x[attr], 10) - parseInt(y[attr], 10));
3681 });
3682 }
3683 }
3684
3685 results = results.filter(getResourceNotAlreadyAddedFilter(addedPageResources));
3686 results = results.slice(0, maxResults);
3687
3688 for (var j = 0; j < results.length; ++j) {
3689 addedPageResources[results[j].index] = 1;
3690 }
3691
3692 return results;
3693 }
3694
3695
3696 function getResourceNotAlreadyAddedFilter(addedResourceIndices) {
3697 return function(resource) {
3698 return !addedResourceIndices[resource.index];
3699 };
3700 }
3701
3702
3703 function getResourceMatchesClausesFilter(clauses) {
3704 return function(resource) {
3705 return doesResourceMatchClauses(resource, clauses);
3706 };
3707 }
3708
3709
3710 function doesResourceMatchClauses(resource, clauses) {
3711 for (var i = 0; i < clauses.length; i++) {
3712 var map;
3713 switch (clauses[i].attr) {
3714 case 'type':
3715 map = IS_RESOURCE_OF_TYPE[clauses[i].value];
3716 break;
3717 case 'lang':
3718 map = IS_RESOURCE_IN_LANG[clauses[i].value];
3719 break;
3720 case 'tag':
3721 map = IS_RESOURCE_TAGGED[clauses[i].value];
3722 break;
3723 }
3724
3725 if (!map || (!!clauses[i].negative ? map[resource.index] : !map[resource.index])) {
3726 return clauses[i].negative;
3727 }
3728 }
3729 return true;
3730 }
3731
3732
3733 function parseResourceQuery(query) {
3734 // Parse query into array of expressions (expression e.g. 'tag:foo + type:video')
3735 var expressions = [];
3736 var expressionStrs = query.split(',') || [];
3737 for (var i = 0; i < expressionStrs.length; i++) {
3738 var expr = expressionStrs[i] || '';
3739
3740 // Break expression into clauses (clause e.g. 'tag:foo')
3741 var clauses = [];
3742 var clauseStrs = expr.split(/(?=[\+\-])/);
3743 for (var j = 0; j < clauseStrs.length; j++) {
3744 var clauseStr = clauseStrs[j] || '';
3745
3746 // Get attribute and value from clause (e.g. attribute='tag', value='foo')
3747 var parts = clauseStr.split(':');
3748 var clause = {};
3749
3750 clause.attr = parts[0].replace(/^\s+|\s+$/g,'');
3751 if (clause.attr) {
3752 if (clause.attr.charAt(0) == '+') {
3753 clause.attr = clause.attr.substring(1);
3754 } else if (clause.attr.charAt(0) == '-') {
3755 clause.negative = true;
3756 clause.attr = clause.attr.substring(1);
3757 }
3758 }
3759
3760 if (parts.length > 1) {
3761 clause.value = parts[1].replace(/^\s+|\s+$/g,'');
3762 }
3763
3764 clauses.push(clause);
3765 }
3766
3767 if (!clauses.length) {
3768 continue;
3769 }
3770
3771 expressions.push(clauses);
3772 }
3773
3774 return expressions;
3775 }
3776})();
3777
3778(function($) {
3779 /* Simple jquery function to create dom for a standard resource card */
3780 $.fn.decorateResourceCard = function(resource,plusone) {
3781 var section = resource.group || resource.type;
3782 var imgUrl;
3783 if (resource.image) {
3784 //keep url clean for matching and offline mode handling
3785 var urlPrefix = resource.image.indexOf("//") > -1 ? "" : toRoot;
3786 imgUrl = urlPrefix + resource.image;
3787 }
3788 //add linkout logic here. check url or type, assign a class, map to css :after
3789 $('<div>')
3790 .addClass('card-bg')
3791 .css('background-image', 'url(' + (imgUrl || toRoot + 'assets/images/resource-card-default-android.jpg') + ')')
3792 .appendTo(this);
3793 if (!plusone) {
3794 $('<div>').addClass('card-info' + (!resource.summary ? ' empty-desc' : ''))
3795 .append($('<div>').addClass('section').text(section))
3796 .append($('<div>').addClass('title').html(resource.title))
3797 .append($('<div>').addClass('description ellipsis')
3798 .append($('<div>').addClass('text').html(resource.summary))
3799 .append($('<div>').addClass('util')))
3800 .appendTo(this);
3801 } else {
3802 $('<div>').addClass('card-info' + (!resource.summary ? ' empty-desc' : ''))
3803 .append($('<div>').addClass('section').text(section))
3804 .append($('<div>').addClass('title').html(resource.title))
3805 .append($('<div>').addClass('description ellipsis')
3806 .append($('<div>').addClass('text').html(resource.summary))
3807 .append($('<div>').addClass('util')
3808 .append($('<div>').addClass('g-plusone')
3809 .attr('data-size', 'small')
3810 .attr('data-align', 'right')
3811 .attr('data-href', resource.url))))
3812 .appendTo(this);
3813 }
3814
3815 return this;
3816 };
3817
3818 /* Simple jquery function to create dom for a resource section card (menu) */
3819 $.fn.decorateResourceSection = function(section,plusone) {
3820 var resource = section.resource;
3821 //keep url clean for matching and offline mode handling
3822 var urlPrefix = resource.image.indexOf("//") > -1 ? "" : toRoot;
3823 var $base = $('<a>')
3824 .addClass('card-bg')
3825 .attr('href', resource.url)
3826 .append($('<div>').addClass('card-section-icon')
3827 .append($('<div>').addClass('icon'))
3828 .append($('<div>').addClass('section').html(resource.title)))
3829 .appendTo(this);
3830
3831 var $cardInfo = $('<div>').addClass('card-info').appendTo(this);
3832
3833 if (section.sections && section.sections.length) {
3834 // Recurse the section sub-tree to find a resource image.
3835 var stack = [section];
3836
3837 while (stack.length) {
3838 if (stack[0].resource.image) {
3839 $base.css('background-image', 'url(' + urlPrefix + stack[0].resource.image + ')');
3840 break;
3841 }
3842
3843 if (stack[0].sections) {
3844 stack = stack.concat(stack[0].sections);
3845 }
3846
3847 stack.shift();
3848 }
3849
3850 var $ul = $('<ul>')
3851 .appendTo($cardInfo);
3852
3853 var max = section.sections.length > 3 ? 3 : section.sections.length;
3854
3855 for (var i = 0; i < max; ++i) {
3856
3857 var subResource = section.sections[i];
3858 if (!plusone) {
3859 $('<li>')
3860 .append($('<a>').attr('href', subResource.url)
3861 .append($('<div>').addClass('title').html(subResource.title))
3862 .append($('<div>').addClass('description ellipsis')
3863 .append($('<div>').addClass('text').html(subResource.summary))
3864 .append($('<div>').addClass('util'))))
3865 .appendTo($ul);
3866 } else {
3867 $('<li>')
3868 .append($('<a>').attr('href', subResource.url)
3869 .append($('<div>').addClass('title').html(subResource.title))
3870 .append($('<div>').addClass('description ellipsis')
3871 .append($('<div>').addClass('text').html(subResource.summary))
3872 .append($('<div>').addClass('util')
3873 .append($('<div>').addClass('g-plusone')
3874 .attr('data-size', 'small')
3875 .attr('data-align', 'right')
3876 .attr('data-href', resource.url)))))
3877 .appendTo($ul);
3878 }
3879 }
3880
3881 // Add a more row
3882 if (max < section.sections.length) {
3883 $('<li>')
3884 .append($('<a>').attr('href', resource.url)
3885 .append($('<div>')
3886 .addClass('title')
3887 .text('More')))
3888 .appendTo($ul);
3889 }
3890 } else {
3891 // No sub-resources, just render description?
3892 }
3893
3894 return this;
3895 };
3896})(jQuery);
3897/* Calculate the vertical area remaining */
3898(function($) {
3899 $.fn.ellipsisfade= function(lineHeight) {
3900 this.each(function() {
3901 // get element text
3902 var $this = $(this);
3903 var remainingHeight = $this.parent().parent().height();
3904 $this.parent().siblings().each(function ()
3905 {
3906 var h = $(this).height();
3907 remainingHeight = remainingHeight - h;
3908 });
3909
3910 adjustedRemainingHeight = ((remainingHeight)/lineHeight>>0)*lineHeight
3911 $this.parent().css({'height': adjustedRemainingHeight});
3912 $this.css({'height': "auto"});
3913 });
3914
3915 return this;
3916 };
3917}) (jQuery);