blob: c10b84814346e2e8b5d073cbf24156dd85eb421f [file] [log] [blame]
Scott Maine4d8f1b2012-06-21 18:03:05 -07001var classesNav;
2var devdocNav;
3var sidenav;
4var cookie_namespace = 'android_developer';
5var NAV_PREF_TREE = "tree";
6var NAV_PREF_PANELS = "panels";
7var nav_pref;
Scott Maine4d8f1b2012-06-21 18:03:05 -07008var isMobile = false; // true if mobile, so we can adjust some layout
Scott Mainf6145542013-04-01 16:38:11 -07009var mPagePath; // initialized in ready() function
Scott Maine4d8f1b2012-06-21 18:03:05 -070010
Scott Main1b3db112012-07-03 14:06:22 -070011var basePath = getBaseUri(location.pathname);
12var SITE_ROOT = toRoot + basePath.substring(1,basePath.indexOf("/",1));
Scott Main7e447ed2013-02-19 17:22:37 -080013var GOOGLE_DATA; // combined data for google service apis, used for search suggest
Scott Main3b90aff2013-08-01 18:09:35 -070014
Scott Main25e73002013-03-27 15:24:06 -070015// Ensure that all ajax getScript() requests allow caching
16$.ajaxSetup({
17 cache: true
18});
Scott Maine4d8f1b2012-06-21 18:03:05 -070019
20/****** ON LOAD SET UP STUFF *********/
21
Scott Maine4d8f1b2012-06-21 18:03:05 -070022$(document).ready(function() {
Scott Main7e447ed2013-02-19 17:22:37 -080023
Scott Main0e76e7e2013-03-12 10:24:07 -070024 // load json file for JD doc search suggestions
Scott Main719acb42013-12-05 16:05:09 -080025 $.getScript(toRoot + 'jd_lists_unified.js');
Scott Main7e447ed2013-02-19 17:22:37 -080026 // load json file for Android API search suggestions
27 $.getScript(toRoot + 'reference/lists.js');
28 // load json files for Google services API suggestions
Scott Main9f2971d2013-02-26 13:07:41 -080029 $.getScript(toRoot + 'reference/gcm_lists.js', function(data, textStatus, jqxhr) {
Scott Main7e447ed2013-02-19 17:22:37 -080030 // once the GCM json (GCM_DATA) is loaded, load the GMS json (GMS_DATA) and merge the data
31 if(jqxhr.status === 200) {
Scott Main9f2971d2013-02-26 13:07:41 -080032 $.getScript(toRoot + 'reference/gms_lists.js', function(data, textStatus, jqxhr) {
Scott Main7e447ed2013-02-19 17:22:37 -080033 if(jqxhr.status === 200) {
34 // combine GCM and GMS data
35 GOOGLE_DATA = GMS_DATA;
36 var start = GOOGLE_DATA.length;
37 for (var i=0; i<GCM_DATA.length; i++) {
38 GOOGLE_DATA.push({id:start+i, label:GCM_DATA[i].label,
39 link:GCM_DATA[i].link, type:GCM_DATA[i].type});
40 }
41 }
42 });
43 }
44 });
45
Scott Main0e76e7e2013-03-12 10:24:07 -070046 // setup keyboard listener for search shortcut
47 $('body').keyup(function(event) {
48 if (event.which == 191) {
49 $('#search_autocomplete').focus();
50 }
51 });
Scott Main015d6162013-01-29 09:01:52 -080052
Scott Maine4d8f1b2012-06-21 18:03:05 -070053 // init the fullscreen toggle click event
54 $('#nav-swap .fullscreen').click(function(){
55 if ($(this).hasClass('disabled')) {
56 toggleFullscreen(true);
57 } else {
58 toggleFullscreen(false);
59 }
60 });
Scott Main3b90aff2013-08-01 18:09:35 -070061
Scott Maine4d8f1b2012-06-21 18:03:05 -070062 // initialize the divs with custom scrollbars
63 $('.scroll-pane').jScrollPane( {verticalGutter:0} );
Scott Main3b90aff2013-08-01 18:09:35 -070064
Scott Maine4d8f1b2012-06-21 18:03:05 -070065 // add HRs below all H2s (except for a few other h2 variants)
Scott Mainc29b3f52014-05-30 21:18:30 -070066 $('h2').not('#qv h2')
67 .not('#tb h2')
68 .not('.sidebox h2')
69 .not('#devdoc-nav h2')
70 .not('h2.norule').css({marginBottom:0})
71 .after('<hr/>');
Scott Maine4d8f1b2012-06-21 18:03:05 -070072
73 // set up the search close button
74 $('.search .close').click(function() {
75 $searchInput = $('#search_autocomplete');
76 $searchInput.attr('value', '');
77 $(this).addClass("hide");
78 $("#search-container").removeClass('active');
79 $("#search_autocomplete").blur();
Scott Main0e76e7e2013-03-12 10:24:07 -070080 search_focus_changed($searchInput.get(), false);
81 hideResults();
Scott Maine4d8f1b2012-06-21 18:03:05 -070082 });
83
84 // Set up quicknav
Scott Main3b90aff2013-08-01 18:09:35 -070085 var quicknav_open = false;
Scott Maine4d8f1b2012-06-21 18:03:05 -070086 $("#btn-quicknav").click(function() {
87 if (quicknav_open) {
88 $(this).removeClass('active');
89 quicknav_open = false;
90 collapse();
91 } else {
92 $(this).addClass('active');
93 quicknav_open = true;
94 expand();
95 }
96 })
Scott Main3b90aff2013-08-01 18:09:35 -070097
Scott Maine4d8f1b2012-06-21 18:03:05 -070098 var expand = function() {
99 $('#header-wrap').addClass('quicknav');
100 $('#quicknav').stop().show().animate({opacity:'1'});
101 }
Scott Main3b90aff2013-08-01 18:09:35 -0700102
Scott Maine4d8f1b2012-06-21 18:03:05 -0700103 var collapse = function() {
104 $('#quicknav').stop().animate({opacity:'0'}, 100, function() {
105 $(this).hide();
106 $('#header-wrap').removeClass('quicknav');
107 });
108 }
Scott Main3b90aff2013-08-01 18:09:35 -0700109
110
Scott Maine4d8f1b2012-06-21 18:03:05 -0700111 //Set up search
112 $("#search_autocomplete").focus(function() {
113 $("#search-container").addClass('active');
114 })
115 $("#search-container").mouseover(function() {
116 $("#search-container").addClass('active');
117 $("#search_autocomplete").focus();
118 })
119 $("#search-container").mouseout(function() {
120 if ($("#search_autocomplete").is(":focus")) return;
121 if ($("#search_autocomplete").val() == '') {
122 setTimeout(function(){
123 $("#search-container").removeClass('active');
124 $("#search_autocomplete").blur();
125 },250);
126 }
127 })
128 $("#search_autocomplete").blur(function() {
129 if ($("#search_autocomplete").val() == '') {
130 $("#search-container").removeClass('active');
131 }
132 })
133
Scott Main3b90aff2013-08-01 18:09:35 -0700134
Scott Maine4d8f1b2012-06-21 18:03:05 -0700135 // prep nav expandos
136 var pagePath = document.location.pathname;
137 // account for intl docs by removing the intl/*/ path
138 if (pagePath.indexOf("/intl/") == 0) {
139 pagePath = pagePath.substr(pagePath.indexOf("/",6)); // start after intl/ to get last /
140 }
Scott Mainac2aef52013-02-12 14:15:23 -0800141
Scott Maine4d8f1b2012-06-21 18:03:05 -0700142 if (pagePath.indexOf(SITE_ROOT) == 0) {
143 if (pagePath == '' || pagePath.charAt(pagePath.length - 1) == '/') {
144 pagePath += 'index.html';
145 }
146 }
147
Scott Main01a25452013-02-12 17:32:27 -0800148 // Need a copy of the pagePath before it gets changed in the next block;
149 // it's needed to perform proper tab highlighting in offline docs (see rootDir below)
150 var pagePathOriginal = pagePath;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700151 if (SITE_ROOT.match(/\.\.\//) || SITE_ROOT == '') {
152 // If running locally, SITE_ROOT will be a relative path, so account for that by
153 // finding the relative URL to this page. This will allow us to find links on the page
154 // leading back to this page.
155 var pathParts = pagePath.split('/');
156 var relativePagePathParts = [];
157 var upDirs = (SITE_ROOT.match(/(\.\.\/)+/) || [''])[0].length / 3;
158 for (var i = 0; i < upDirs; i++) {
159 relativePagePathParts.push('..');
160 }
161 for (var i = 0; i < upDirs; i++) {
162 relativePagePathParts.push(pathParts[pathParts.length - (upDirs - i) - 1]);
163 }
164 relativePagePathParts.push(pathParts[pathParts.length - 1]);
165 pagePath = relativePagePathParts.join('/');
166 } else {
167 // Otherwise the page path is already an absolute URL
168 }
169
Scott Mainac2aef52013-02-12 14:15:23 -0800170 // Highlight the header tabs...
171 // highlight Design tab
172 if ($("body").hasClass("design")) {
173 $("#header li.design a").addClass("selected");
Dirk Doughertyc3921652014-05-13 16:55:26 -0700174 $("#sticky-header").addClass("design");
Scott Mainac2aef52013-02-12 14:15:23 -0800175
smain@google.com6040ffa2014-06-13 15:06:23 -0700176 // highlight About tabs
177 } else if ($("body").hasClass("about")) {
178 var rootDir = pagePathOriginal.substring(1,pagePathOriginal.indexOf('/', 1));
179 if (rootDir == "about") {
180 $("#nav-x li.about a").addClass("selected");
181 } else if (rootDir == "wear") {
182 $("#nav-x li.wear a").addClass("selected");
183 } else if (rootDir == "tv") {
184 $("#nav-x li.tv a").addClass("selected");
185 } else if (rootDir == "auto") {
186 $("#nav-x li.auto a").addClass("selected");
187 }
Scott Mainac2aef52013-02-12 14:15:23 -0800188 // highlight Develop tab
189 } else if ($("body").hasClass("develop") || $("body").hasClass("google")) {
190 $("#header li.develop a").addClass("selected");
Dirk Doughertyc3921652014-05-13 16:55:26 -0700191 $("#sticky-header").addClass("develop");
Scott Mainac2aef52013-02-12 14:15:23 -0800192 // In Develop docs, also highlight appropriate sub-tab
Scott Main01a25452013-02-12 17:32:27 -0800193 var rootDir = pagePathOriginal.substring(1,pagePathOriginal.indexOf('/', 1));
Scott Mainac2aef52013-02-12 14:15:23 -0800194 if (rootDir == "training") {
195 $("#nav-x li.training a").addClass("selected");
196 } else if (rootDir == "guide") {
197 $("#nav-x li.guide a").addClass("selected");
198 } else if (rootDir == "reference") {
199 // If the root is reference, but page is also part of Google Services, select Google
200 if ($("body").hasClass("google")) {
201 $("#nav-x li.google a").addClass("selected");
202 } else {
203 $("#nav-x li.reference a").addClass("selected");
204 }
205 } else if ((rootDir == "tools") || (rootDir == "sdk")) {
206 $("#nav-x li.tools a").addClass("selected");
207 } else if ($("body").hasClass("google")) {
208 $("#nav-x li.google a").addClass("selected");
Dirk Dougherty4f7e5152010-09-16 10:43:40 -0700209 } else if ($("body").hasClass("samples")) {
210 $("#nav-x li.samples a").addClass("selected");
Scott Mainac2aef52013-02-12 14:15:23 -0800211 }
212
213 // highlight Distribute tab
214 } else if ($("body").hasClass("distribute")) {
215 $("#header li.distribute a").addClass("selected");
Dirk Doughertyc3921652014-05-13 16:55:26 -0700216 $("#sticky-header").addClass("distribute");
217
218 var baseFrag = pagePathOriginal.indexOf('/', 1) + 1;
219 var secondFrag = pagePathOriginal.substring(baseFrag, pagePathOriginal.indexOf('/', baseFrag));
220 if (secondFrag == "users") {
221 $("#nav-x li.users a").addClass("selected");
222 } else if (secondFrag == "engage") {
223 $("#nav-x li.engage a").addClass("selected");
224 } else if (secondFrag == "monetize") {
225 $("#nav-x li.monetize a").addClass("selected");
226 } else if (secondFrag == "tools") {
227 $("#nav-x li.disttools a").addClass("selected");
228 } else if (secondFrag == "stories") {
229 $("#nav-x li.stories a").addClass("selected");
230 } else if (secondFrag == "essentials") {
231 $("#nav-x li.essentials a").addClass("selected");
232 } else if (secondFrag == "googleplay") {
233 $("#nav-x li.googleplay a").addClass("selected");
234 }
235 } else if ($("body").hasClass("about")) {
236 $("#sticky-header").addClass("about");
Scott Mainb16376f2014-05-21 20:35:47 -0700237 }
Scott Mainac2aef52013-02-12 14:15:23 -0800238
Scott Mainf6145542013-04-01 16:38:11 -0700239 // set global variable so we can highlight the sidenav a bit later (such as for google reference)
240 // and highlight the sidenav
241 mPagePath = pagePath;
242 highlightSidenav();
Dirk Doughertyc3921652014-05-13 16:55:26 -0700243 buildBreadcrumbs();
Scott Mainac2aef52013-02-12 14:15:23 -0800244
Scott Mainf6145542013-04-01 16:38:11 -0700245 // set up prev/next links if they exist
Scott Maine4d8f1b2012-06-21 18:03:05 -0700246 var $selNavLink = $('#nav').find('a[href="' + pagePath + '"]');
Scott Main5a1123e2012-09-26 12:51:28 -0700247 var $selListItem;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700248 if ($selNavLink.length) {
Scott Mainac2aef52013-02-12 14:15:23 -0800249 $selListItem = $selNavLink.closest('li');
Scott Maine4d8f1b2012-06-21 18:03:05 -0700250
251 // set up prev links
252 var $prevLink = [];
253 var $prevListItem = $selListItem.prev('li');
Scott Main3b90aff2013-08-01 18:09:35 -0700254
Scott Maine4d8f1b2012-06-21 18:03:05 -0700255 var crossBoundaries = ($("body.design").length > 0) || ($("body.guide").length > 0) ? true :
256false; // navigate across topic boundaries only in design docs
257 if ($prevListItem.length) {
258 if ($prevListItem.hasClass('nav-section')) {
Scott Main5a1123e2012-09-26 12:51:28 -0700259 // jump to last topic of previous section
260 $prevLink = $prevListItem.find('a:last');
261 } else if (!$selListItem.hasClass('nav-section')) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700262 // jump to previous topic in this section
263 $prevLink = $prevListItem.find('a:eq(0)');
264 }
265 } else {
266 // jump to this section's index page (if it exists)
267 var $parentListItem = $selListItem.parents('li');
268 $prevLink = $selListItem.parents('li').find('a');
Scott Main3b90aff2013-08-01 18:09:35 -0700269
Scott Maine4d8f1b2012-06-21 18:03:05 -0700270 // except if cross boundaries aren't allowed, and we're at the top of a section already
271 // (and there's another parent)
Scott Main3b90aff2013-08-01 18:09:35 -0700272 if (!crossBoundaries && $parentListItem.hasClass('nav-section')
Scott Maine4d8f1b2012-06-21 18:03:05 -0700273 && $selListItem.hasClass('nav-section')) {
274 $prevLink = [];
275 }
276 }
277
Scott Maine4d8f1b2012-06-21 18:03:05 -0700278 // set up next links
279 var $nextLink = [];
Scott Maine4d8f1b2012-06-21 18:03:05 -0700280 var startClass = false;
281 var training = $(".next-class-link").length; // decides whether to provide "next class" link
282 var isCrossingBoundary = false;
Scott Main3b90aff2013-08-01 18:09:35 -0700283
Scott Main1a00f7f2013-10-29 11:11:19 -0700284 if ($selListItem.hasClass('nav-section') && $selListItem.children('div.empty').length == 0) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700285 // we're on an index page, jump to the first topic
Scott Mainb505ca62012-07-26 18:00:14 -0700286 $nextLink = $selListItem.find('ul:eq(0)').find('a:eq(0)');
Scott Maine4d8f1b2012-06-21 18:03:05 -0700287
288 // if there aren't any children, go to the next section (required for About pages)
289 if($nextLink.length == 0) {
290 $nextLink = $selListItem.next('li').find('a');
Scott Mainb505ca62012-07-26 18:00:14 -0700291 } else if ($('.topic-start-link').length) {
292 // as long as there's a child link and there is a "topic start link" (we're on a landing)
293 // then set the landing page "start link" text to be the first doc title
294 $('.topic-start-link').text($nextLink.text().toUpperCase());
Scott Maine4d8f1b2012-06-21 18:03:05 -0700295 }
Scott Main3b90aff2013-08-01 18:09:35 -0700296
Scott Main5a1123e2012-09-26 12:51:28 -0700297 // If the selected page has a description, then it's a class or article homepage
298 if ($selListItem.find('a[description]').length) {
299 // this means we're on a class landing page
Scott Maine4d8f1b2012-06-21 18:03:05 -0700300 startClass = true;
301 }
302 } else {
303 // jump to the next topic in this section (if it exists)
304 $nextLink = $selListItem.next('li').find('a:eq(0)');
Scott Main1a00f7f2013-10-29 11:11:19 -0700305 if ($nextLink.length == 0) {
Scott Main5a1123e2012-09-26 12:51:28 -0700306 isCrossingBoundary = true;
307 // no more topics in this section, jump to the first topic in the next section
308 $nextLink = $selListItem.parents('li:eq(0)').next('li.nav-section').find('a:eq(0)');
309 if (!$nextLink.length) { // Go up another layer to look for next page (lesson > class > course)
310 $nextLink = $selListItem.parents('li:eq(1)').next('li.nav-section').find('a:eq(0)');
Scott Main1a00f7f2013-10-29 11:11:19 -0700311 if ($nextLink.length == 0) {
312 // if that doesn't work, we're at the end of the list, so disable NEXT link
313 $('.next-page-link').attr('href','').addClass("disabled")
314 .click(function() { return false; });
315 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700316 }
317 }
318 }
Scott Main5a1123e2012-09-26 12:51:28 -0700319
320 if (startClass) {
321 $('.start-class-link').attr('href', $nextLink.attr('href')).removeClass("hide");
322
Scott Main3b90aff2013-08-01 18:09:35 -0700323 // if there's no training bar (below the start button),
Scott Main5a1123e2012-09-26 12:51:28 -0700324 // then we need to add a bottom border to button
325 if (!$("#tb").length) {
326 $('.start-class-link').css({'border-bottom':'1px solid #DADADA'});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700327 }
Scott Main5a1123e2012-09-26 12:51:28 -0700328 } else if (isCrossingBoundary && !$('body.design').length) { // Design always crosses boundaries
329 $('.content-footer.next-class').show();
330 $('.next-page-link').attr('href','')
331 .removeClass("hide").addClass("disabled")
332 .click(function() { return false; });
Scott Main1a00f7f2013-10-29 11:11:19 -0700333 if ($nextLink.length) {
334 $('.next-class-link').attr('href',$nextLink.attr('href'))
335 .removeClass("hide").append($nextLink.html());
336 $('.next-class-link').find('.new').empty();
337 }
Scott Main5a1123e2012-09-26 12:51:28 -0700338 } else {
339 $('.next-page-link').attr('href', $nextLink.attr('href')).removeClass("hide");
340 }
341
342 if (!startClass && $prevLink.length) {
343 var prevHref = $prevLink.attr('href');
344 if (prevHref == SITE_ROOT + 'index.html') {
345 // Don't show Previous when it leads to the homepage
346 } else {
347 $('.prev-page-link').attr('href', $prevLink.attr('href')).removeClass("hide");
348 }
Scott Main3b90aff2013-08-01 18:09:35 -0700349 }
Scott Main5a1123e2012-09-26 12:51:28 -0700350
351 // If this is a training 'article', there should be no prev/next nav
352 // ... if the grandparent is the "nav" ... and it has no child list items...
353 if (training && $selListItem.parents('ul').eq(1).is('[id="nav"]') &&
354 !$selListItem.find('li').length) {
355 $('.next-page-link,.prev-page-link').attr('href','').addClass("disabled")
356 .click(function() { return false; });
Scott Maine4d8f1b2012-06-21 18:03:05 -0700357 }
Scott Main3b90aff2013-08-01 18:09:35 -0700358
Scott Maine4d8f1b2012-06-21 18:03:05 -0700359 }
Scott Main3b90aff2013-08-01 18:09:35 -0700360
361
362
Scott Main5a1123e2012-09-26 12:51:28 -0700363 // Set up the course landing pages for Training with class names and descriptions
364 if ($('body.trainingcourse').length) {
365 var $classLinks = $selListItem.find('ul li a').not('#nav .nav-section .nav-section ul a');
Scott Maine7d75352014-05-22 15:50:56 -0700366
367 // create an array for all the class descriptions
368 var $classDescriptions = new Array($classLinks.length);
369 var lang = getLangPref();
370 $classLinks.each(function(index) {
371 var langDescr = $(this).attr(lang + "-description");
372 if (typeof langDescr !== 'undefined' && langDescr !== false) {
373 // if there's a class description in the selected language, use that
374 $classDescriptions[index] = langDescr;
375 } else {
376 // otherwise, use the default english description
377 $classDescriptions[index] = $(this).attr("description");
378 }
379 });
Scott Main3b90aff2013-08-01 18:09:35 -0700380
Scott Main5a1123e2012-09-26 12:51:28 -0700381 var $olClasses = $('<ol class="class-list"></ol>');
382 var $liClass;
383 var $imgIcon;
384 var $h2Title;
385 var $pSummary;
386 var $olLessons;
387 var $liLesson;
388 $classLinks.each(function(index) {
389 $liClass = $('<li></li>');
390 $h2Title = $('<a class="title" href="'+$(this).attr('href')+'"><h2>' + $(this).html()+'</h2><span></span></a>');
Scott Maine7d75352014-05-22 15:50:56 -0700391 $pSummary = $('<p class="description">' + $classDescriptions[index] + '</p>');
Scott Main3b90aff2013-08-01 18:09:35 -0700392
Scott Main5a1123e2012-09-26 12:51:28 -0700393 $olLessons = $('<ol class="lesson-list"></ol>');
Scott Main3b90aff2013-08-01 18:09:35 -0700394
Scott Main5a1123e2012-09-26 12:51:28 -0700395 $lessons = $(this).closest('li').find('ul li a');
Scott Main3b90aff2013-08-01 18:09:35 -0700396
Scott Main5a1123e2012-09-26 12:51:28 -0700397 if ($lessons.length) {
Scott Main3b90aff2013-08-01 18:09:35 -0700398 $imgIcon = $('<img src="'+toRoot+'assets/images/resource-tutorial.png" '
399 + ' width="64" height="64" alt=""/>');
Scott Main5a1123e2012-09-26 12:51:28 -0700400 $lessons.each(function(index) {
401 $olLessons.append('<li><a href="'+$(this).attr('href')+'">' + $(this).html()+'</a></li>');
402 });
403 } else {
Scott Main3b90aff2013-08-01 18:09:35 -0700404 $imgIcon = $('<img src="'+toRoot+'assets/images/resource-article.png" '
405 + ' width="64" height="64" alt=""/>');
Scott Main5a1123e2012-09-26 12:51:28 -0700406 $pSummary.addClass('article');
407 }
408
409 $liClass.append($h2Title).append($imgIcon).append($pSummary).append($olLessons);
410 $olClasses.append($liClass);
411 });
412 $('.jd-descr').append($olClasses);
413 }
414
Scott Maine4d8f1b2012-06-21 18:03:05 -0700415 // Set up expand/collapse behavior
Scott Mainad08f072013-08-20 16:49:57 -0700416 initExpandableNavItems("#nav");
Scott Main3b90aff2013-08-01 18:09:35 -0700417
Scott Main3b90aff2013-08-01 18:09:35 -0700418
Scott Maine4d8f1b2012-06-21 18:03:05 -0700419 $(".scroll-pane").scroll(function(event) {
420 event.preventDefault();
421 return false;
422 });
423
424 /* Resize nav height when window height changes */
425 $(window).resize(function() {
426 if ($('#side-nav').length == 0) return;
427 var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
428 setNavBarLeftPos(); // do this even if sidenav isn't fixed because it could become fixed
429 // make sidenav behave when resizing the window and side-scolling is a concern
Scott Mainf5257812014-05-22 17:26:38 -0700430 if (sticky) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700431 if ((stylesheet.attr("disabled") == "disabled") || stylesheet.length == 0) {
432 updateSideNavPosition();
433 } else {
434 updateSidenavFullscreenWidth();
435 }
436 }
437 resizeNav();
438 });
439
440
Scott Maine4d8f1b2012-06-21 18:03:05 -0700441 var navBarLeftPos;
442 if ($('#devdoc-nav').length) {
443 setNavBarLeftPos();
444 }
445
446
Scott Maine4d8f1b2012-06-21 18:03:05 -0700447 // Set up play-on-hover <video> tags.
448 $('video.play-on-hover').bind('click', function(){
449 $(this).get(0).load(); // in case the video isn't seekable
450 $(this).get(0).play();
451 });
452
453 // Set up tooltips
454 var TOOLTIP_MARGIN = 10;
Scott Maindb3678b2012-10-23 14:13:41 -0700455 $('acronym,.tooltip-link').each(function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700456 var $target = $(this);
457 var $tooltip = $('<div>')
458 .addClass('tooltip-box')
Scott Maindb3678b2012-10-23 14:13:41 -0700459 .append($target.attr('title'))
Scott Maine4d8f1b2012-06-21 18:03:05 -0700460 .hide()
461 .appendTo('body');
462 $target.removeAttr('title');
463
464 $target.hover(function() {
465 // in
466 var targetRect = $target.offset();
467 targetRect.width = $target.width();
468 targetRect.height = $target.height();
469
470 $tooltip.css({
471 left: targetRect.left,
472 top: targetRect.top + targetRect.height + TOOLTIP_MARGIN
473 });
474 $tooltip.addClass('below');
475 $tooltip.show();
476 }, function() {
477 // out
478 $tooltip.hide();
479 });
480 });
481
482 // Set up <h2> deeplinks
483 $('h2').click(function() {
484 var id = $(this).attr('id');
485 if (id) {
486 document.location.hash = id;
487 }
488 });
489
490 //Loads the +1 button
491 var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
492 po.src = 'https://apis.google.com/js/plusone.js';
493 var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
494
495
Scott Main3b90aff2013-08-01 18:09:35 -0700496 // Revise the sidenav widths to make room for the scrollbar
Scott Maine4d8f1b2012-06-21 18:03:05 -0700497 // which avoids the visible width from changing each time the bar appears
498 var $sidenav = $("#side-nav");
499 var sidenav_width = parseInt($sidenav.innerWidth());
Scott Main3b90aff2013-08-01 18:09:35 -0700500
Scott Maine4d8f1b2012-06-21 18:03:05 -0700501 $("#devdoc-nav #nav").css("width", sidenav_width - 4 + "px"); // 4px is scrollbar width
502
503
504 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
Scott Main3b90aff2013-08-01 18:09:35 -0700505
Scott Maine4d8f1b2012-06-21 18:03:05 -0700506 if ($(".scroll-pane").length > 1) {
507 // Check if there's a user preference for the panel heights
508 var cookieHeight = readCookie("reference_height");
509 if (cookieHeight) {
510 restoreHeight(cookieHeight);
511 }
512 }
Scott Main3b90aff2013-08-01 18:09:35 -0700513
Scott Main06f3f2c2014-05-30 11:23:00 -0700514 // Resize once loading is finished
Scott Maine4d8f1b2012-06-21 18:03:05 -0700515 resizeNav();
Scott Main06f3f2c2014-05-30 11:23:00 -0700516 // Check if there's an anchor that we need to scroll into view.
517 // A delay is needed, because some browsers do not immediately scroll down to the anchor
518 window.setTimeout(offsetScrollForSticky, 100);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700519
Scott Main015d6162013-01-29 09:01:52 -0800520 /* init the language selector based on user cookie for lang */
521 loadLangPref();
522 changeNavLang(getLangPref());
523
524 /* setup event handlers to ensure the overflow menu is visible while picking lang */
525 $("#language select")
526 .mousedown(function() {
527 $("div.morehover").addClass("hover"); })
528 .blur(function() {
529 $("div.morehover").removeClass("hover"); });
530
531 /* some global variable setup */
532 resizePackagesNav = $("#resize-packages-nav");
533 classesNav = $("#classes-nav");
534 devdocNav = $("#devdoc-nav");
535
536 var cookiePath = "";
537 if (location.href.indexOf("/reference/") != -1) {
538 cookiePath = "reference_";
539 } else if (location.href.indexOf("/guide/") != -1) {
540 cookiePath = "guide_";
541 } else if (location.href.indexOf("/tools/") != -1) {
542 cookiePath = "tools_";
543 } else if (location.href.indexOf("/training/") != -1) {
544 cookiePath = "training_";
545 } else if (location.href.indexOf("/design/") != -1) {
546 cookiePath = "design_";
547 } else if (location.href.indexOf("/distribute/") != -1) {
548 cookiePath = "distribute_";
549 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700550
551});
Scott Main7e447ed2013-02-19 17:22:37 -0800552// END of the onload event
Scott Maine4d8f1b2012-06-21 18:03:05 -0700553
554
Scott Mainad08f072013-08-20 16:49:57 -0700555function initExpandableNavItems(rootTag) {
556 $(rootTag + ' li.nav-section .nav-section-header').click(function() {
557 var section = $(this).closest('li.nav-section');
558 if (section.hasClass('expanded')) {
Scott Mainf0093852013-08-22 11:37:11 -0700559 /* hide me and descendants */
560 section.find('ul').slideUp(250, function() {
561 // remove 'expanded' class from my section and any children
Scott Mainad08f072013-08-20 16:49:57 -0700562 section.closest('li').removeClass('expanded');
Scott Mainf0093852013-08-22 11:37:11 -0700563 $('li.nav-section', section).removeClass('expanded');
Scott Mainad08f072013-08-20 16:49:57 -0700564 resizeNav();
565 });
566 } else {
567 /* show me */
568 // first hide all other siblings
Scott Main70557ee2013-10-30 14:47:40 -0700569 var $others = $('li.nav-section.expanded', $(this).closest('ul')).not('.sticky');
Scott Mainad08f072013-08-20 16:49:57 -0700570 $others.removeClass('expanded').children('ul').slideUp(250);
571
572 // now expand me
573 section.closest('li').addClass('expanded');
574 section.children('ul').slideDown(250, function() {
575 resizeNav();
576 });
577 }
578 });
Scott Mainf0093852013-08-22 11:37:11 -0700579
580 // Stop expand/collapse behavior when clicking on nav section links
581 // (since we're navigating away from the page)
582 // This selector captures the first instance of <a>, but not those with "#" as the href.
583 $('.nav-section-header').find('a:eq(0)').not('a[href="#"]').click(function(evt) {
584 window.location.href = $(this).attr('href');
585 return false;
586 });
Scott Mainad08f072013-08-20 16:49:57 -0700587}
588
Dirk Doughertyc3921652014-05-13 16:55:26 -0700589
590/** Create the list of breadcrumb links in the sticky header */
591function buildBreadcrumbs() {
592 var $breadcrumbUl = $("#sticky-header ul.breadcrumb");
593 // Add the secondary horizontal nav item, if provided
594 var $selectedSecondNav = $("div#nav-x ul.nav-x a.selected").clone().removeClass("selected");
595 if ($selectedSecondNav.length) {
596 $breadcrumbUl.prepend($("<li>").append($selectedSecondNav))
597 }
598 // Add the primary horizontal nav
599 var $selectedFirstNav = $("div#header-wrap ul.nav-x a.selected").clone().removeClass("selected");
600 // If there's no header nav item, use the logo link and title from alt text
601 if ($selectedFirstNav.length < 1) {
602 $selectedFirstNav = $("<a>")
603 .attr('href', $("div#header .logo a").attr('href'))
604 .text($("div#header .logo img").attr('alt'));
605 }
606 $breadcrumbUl.prepend($("<li>").append($selectedFirstNav));
607}
608
609
610
Scott Maine624b3f2013-09-12 12:56:41 -0700611/** Highlight the current page in sidenav, expanding children as appropriate */
Scott Mainf6145542013-04-01 16:38:11 -0700612function highlightSidenav() {
Scott Maine624b3f2013-09-12 12:56:41 -0700613 // if something is already highlighted, undo it. This is for dynamic navigation (Samples index)
614 if ($("ul#nav li.selected").length) {
615 unHighlightSidenav();
616 }
617 // look for URL in sidenav, including the hash
618 var $selNavLink = $('#nav').find('a[href="' + mPagePath + location.hash + '"]');
619
620 // If the selNavLink is still empty, look for it without the hash
621 if ($selNavLink.length == 0) {
622 $selNavLink = $('#nav').find('a[href="' + mPagePath + '"]');
623 }
624
Scott Mainf6145542013-04-01 16:38:11 -0700625 var $selListItem;
626 if ($selNavLink.length) {
Scott Mainf6145542013-04-01 16:38:11 -0700627 // Find this page's <li> in sidenav and set selected
628 $selListItem = $selNavLink.closest('li');
629 $selListItem.addClass('selected');
Scott Main3b90aff2013-08-01 18:09:35 -0700630
Scott Mainf6145542013-04-01 16:38:11 -0700631 // Traverse up the tree and expand all parent nav-sections
632 $selNavLink.parents('li.nav-section').each(function() {
633 $(this).addClass('expanded');
634 $(this).children('ul').show();
635 });
636 }
637}
638
Scott Maine624b3f2013-09-12 12:56:41 -0700639function unHighlightSidenav() {
640 $("ul#nav li.selected").removeClass("selected");
641 $('ul#nav li.nav-section.expanded').removeClass('expanded').children('ul').hide();
642}
Scott Maine4d8f1b2012-06-21 18:03:05 -0700643
644function toggleFullscreen(enable) {
645 var delay = 20;
646 var enabled = true;
647 var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
648 if (enable) {
649 // Currently NOT USING fullscreen; enable fullscreen
650 stylesheet.removeAttr('disabled');
651 $('#nav-swap .fullscreen').removeClass('disabled');
652 $('#devdoc-nav').css({left:''});
653 setTimeout(updateSidenavFullscreenWidth,delay); // need to wait a moment for css to switch
654 enabled = true;
655 } else {
656 // Currently USING fullscreen; disable fullscreen
657 stylesheet.attr('disabled', 'disabled');
658 $('#nav-swap .fullscreen').addClass('disabled');
659 setTimeout(updateSidenavFixedWidth,delay); // need to wait a moment for css to switch
660 enabled = false;
661 }
662 writeCookie("fullscreen", enabled, null, null);
663 setNavBarLeftPos();
664 resizeNav(delay);
665 updateSideNavPosition();
666 setTimeout(initSidenavHeightResize,delay);
667}
668
669
670function setNavBarLeftPos() {
671 navBarLeftPos = $('#body-content').offset().left;
672}
673
674
675function updateSideNavPosition() {
676 var newLeft = $(window).scrollLeft() - navBarLeftPos;
677 $('#devdoc-nav').css({left: -newLeft});
678 $('#devdoc-nav .totop').css({left: -(newLeft - parseInt($('#side-nav').css('margin-left')))});
679}
Scott Main3b90aff2013-08-01 18:09:35 -0700680
Scott Maine4d8f1b2012-06-21 18:03:05 -0700681// TODO: use $(document).ready instead
682function addLoadEvent(newfun) {
683 var current = window.onload;
684 if (typeof window.onload != 'function') {
685 window.onload = newfun;
686 } else {
687 window.onload = function() {
688 current();
689 newfun();
690 }
691 }
692}
693
694var agent = navigator['userAgent'].toLowerCase();
695// If a mobile phone, set flag and do mobile setup
696if ((agent.indexOf("mobile") != -1) || // android, iphone, ipod
697 (agent.indexOf("blackberry") != -1) ||
698 (agent.indexOf("webos") != -1) ||
699 (agent.indexOf("mini") != -1)) { // opera mini browsers
700 isMobile = true;
701}
702
703
Scott Main498d7102013-08-21 15:47:38 -0700704$(document).ready(function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700705 $("pre:not(.no-pretty-print)").addClass("prettyprint");
706 prettyPrint();
Scott Main498d7102013-08-21 15:47:38 -0700707});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700708
Scott Maine4d8f1b2012-06-21 18:03:05 -0700709
710
711
712/* ######### RESIZE THE SIDENAV HEIGHT ########## */
713
714function resizeNav(delay) {
715 var $nav = $("#devdoc-nav");
716 var $window = $(window);
717 var navHeight;
Scott Main3b90aff2013-08-01 18:09:35 -0700718
Scott Maine4d8f1b2012-06-21 18:03:05 -0700719 // Get the height of entire window and the total header height.
720 // Then figure out based on scroll position whether the header is visible
721 var windowHeight = $window.height();
722 var scrollTop = $window.scrollTop();
Dirk Doughertyc3921652014-05-13 16:55:26 -0700723 var headerHeight = $('#header-wrapper').outerHeight();
724 var headerVisible = scrollTop < stickyTop;
Scott Main3b90aff2013-08-01 18:09:35 -0700725
726 // get the height of space between nav and top of window.
Scott Maine4d8f1b2012-06-21 18:03:05 -0700727 // Could be either margin or top position, depending on whether the nav is fixed.
Scott Main3b90aff2013-08-01 18:09:35 -0700728 var topMargin = (parseInt($nav.css('margin-top')) || parseInt($nav.css('top'))) + 1;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700729 // add 1 for the #side-nav bottom margin
Scott Main3b90aff2013-08-01 18:09:35 -0700730
Scott Maine4d8f1b2012-06-21 18:03:05 -0700731 // Depending on whether the header is visible, set the side nav's height.
732 if (headerVisible) {
733 // The sidenav height grows as the header goes off screen
Dirk Doughertyc3921652014-05-13 16:55:26 -0700734 navHeight = windowHeight - (headerHeight - scrollTop) - topMargin;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700735 } else {
736 // Once header is off screen, the nav height is almost full window height
737 navHeight = windowHeight - topMargin;
738 }
Scott Main3b90aff2013-08-01 18:09:35 -0700739
740
741
Scott Maine4d8f1b2012-06-21 18:03:05 -0700742 $scrollPanes = $(".scroll-pane");
743 if ($scrollPanes.length > 1) {
744 // subtract the height of the api level widget and nav swapper from the available nav height
745 navHeight -= ($('#api-nav-header').outerHeight(true) + $('#nav-swap').outerHeight(true));
Scott Main3b90aff2013-08-01 18:09:35 -0700746
Scott Maine4d8f1b2012-06-21 18:03:05 -0700747 $("#swapper").css({height:navHeight + "px"});
748 if ($("#nav-tree").is(":visible")) {
749 $("#nav-tree").css({height:navHeight});
750 }
Scott Main3b90aff2013-08-01 18:09:35 -0700751
752 var classesHeight = navHeight - parseInt($("#resize-packages-nav").css("height")) - 10 + "px";
Scott Maine4d8f1b2012-06-21 18:03:05 -0700753 //subtract 10px to account for drag bar
Scott Main3b90aff2013-08-01 18:09:35 -0700754
755 // if the window becomes small enough to make the class panel height 0,
Scott Maine4d8f1b2012-06-21 18:03:05 -0700756 // then the package panel should begin to shrink
757 if (parseInt(classesHeight) <= 0) {
758 $("#resize-packages-nav").css({height:navHeight - 10}); //subtract 10px for drag bar
759 $("#packages-nav").css({height:navHeight - 10});
760 }
Scott Main3b90aff2013-08-01 18:09:35 -0700761
Scott Maine4d8f1b2012-06-21 18:03:05 -0700762 $("#classes-nav").css({'height':classesHeight, 'margin-top':'10px'});
763 $("#classes-nav .jspContainer").css({height:classesHeight});
Scott Main3b90aff2013-08-01 18:09:35 -0700764
765
Scott Maine4d8f1b2012-06-21 18:03:05 -0700766 } else {
767 $nav.height(navHeight);
768 }
Scott Main3b90aff2013-08-01 18:09:35 -0700769
Scott Maine4d8f1b2012-06-21 18:03:05 -0700770 if (delay) {
771 updateFromResize = true;
772 delayedReInitScrollbars(delay);
773 } else {
774 reInitScrollbars();
775 }
Scott Main3b90aff2013-08-01 18:09:35 -0700776
Scott Maine4d8f1b2012-06-21 18:03:05 -0700777}
778
779var updateScrollbars = false;
780var updateFromResize = false;
781
782/* Re-initialize the scrollbars to account for changed nav size.
783 * This method postpones the actual update by a 1/4 second in order to optimize the
784 * scroll performance while the header is still visible, because re-initializing the
785 * scroll panes is an intensive process.
786 */
787function delayedReInitScrollbars(delay) {
788 // If we're scheduled for an update, but have received another resize request
789 // before the scheduled resize has occured, just ignore the new request
790 // (and wait for the scheduled one).
791 if (updateScrollbars && updateFromResize) {
792 updateFromResize = false;
793 return;
794 }
Scott Main3b90aff2013-08-01 18:09:35 -0700795
Scott Maine4d8f1b2012-06-21 18:03:05 -0700796 // We're scheduled for an update and the update request came from this method's setTimeout
797 if (updateScrollbars && !updateFromResize) {
798 reInitScrollbars();
799 updateScrollbars = false;
800 } else {
801 updateScrollbars = true;
802 updateFromResize = false;
803 setTimeout('delayedReInitScrollbars()',delay);
804 }
805}
806
807/* Re-initialize the scrollbars to account for changed nav size. */
808function reInitScrollbars() {
809 var pane = $(".scroll-pane").each(function(){
810 var api = $(this).data('jsp');
811 if (!api) { setTimeout(reInitScrollbars,300); return;}
812 api.reinitialise( {verticalGutter:0} );
Scott Main3b90aff2013-08-01 18:09:35 -0700813 });
Scott Maine4d8f1b2012-06-21 18:03:05 -0700814 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
815}
816
817
818/* Resize the height of the nav panels in the reference,
819 * and save the new size to a cookie */
820function saveNavPanels() {
821 var basePath = getBaseUri(location.pathname);
822 var section = basePath.substring(1,basePath.indexOf("/",1));
823 writeCookie("height", resizePackagesNav.css("height"), section, null);
824}
825
826
827
828function restoreHeight(packageHeight) {
829 $("#resize-packages-nav").height(packageHeight);
830 $("#packages-nav").height(packageHeight);
831 // var classesHeight = navHeight - packageHeight;
832 // $("#classes-nav").css({height:classesHeight});
833 // $("#classes-nav .jspContainer").css({height:classesHeight});
834}
835
836
837
838/* ######### END RESIZE THE SIDENAV HEIGHT ########## */
839
840
841
842
843
Scott Main3b90aff2013-08-01 18:09:35 -0700844/** Scroll the jScrollPane to make the currently selected item visible
Scott Maine4d8f1b2012-06-21 18:03:05 -0700845 This is called when the page finished loading. */
846function scrollIntoView(nav) {
847 var $nav = $("#"+nav);
848 var element = $nav.jScrollPane({/* ...settings... */});
849 var api = element.data('jsp');
850
851 if ($nav.is(':visible')) {
852 var $selected = $(".selected", $nav);
Scott Mainbc729572013-07-30 18:00:51 -0700853 if ($selected.length == 0) {
854 // If no selected item found, exit
855 return;
856 }
Scott Main52dd2062013-08-15 12:22:28 -0700857 // get the selected item's offset from its container nav by measuring the item's offset
858 // relative to the document then subtract the container nav's offset relative to the document
859 var selectedOffset = $selected.offset().top - $nav.offset().top;
860 if (selectedOffset > $nav.height() * .8) { // multiply nav height by .8 so we move up the item
861 // if it's more than 80% down the nav
862 // scroll the item up by an amount equal to 80% the container nav's height
863 api.scrollTo(0, selectedOffset - ($nav.height() * .8), false);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700864 }
865 }
866}
867
868
869
870
871
872
873/* Show popup dialogs */
874function showDialog(id) {
875 $dialog = $("#"+id);
876 $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>');
877 $dialog.wrapInner('<div/>');
878 $dialog.removeClass("hide");
879}
880
881
882
883
884
885/* ######### COOKIES! ########## */
886
887function readCookie(cookie) {
888 var myCookie = cookie_namespace+"_"+cookie+"=";
889 if (document.cookie) {
890 var index = document.cookie.indexOf(myCookie);
891 if (index != -1) {
892 var valStart = index + myCookie.length;
893 var valEnd = document.cookie.indexOf(";", valStart);
894 if (valEnd == -1) {
895 valEnd = document.cookie.length;
896 }
897 var val = document.cookie.substring(valStart, valEnd);
898 return val;
899 }
900 }
901 return 0;
902}
903
904function writeCookie(cookie, val, section, expiration) {
905 if (val==undefined) return;
906 section = section == null ? "_" : "_"+section+"_";
907 if (expiration == null) {
908 var date = new Date();
909 date.setTime(date.getTime()+(10*365*24*60*60*1000)); // default expiration is one week
910 expiration = date.toGMTString();
911 }
Scott Main3b90aff2013-08-01 18:09:35 -0700912 var cookieValue = cookie_namespace + section + cookie + "=" + val
Scott Maine4d8f1b2012-06-21 18:03:05 -0700913 + "; expires=" + expiration+"; path=/";
914 document.cookie = cookieValue;
915}
916
917/* ######### END COOKIES! ########## */
918
919
Dirk Doughertyca1230c2014-05-14 20:00:03 -0700920var sticky = false;
Dirk Doughertyc3921652014-05-13 16:55:26 -0700921var stickyTop;
Dirk Doughertyca1230c2014-05-14 20:00:03 -0700922var prevScrollLeft = 0; // used to compare current position to previous position of horiz scroll
Dirk Doughertyc3921652014-05-13 16:55:26 -0700923/* Sets the vertical scoll position at which the sticky bar should appear.
924 This method is called to reset the position when search results appear or hide */
925function setStickyTop() {
926 stickyTop = $('#header-wrapper').outerHeight() - $('#sticky-header').outerHeight();
927}
Scott Maine4d8f1b2012-06-21 18:03:05 -0700928
Dirk Doughertyca1230c2014-05-14 20:00:03 -0700929/*
Scott Mainb16376f2014-05-21 20:35:47 -0700930 * Displays sticky nav bar on pages when dac header scrolls out of view
Dirk Doughertyc3921652014-05-13 16:55:26 -0700931 */
Dirk Doughertyca1230c2014-05-14 20:00:03 -0700932$(window).scroll(function(event) {
933
934 setStickyTop();
935 var hiding = false;
936 var $stickyEl = $('#sticky-header');
937 var $menuEl = $('.menu-container');
938 // Exit if there's no sidenav
939 if ($('#side-nav').length == 0) return;
940 // Exit if the mouse target is a DIV, because that means the event is coming
941 // from a scrollable div and so there's no need to make adjustments to our layout
942 if ($(event.target).nodeName == "DIV") {
943 return;
944 }
945
946 var top = $(window).scrollTop();
947 // we set the navbar fixed when the scroll position is beyond the height of the site header...
948 var shouldBeSticky = top >= stickyTop;
949 // ... except if the document content is shorter than the sidenav height.
950 // (this is necessary to avoid crazy behavior on OSX Lion due to overscroll bouncing)
951 if ($("#doc-col").height() < $("#side-nav").height()) {
952 shouldBeSticky = false;
953 }
Scott Mainf5257812014-05-22 17:26:38 -0700954 // Account for horizontal scroll
955 var scrollLeft = $(window).scrollLeft();
956 // When the sidenav is fixed and user scrolls horizontally, reposition the sidenav to match
957 if (sticky && (scrollLeft != prevScrollLeft)) {
958 updateSideNavPosition();
959 prevScrollLeft = scrollLeft;
960 }
Dirk Doughertyca1230c2014-05-14 20:00:03 -0700961
962 // Don't continue if the header is sufficently far away
963 // (to avoid intensive resizing that slows scrolling)
964 if (sticky == shouldBeSticky) {
965 return;
966 }
Dirk Doughertyca1230c2014-05-14 20:00:03 -0700967
968 // If sticky header visible and position is now near top, hide sticky
969 if (sticky && !shouldBeSticky) {
970 sticky = false;
971 hiding = true;
972 // make the sidenav static again
973 $('#devdoc-nav')
974 .removeClass('fixed')
975 .css({'width':'auto','margin':''})
976 .prependTo('#side-nav');
977 // delay hide the sticky
978 $menuEl.removeClass('sticky-menu');
979 $stickyEl.fadeOut(250);
980 hiding = false;
981
982 // update the sidenaav position for side scrolling
983 updateSideNavPosition();
984 } else if (!sticky && shouldBeSticky) {
985 sticky = true;
986 $stickyEl.fadeIn(10);
987 $menuEl.addClass('sticky-menu');
988
989 // make the sidenav fixed
990 var width = $('#devdoc-nav').width();
991 $('#devdoc-nav')
992 .addClass('fixed')
993 .css({'width':width+'px'})
994 .prependTo('#body-content');
995
996 // update the sidenaav position for side scrolling
997 updateSideNavPosition();
998
999 } else if (hiding && top < 15) {
1000 $menuEl.removeClass('sticky-menu');
1001 $stickyEl.hide();
1002 hiding = false;
1003 }
1004 resizeNav(250); // pass true in order to delay the scrollbar re-initialization for performance
1005});
1006
1007/*
1008 * Manages secion card states and nav resize to conclude loading
1009 */
Dirk Doughertyc3921652014-05-13 16:55:26 -07001010(function() {
1011 $(document).ready(function() {
1012
Dirk Doughertyc3921652014-05-13 16:55:26 -07001013 // Stack hover states
1014 $('.section-card-menu').each(function(index, el) {
1015 var height = $(el).height();
1016 $(el).css({height:height+'px', position:'relative'});
1017 var $cardInfo = $(el).find('.card-info');
1018
1019 $cardInfo.css({position: 'absolute', bottom:'0px', left:'0px', right:'0px', overflow:'visible'});
1020 });
1021
Dirk Doughertyc3921652014-05-13 16:55:26 -07001022 });
1023
1024})();
1025
Scott Maine4d8f1b2012-06-21 18:03:05 -07001026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
Scott Maind7026f72013-06-17 15:08:49 -07001039/* MISC LIBRARY FUNCTIONS */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001040
1041
1042
1043
1044
1045function toggle(obj, slide) {
1046 var ul = $("ul:first", obj);
1047 var li = ul.parent();
1048 if (li.hasClass("closed")) {
1049 if (slide) {
1050 ul.slideDown("fast");
1051 } else {
1052 ul.show();
1053 }
1054 li.removeClass("closed");
1055 li.addClass("open");
1056 $(".toggle-img", li).attr("title", "hide pages");
1057 } else {
1058 ul.slideUp("fast");
1059 li.removeClass("open");
1060 li.addClass("closed");
1061 $(".toggle-img", li).attr("title", "show pages");
1062 }
1063}
1064
1065
Scott Maine4d8f1b2012-06-21 18:03:05 -07001066function buildToggleLists() {
1067 $(".toggle-list").each(
1068 function(i) {
1069 $("div:first", this).append("<a class='toggle-img' href='#' title='show pages' onClick='toggle(this.parentNode.parentNode, true); return false;'></a>");
1070 $(this).addClass("closed");
1071 });
1072}
1073
1074
1075
Scott Maind7026f72013-06-17 15:08:49 -07001076function hideNestedItems(list, toggle) {
1077 $list = $(list);
1078 // hide nested lists
1079 if($list.hasClass('showing')) {
1080 $("li ol", $list).hide('fast');
1081 $list.removeClass('showing');
1082 // show nested lists
1083 } else {
1084 $("li ol", $list).show('fast');
1085 $list.addClass('showing');
1086 }
1087 $(".more,.less",$(toggle)).toggle();
1088}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117/* REFERENCE NAV SWAP */
1118
1119
1120function getNavPref() {
1121 var v = readCookie('reference_nav');
1122 if (v != NAV_PREF_TREE) {
1123 v = NAV_PREF_PANELS;
1124 }
1125 return v;
1126}
1127
1128function chooseDefaultNav() {
1129 nav_pref = getNavPref();
1130 if (nav_pref == NAV_PREF_TREE) {
1131 $("#nav-panels").toggle();
1132 $("#panel-link").toggle();
1133 $("#nav-tree").toggle();
1134 $("#tree-link").toggle();
1135 }
1136}
1137
1138function swapNav() {
1139 if (nav_pref == NAV_PREF_TREE) {
1140 nav_pref = NAV_PREF_PANELS;
1141 } else {
1142 nav_pref = NAV_PREF_TREE;
1143 init_default_navtree(toRoot);
1144 }
1145 var date = new Date();
1146 date.setTime(date.getTime()+(10*365*24*60*60*1000)); // keep this for 10 years
1147 writeCookie("nav", nav_pref, "reference", date.toGMTString());
1148
1149 $("#nav-panels").toggle();
1150 $("#panel-link").toggle();
1151 $("#nav-tree").toggle();
1152 $("#tree-link").toggle();
Scott Main3b90aff2013-08-01 18:09:35 -07001153
Scott Maine4d8f1b2012-06-21 18:03:05 -07001154 resizeNav();
1155
1156 // Gross nasty hack to make tree view show up upon first swap by setting height manually
1157 $("#nav-tree .jspContainer:visible")
1158 .css({'height':$("#nav-tree .jspContainer .jspPane").height() +'px'});
1159 // Another nasty hack to make the scrollbar appear now that we have height
1160 resizeNav();
Scott Main3b90aff2013-08-01 18:09:35 -07001161
Scott Maine4d8f1b2012-06-21 18:03:05 -07001162 if ($("#nav-tree").is(':visible')) {
1163 scrollIntoView("nav-tree");
1164 } else {
1165 scrollIntoView("packages-nav");
1166 scrollIntoView("classes-nav");
1167 }
1168}
1169
1170
1171
Scott Mainf5089842012-08-14 16:31:07 -07001172/* ############################################ */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001173/* ########## LOCALIZATION ############ */
Scott Mainf5089842012-08-14 16:31:07 -07001174/* ############################################ */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001175
1176function getBaseUri(uri) {
1177 var intlUrl = (uri.substring(0,6) == "/intl/");
1178 if (intlUrl) {
1179 base = uri.substring(uri.indexOf('intl/')+5,uri.length);
1180 base = base.substring(base.indexOf('/')+1, base.length);
1181 //alert("intl, returning base url: /" + base);
1182 return ("/" + base);
1183 } else {
1184 //alert("not intl, returning uri as found.");
1185 return uri;
1186 }
1187}
1188
1189function requestAppendHL(uri) {
1190//append "?hl=<lang> to an outgoing request (such as to blog)
1191 var lang = getLangPref();
1192 if (lang) {
1193 var q = 'hl=' + lang;
1194 uri += '?' + q;
1195 window.location = uri;
1196 return false;
1197 } else {
1198 return true;
1199 }
1200}
1201
1202
Scott Maine4d8f1b2012-06-21 18:03:05 -07001203function changeNavLang(lang) {
Scott Main6eb95f12012-10-02 17:12:23 -07001204 var $links = $("#devdoc-nav,#header,#nav-x,.training-nav-top,.content-footer").find("a["+lang+"-lang]");
1205 $links.each(function(i){ // for each link with a translation
1206 var $link = $(this);
1207 if (lang != "en") { // No need to worry about English, because a language change invokes new request
1208 // put the desired language from the attribute as the text
1209 $link.text($link.attr(lang+"-lang"))
Scott Maine4d8f1b2012-06-21 18:03:05 -07001210 }
Scott Main6eb95f12012-10-02 17:12:23 -07001211 });
Scott Maine4d8f1b2012-06-21 18:03:05 -07001212}
1213
Scott Main015d6162013-01-29 09:01:52 -08001214function changeLangPref(lang, submit) {
Scott Maine4d8f1b2012-06-21 18:03:05 -07001215 var date = new Date();
Scott Main3b90aff2013-08-01 18:09:35 -07001216 expires = date.toGMTString(date.setTime(date.getTime()+(10*365*24*60*60*1000)));
Scott Maine4d8f1b2012-06-21 18:03:05 -07001217 // keep this for 50 years
1218 //alert("expires: " + expires)
1219 writeCookie("pref_lang", lang, null, expires);
Scott Main015d6162013-01-29 09:01:52 -08001220
1221 // ####### TODO: Remove this condition once we're stable on devsite #######
1222 // This condition is only needed if we still need to support legacy GAE server
1223 if (devsite) {
1224 // Switch language when on Devsite server
1225 if (submit) {
1226 $("#setlang").submit();
1227 }
1228 } else {
1229 // Switch language when on legacy GAE server
Scott Main015d6162013-01-29 09:01:52 -08001230 if (submit) {
1231 window.location = getBaseUri(location.pathname);
1232 }
Scott Maine4d8f1b2012-06-21 18:03:05 -07001233 }
1234}
1235
1236function loadLangPref() {
1237 var lang = readCookie("pref_lang");
1238 if (lang != 0) {
1239 $("#language").find("option[value='"+lang+"']").attr("selected",true);
1240 }
1241}
1242
1243function getLangPref() {
1244 var lang = $("#language").find(":selected").attr("value");
1245 if (!lang) {
1246 lang = readCookie("pref_lang");
1247 }
1248 return (lang != 0) ? lang : 'en';
1249}
1250
1251/* ########## END LOCALIZATION ############ */
1252
1253
1254
1255
1256
1257
1258/* Used to hide and reveal supplemental content, such as long code samples.
1259 See the companion CSS in android-developer-docs.css */
1260function toggleContent(obj) {
Scott Maindc63dda2013-08-22 16:03:21 -07001261 var div = $(obj).closest(".toggle-content");
1262 var toggleMe = $(".toggle-content-toggleme:eq(0)",div);
Scott Maine4d8f1b2012-06-21 18:03:05 -07001263 if (div.hasClass("closed")) { // if it's closed, open it
1264 toggleMe.slideDown();
Scott Maindc63dda2013-08-22 16:03:21 -07001265 $(".toggle-content-text:eq(0)", obj).toggle();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001266 div.removeClass("closed").addClass("open");
Scott Maindc63dda2013-08-22 16:03:21 -07001267 $(".toggle-content-img:eq(0)", div).attr("title", "hide").attr("src", toRoot
Scott Maine4d8f1b2012-06-21 18:03:05 -07001268 + "assets/images/triangle-opened.png");
1269 } else { // if it's open, close it
1270 toggleMe.slideUp('fast', function() { // Wait until the animation is done before closing arrow
Scott Maindc63dda2013-08-22 16:03:21 -07001271 $(".toggle-content-text:eq(0)", obj).toggle();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001272 div.removeClass("open").addClass("closed");
Scott Maindc63dda2013-08-22 16:03:21 -07001273 div.find(".toggle-content").removeClass("open").addClass("closed")
1274 .find(".toggle-content-toggleme").hide();
Scott Main3b90aff2013-08-01 18:09:35 -07001275 $(".toggle-content-img", div).attr("title", "show").attr("src", toRoot
Scott Maine4d8f1b2012-06-21 18:03:05 -07001276 + "assets/images/triangle-closed.png");
1277 });
1278 }
1279 return false;
1280}
Scott Mainf5089842012-08-14 16:31:07 -07001281
1282
Scott Maindb3678b2012-10-23 14:13:41 -07001283/* New version of expandable content */
1284function toggleExpandable(link,id) {
1285 if($(id).is(':visible')) {
1286 $(id).slideUp();
1287 $(link).removeClass('expanded');
1288 } else {
1289 $(id).slideDown();
1290 $(link).addClass('expanded');
1291 }
1292}
1293
1294function hideExpandable(ids) {
1295 $(ids).slideUp();
Scott Main55d99832012-11-12 23:03:59 -08001296 $(ids).prev('h4').find('a.expandable').removeClass('expanded');
Scott Maindb3678b2012-10-23 14:13:41 -07001297}
1298
Scott Mainf5089842012-08-14 16:31:07 -07001299
1300
1301
1302
Scott Main3b90aff2013-08-01 18:09:35 -07001303/*
Scott Mainf5089842012-08-14 16:31:07 -07001304 * Slideshow 1.0
1305 * Used on /index.html and /develop/index.html for carousel
1306 *
1307 * Sample usage:
1308 * HTML -
1309 * <div class="slideshow-container">
1310 * <a href="" class="slideshow-prev">Prev</a>
1311 * <a href="" class="slideshow-next">Next</a>
1312 * <ul>
1313 * <li class="item"><img src="images/marquee1.jpg"></li>
1314 * <li class="item"><img src="images/marquee2.jpg"></li>
1315 * <li class="item"><img src="images/marquee3.jpg"></li>
1316 * <li class="item"><img src="images/marquee4.jpg"></li>
1317 * </ul>
1318 * </div>
1319 *
1320 * <script type="text/javascript">
1321 * $('.slideshow-container').dacSlideshow({
1322 * auto: true,
1323 * btnPrev: '.slideshow-prev',
1324 * btnNext: '.slideshow-next'
1325 * });
1326 * </script>
1327 *
1328 * Options:
1329 * btnPrev: optional identifier for previous button
1330 * btnNext: optional identifier for next button
Scott Maineb410352013-01-14 19:03:40 -08001331 * btnPause: optional identifier for pause button
Scott Mainf5089842012-08-14 16:31:07 -07001332 * auto: whether or not to auto-proceed
1333 * speed: animation speed
1334 * autoTime: time between auto-rotation
1335 * easing: easing function for transition
1336 * start: item to select by default
1337 * scroll: direction to scroll in
1338 * pagination: whether or not to include dotted pagination
1339 *
1340 */
1341
1342 (function($) {
1343 $.fn.dacSlideshow = function(o) {
Scott Main3b90aff2013-08-01 18:09:35 -07001344
Scott Mainf5089842012-08-14 16:31:07 -07001345 //Options - see above
1346 o = $.extend({
1347 btnPrev: null,
1348 btnNext: null,
Scott Maineb410352013-01-14 19:03:40 -08001349 btnPause: null,
Scott Mainf5089842012-08-14 16:31:07 -07001350 auto: true,
1351 speed: 500,
1352 autoTime: 12000,
1353 easing: null,
1354 start: 0,
1355 scroll: 1,
1356 pagination: true
1357
1358 }, o || {});
Scott Main3b90aff2013-08-01 18:09:35 -07001359
1360 //Set up a carousel for each
Scott Mainf5089842012-08-14 16:31:07 -07001361 return this.each(function() {
1362
1363 var running = false;
1364 var animCss = o.vertical ? "top" : "left";
1365 var sizeCss = o.vertical ? "height" : "width";
1366 var div = $(this);
1367 var ul = $("ul", div);
1368 var tLi = $("li", ul);
Scott Main3b90aff2013-08-01 18:09:35 -07001369 var tl = tLi.size();
Scott Mainf5089842012-08-14 16:31:07 -07001370 var timer = null;
1371
1372 var li = $("li", ul);
1373 var itemLength = li.size();
1374 var curr = o.start;
1375
1376 li.css({float: o.vertical ? "none" : "left"});
1377 ul.css({margin: "0", padding: "0", position: "relative", "list-style-type": "none", "z-index": "1"});
1378 div.css({position: "relative", "z-index": "2", left: "0px"});
1379
1380 var liSize = o.vertical ? height(li) : width(li);
1381 var ulSize = liSize * itemLength;
1382 var divSize = liSize;
1383
1384 li.css({width: li.width(), height: li.height()});
1385 ul.css(sizeCss, ulSize+"px").css(animCss, -(curr*liSize));
1386
1387 div.css(sizeCss, divSize+"px");
Scott Main3b90aff2013-08-01 18:09:35 -07001388
Scott Mainf5089842012-08-14 16:31:07 -07001389 //Pagination
1390 if (o.pagination) {
1391 var pagination = $("<div class='pagination'></div>");
1392 var pag_ul = $("<ul></ul>");
1393 if (tl > 1) {
1394 for (var i=0;i<tl;i++) {
1395 var li = $("<li>"+i+"</li>");
1396 pag_ul.append(li);
1397 if (i==o.start) li.addClass('active');
1398 li.click(function() {
1399 go(parseInt($(this).text()));
1400 })
1401 }
1402 pagination.append(pag_ul);
1403 div.append(pagination);
1404 }
1405 }
Scott Main3b90aff2013-08-01 18:09:35 -07001406
Scott Mainf5089842012-08-14 16:31:07 -07001407 //Previous button
1408 if(o.btnPrev)
1409 $(o.btnPrev).click(function(e) {
1410 e.preventDefault();
1411 return go(curr-o.scroll);
1412 });
1413
1414 //Next button
1415 if(o.btnNext)
1416 $(o.btnNext).click(function(e) {
1417 e.preventDefault();
1418 return go(curr+o.scroll);
1419 });
Scott Maineb410352013-01-14 19:03:40 -08001420
1421 //Pause button
1422 if(o.btnPause)
1423 $(o.btnPause).click(function(e) {
1424 e.preventDefault();
1425 if ($(this).hasClass('paused')) {
1426 startRotateTimer();
1427 } else {
1428 pauseRotateTimer();
1429 }
1430 });
Scott Main3b90aff2013-08-01 18:09:35 -07001431
Scott Mainf5089842012-08-14 16:31:07 -07001432 //Auto rotation
1433 if(o.auto) startRotateTimer();
Scott Main3b90aff2013-08-01 18:09:35 -07001434
Scott Mainf5089842012-08-14 16:31:07 -07001435 function startRotateTimer() {
1436 clearInterval(timer);
1437 timer = setInterval(function() {
1438 if (curr == tl-1) {
1439 go(0);
1440 } else {
Scott Main3b90aff2013-08-01 18:09:35 -07001441 go(curr+o.scroll);
1442 }
Scott Mainf5089842012-08-14 16:31:07 -07001443 }, o.autoTime);
Scott Maineb410352013-01-14 19:03:40 -08001444 $(o.btnPause).removeClass('paused');
1445 }
1446
1447 function pauseRotateTimer() {
1448 clearInterval(timer);
1449 $(o.btnPause).addClass('paused');
Scott Mainf5089842012-08-14 16:31:07 -07001450 }
1451
1452 //Go to an item
1453 function go(to) {
1454 if(!running) {
1455
1456 if(to<0) {
1457 to = itemLength-1;
1458 } else if (to>itemLength-1) {
1459 to = 0;
1460 }
1461 curr = to;
1462
1463 running = true;
1464
1465 ul.animate(
1466 animCss == "left" ? { left: -(curr*liSize) } : { top: -(curr*liSize) } , o.speed, o.easing,
1467 function() {
1468 running = false;
1469 }
1470 );
1471
1472 $(o.btnPrev + "," + o.btnNext).removeClass("disabled");
1473 $( (curr-o.scroll<0 && o.btnPrev)
1474 ||
1475 (curr+o.scroll > itemLength && o.btnNext)
1476 ||
1477 []
1478 ).addClass("disabled");
1479
Scott Main3b90aff2013-08-01 18:09:35 -07001480
Scott Mainf5089842012-08-14 16:31:07 -07001481 var nav_items = $('li', pagination);
1482 nav_items.removeClass('active');
1483 nav_items.eq(to).addClass('active');
Scott Main3b90aff2013-08-01 18:09:35 -07001484
Scott Mainf5089842012-08-14 16:31:07 -07001485
1486 }
1487 if(o.auto) startRotateTimer();
1488 return false;
1489 };
1490 });
1491 };
1492
1493 function css(el, prop) {
1494 return parseInt($.css(el[0], prop)) || 0;
1495 };
1496 function width(el) {
1497 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1498 };
1499 function height(el) {
1500 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1501 };
1502
1503 })(jQuery);
1504
1505
Scott Main3b90aff2013-08-01 18:09:35 -07001506/*
Scott Mainf5089842012-08-14 16:31:07 -07001507 * dacSlideshow 1.0
1508 * Used on develop/index.html for side-sliding tabs
1509 *
1510 * Sample usage:
1511 * HTML -
1512 * <div class="slideshow-container">
1513 * <a href="" class="slideshow-prev">Prev</a>
1514 * <a href="" class="slideshow-next">Next</a>
1515 * <ul>
1516 * <li class="item"><img src="images/marquee1.jpg"></li>
1517 * <li class="item"><img src="images/marquee2.jpg"></li>
1518 * <li class="item"><img src="images/marquee3.jpg"></li>
1519 * <li class="item"><img src="images/marquee4.jpg"></li>
1520 * </ul>
1521 * </div>
1522 *
1523 * <script type="text/javascript">
1524 * $('.slideshow-container').dacSlideshow({
1525 * auto: true,
1526 * btnPrev: '.slideshow-prev',
1527 * btnNext: '.slideshow-next'
1528 * });
1529 * </script>
1530 *
1531 * Options:
1532 * btnPrev: optional identifier for previous button
1533 * btnNext: optional identifier for next button
1534 * auto: whether or not to auto-proceed
1535 * speed: animation speed
1536 * autoTime: time between auto-rotation
1537 * easing: easing function for transition
1538 * start: item to select by default
1539 * scroll: direction to scroll in
1540 * pagination: whether or not to include dotted pagination
1541 *
1542 */
1543 (function($) {
1544 $.fn.dacTabbedList = function(o) {
Scott Main3b90aff2013-08-01 18:09:35 -07001545
Scott Mainf5089842012-08-14 16:31:07 -07001546 //Options - see above
1547 o = $.extend({
1548 speed : 250,
1549 easing: null,
1550 nav_id: null,
1551 frame_id: null
1552 }, o || {});
Scott Main3b90aff2013-08-01 18:09:35 -07001553
1554 //Set up a carousel for each
Scott Mainf5089842012-08-14 16:31:07 -07001555 return this.each(function() {
1556
1557 var curr = 0;
1558 var running = false;
1559 var animCss = "margin-left";
1560 var sizeCss = "width";
1561 var div = $(this);
Scott Main3b90aff2013-08-01 18:09:35 -07001562
Scott Mainf5089842012-08-14 16:31:07 -07001563 var nav = $(o.nav_id, div);
1564 var nav_li = $("li", nav);
Scott Main3b90aff2013-08-01 18:09:35 -07001565 var nav_size = nav_li.size();
Scott Mainf5089842012-08-14 16:31:07 -07001566 var frame = div.find(o.frame_id);
1567 var content_width = $(frame).find('ul').width();
1568 //Buttons
1569 $(nav_li).click(function(e) {
1570 go($(nav_li).index($(this)));
1571 })
Scott Main3b90aff2013-08-01 18:09:35 -07001572
Scott Mainf5089842012-08-14 16:31:07 -07001573 //Go to an item
1574 function go(to) {
1575 if(!running) {
1576 curr = to;
1577 running = true;
1578
1579 frame.animate({ 'margin-left' : -(curr*content_width) }, o.speed, o.easing,
1580 function() {
1581 running = false;
1582 }
1583 );
1584
Scott Main3b90aff2013-08-01 18:09:35 -07001585
Scott Mainf5089842012-08-14 16:31:07 -07001586 nav_li.removeClass('active');
1587 nav_li.eq(to).addClass('active');
Scott Main3b90aff2013-08-01 18:09:35 -07001588
Scott Mainf5089842012-08-14 16:31:07 -07001589
1590 }
1591 return false;
1592 };
1593 });
1594 };
1595
1596 function css(el, prop) {
1597 return parseInt($.css(el[0], prop)) || 0;
1598 };
1599 function width(el) {
1600 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1601 };
1602 function height(el) {
1603 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1604 };
1605
1606 })(jQuery);
1607
1608
1609
1610
1611
1612/* ######################################################## */
1613/* ################ SEARCH SUGGESTIONS ################## */
1614/* ######################################################## */
1615
1616
Scott Main7e447ed2013-02-19 17:22:37 -08001617
Scott Main0e76e7e2013-03-12 10:24:07 -07001618var gSelectedIndex = -1; // the index position of currently highlighted suggestion
1619var gSelectedColumn = -1; // which column of suggestion lists is currently focused
1620
Scott Mainf5089842012-08-14 16:31:07 -07001621var gMatches = new Array();
1622var gLastText = "";
Scott Mainf5089842012-08-14 16:31:07 -07001623var gInitialized = false;
Scott Main7e447ed2013-02-19 17:22:37 -08001624var ROW_COUNT_FRAMEWORK = 20; // max number of results in list
1625var gListLength = 0;
1626
1627
1628var gGoogleMatches = new Array();
1629var ROW_COUNT_GOOGLE = 15; // max number of results in list
1630var gGoogleListLength = 0;
Scott Mainf5089842012-08-14 16:31:07 -07001631
Scott Main0e76e7e2013-03-12 10:24:07 -07001632var gDocsMatches = new Array();
1633var ROW_COUNT_DOCS = 100; // max number of results in list
1634var gDocsListLength = 0;
1635
Scott Mainde295272013-03-25 15:48:35 -07001636function onSuggestionClick(link) {
1637 // When user clicks a suggested document, track it
1638 _gaq.push(['_trackEvent', 'Suggestion Click', 'clicked: ' + $(link).text(),
1639 'from: ' + $("#search_autocomplete").val()]);
1640}
1641
Scott Mainf5089842012-08-14 16:31:07 -07001642function set_item_selected($li, selected)
1643{
1644 if (selected) {
1645 $li.attr('class','jd-autocomplete jd-selected');
1646 } else {
1647 $li.attr('class','jd-autocomplete');
1648 }
1649}
1650
1651function set_item_values(toroot, $li, match)
1652{
1653 var $link = $('a',$li);
1654 $link.html(match.__hilabel || match.label);
1655 $link.attr('href',toroot + match.link);
1656}
1657
Scott Main719acb42013-12-05 16:05:09 -08001658function set_item_values_jd(toroot, $li, match)
1659{
1660 var $link = $('a',$li);
1661 $link.html(match.title);
1662 $link.attr('href',toroot + match.url);
1663}
1664
Scott Main0e76e7e2013-03-12 10:24:07 -07001665function new_suggestion($list) {
Scott Main7e447ed2013-02-19 17:22:37 -08001666 var $li = $("<li class='jd-autocomplete'></li>");
1667 $list.append($li);
1668
1669 $li.mousedown(function() {
1670 window.location = this.firstChild.getAttribute("href");
1671 });
1672 $li.mouseover(function() {
Scott Main0e76e7e2013-03-12 10:24:07 -07001673 $('.search_filtered_wrapper li').removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001674 $(this).addClass('jd-selected');
Scott Main0e76e7e2013-03-12 10:24:07 -07001675 gSelectedColumn = $(".search_filtered:visible").index($(this).closest('.search_filtered'));
1676 gSelectedIndex = $("li", $(".search_filtered:visible")[gSelectedColumn]).index(this);
Scott Main7e447ed2013-02-19 17:22:37 -08001677 });
Scott Mainde295272013-03-25 15:48:35 -07001678 $li.append("<a onclick='onSuggestionClick(this)'></a>");
Scott Main7e447ed2013-02-19 17:22:37 -08001679 $li.attr('class','show-item');
1680 return $li;
1681}
1682
Scott Mainf5089842012-08-14 16:31:07 -07001683function sync_selection_table(toroot)
1684{
Scott Mainf5089842012-08-14 16:31:07 -07001685 var $li; //list item jquery object
1686 var i; //list item iterator
Scott Main7e447ed2013-02-19 17:22:37 -08001687
Scott Main0e76e7e2013-03-12 10:24:07 -07001688 // if there are NO results at all, hide all columns
1689 if (!(gMatches.length > 0) && !(gGoogleMatches.length > 0) && !(gDocsMatches.length > 0)) {
1690 $('.suggest-card').hide(300);
1691 return;
1692 }
1693
1694 // if there are api results
Scott Main7e447ed2013-02-19 17:22:37 -08001695 if ((gMatches.length > 0) || (gGoogleMatches.length > 0)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001696 // reveal suggestion list
1697 $('.suggest-card.dummy').show();
1698 $('.suggest-card.reference').show();
1699 var listIndex = 0; // list index position
Scott Main7e447ed2013-02-19 17:22:37 -08001700
Scott Main0e76e7e2013-03-12 10:24:07 -07001701 // reset the lists
1702 $(".search_filtered_wrapper.reference li").remove();
Scott Main7e447ed2013-02-19 17:22:37 -08001703
Scott Main0e76e7e2013-03-12 10:24:07 -07001704 // ########### ANDROID RESULTS #############
1705 if (gMatches.length > 0) {
Scott Main7e447ed2013-02-19 17:22:37 -08001706
Scott Main0e76e7e2013-03-12 10:24:07 -07001707 // determine android results to show
1708 gListLength = gMatches.length < ROW_COUNT_FRAMEWORK ?
1709 gMatches.length : ROW_COUNT_FRAMEWORK;
1710 for (i=0; i<gListLength; i++) {
1711 var $li = new_suggestion($(".suggest-card.reference ul"));
1712 set_item_values(toroot, $li, gMatches[i]);
1713 set_item_selected($li, i == gSelectedIndex);
1714 }
1715 }
Scott Main7e447ed2013-02-19 17:22:37 -08001716
Scott Main0e76e7e2013-03-12 10:24:07 -07001717 // ########### GOOGLE RESULTS #############
1718 if (gGoogleMatches.length > 0) {
1719 // show header for list
1720 $(".suggest-card.reference ul").append("<li class='header'>in Google Services:</li>");
Scott Main7e447ed2013-02-19 17:22:37 -08001721
Scott Main0e76e7e2013-03-12 10:24:07 -07001722 // determine google results to show
1723 gGoogleListLength = gGoogleMatches.length < ROW_COUNT_GOOGLE ? gGoogleMatches.length : ROW_COUNT_GOOGLE;
1724 for (i=0; i<gGoogleListLength; i++) {
1725 var $li = new_suggestion($(".suggest-card.reference ul"));
1726 set_item_values(toroot, $li, gGoogleMatches[i]);
1727 set_item_selected($li, i == gSelectedIndex);
1728 }
1729 }
Scott Mainf5089842012-08-14 16:31:07 -07001730 } else {
Scott Main0e76e7e2013-03-12 10:24:07 -07001731 $('.suggest-card.reference').hide();
1732 $('.suggest-card.dummy').hide();
1733 }
1734
1735 // ########### JD DOC RESULTS #############
1736 if (gDocsMatches.length > 0) {
1737 // reset the lists
1738 $(".search_filtered_wrapper.docs li").remove();
1739
1740 // determine google results to show
Scott Main719acb42013-12-05 16:05:09 -08001741 // NOTE: The order of the conditions below for the sugg.type MUST BE SPECIFIC:
1742 // The order must match the reverse order that each section appears as a card in
1743 // the suggestion UI... this may be only for the "develop" grouped items though.
Scott Main0e76e7e2013-03-12 10:24:07 -07001744 gDocsListLength = gDocsMatches.length < ROW_COUNT_DOCS ? gDocsMatches.length : ROW_COUNT_DOCS;
1745 for (i=0; i<gDocsListLength; i++) {
1746 var sugg = gDocsMatches[i];
1747 var $li;
1748 if (sugg.type == "design") {
1749 $li = new_suggestion($(".suggest-card.design ul"));
1750 } else
1751 if (sugg.type == "distribute") {
1752 $li = new_suggestion($(".suggest-card.distribute ul"));
1753 } else
Scott Main719acb42013-12-05 16:05:09 -08001754 if (sugg.type == "samples") {
1755 $li = new_suggestion($(".suggest-card.develop .child-card.samples"));
1756 } else
Scott Main0e76e7e2013-03-12 10:24:07 -07001757 if (sugg.type == "training") {
1758 $li = new_suggestion($(".suggest-card.develop .child-card.training"));
1759 } else
Scott Main719acb42013-12-05 16:05:09 -08001760 if (sugg.type == "about"||"guide"||"tools"||"google") {
Scott Main0e76e7e2013-03-12 10:24:07 -07001761 $li = new_suggestion($(".suggest-card.develop .child-card.guides"));
1762 } else {
1763 continue;
1764 }
1765
Scott Main719acb42013-12-05 16:05:09 -08001766 set_item_values_jd(toroot, $li, sugg);
Scott Main0e76e7e2013-03-12 10:24:07 -07001767 set_item_selected($li, i == gSelectedIndex);
1768 }
1769
1770 // add heading and show or hide card
1771 if ($(".suggest-card.design li").length > 0) {
1772 $(".suggest-card.design ul").prepend("<li class='header'>Design:</li>");
1773 $(".suggest-card.design").show(300);
1774 } else {
1775 $('.suggest-card.design').hide(300);
1776 }
1777 if ($(".suggest-card.distribute li").length > 0) {
1778 $(".suggest-card.distribute ul").prepend("<li class='header'>Distribute:</li>");
1779 $(".suggest-card.distribute").show(300);
1780 } else {
1781 $('.suggest-card.distribute').hide(300);
1782 }
1783 if ($(".child-card.guides li").length > 0) {
1784 $(".child-card.guides").prepend("<li class='header'>Guides:</li>");
1785 $(".child-card.guides li").appendTo(".suggest-card.develop ul");
1786 }
1787 if ($(".child-card.training li").length > 0) {
1788 $(".child-card.training").prepend("<li class='header'>Training:</li>");
1789 $(".child-card.training li").appendTo(".suggest-card.develop ul");
1790 }
Scott Main719acb42013-12-05 16:05:09 -08001791 if ($(".child-card.samples li").length > 0) {
1792 $(".child-card.samples").prepend("<li class='header'>Samples:</li>");
1793 $(".child-card.samples li").appendTo(".suggest-card.develop ul");
1794 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001795
1796 if ($(".suggest-card.develop li").length > 0) {
1797 $(".suggest-card.develop").show(300);
1798 } else {
1799 $('.suggest-card.develop').hide(300);
1800 }
1801
1802 } else {
1803 $('.search_filtered_wrapper.docs .suggest-card:not(.dummy)').hide(300);
Scott Mainf5089842012-08-14 16:31:07 -07001804 }
1805}
1806
Scott Main0e76e7e2013-03-12 10:24:07 -07001807/** Called by the search input's onkeydown and onkeyup events.
1808 * Handles navigation with keyboard arrows, Enter key to invoke search,
1809 * otherwise invokes search suggestions on key-up event.
1810 * @param e The JS event
1811 * @param kd True if the event is key-down
Scott Main3b90aff2013-08-01 18:09:35 -07001812 * @param toroot A string for the site's root path
Scott Main0e76e7e2013-03-12 10:24:07 -07001813 * @returns True if the event should bubble up
1814 */
Scott Mainf5089842012-08-14 16:31:07 -07001815function search_changed(e, kd, toroot)
1816{
Scott Main719acb42013-12-05 16:05:09 -08001817 var currentLang = getLangPref();
Scott Mainf5089842012-08-14 16:31:07 -07001818 var search = document.getElementById("search_autocomplete");
1819 var text = search.value.replace(/(^ +)|( +$)/g, '');
Scott Main0e76e7e2013-03-12 10:24:07 -07001820 // get the ul hosting the currently selected item
1821 gSelectedColumn = gSelectedColumn >= 0 ? gSelectedColumn : 0;
1822 var $columns = $(".search_filtered_wrapper").find(".search_filtered:visible");
1823 var $selectedUl = $columns[gSelectedColumn];
1824
Scott Mainf5089842012-08-14 16:31:07 -07001825 // show/hide the close button
1826 if (text != '') {
1827 $(".search .close").removeClass("hide");
1828 } else {
1829 $(".search .close").addClass("hide");
1830 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001831 // 27 = esc
1832 if (e.keyCode == 27) {
1833 // close all search results
1834 if (kd) $('.search .close').trigger('click');
1835 return true;
1836 }
Scott Mainf5089842012-08-14 16:31:07 -07001837 // 13 = enter
Scott Main0e76e7e2013-03-12 10:24:07 -07001838 else if (e.keyCode == 13) {
1839 if (gSelectedIndex < 0) {
1840 $('.suggest-card').hide();
Scott Main7e447ed2013-02-19 17:22:37 -08001841 if ($("#searchResults").is(":hidden") && (search.value != "")) {
1842 // if results aren't showing (and text not empty), return true to allow search to execute
Dirk Doughertyc3921652014-05-13 16:55:26 -07001843 $('body,html').animate({scrollTop:0}, '500', 'swing');
Scott Mainf5089842012-08-14 16:31:07 -07001844 return true;
1845 } else {
1846 // otherwise, results are already showing, so allow ajax to auto refresh the results
1847 // and ignore this Enter press to avoid the reload.
1848 return false;
1849 }
1850 } else if (kd && gSelectedIndex >= 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001851 // click the link corresponding to selected item
1852 $("a",$("li",$selectedUl)[gSelectedIndex]).get()[0].click();
Scott Mainf5089842012-08-14 16:31:07 -07001853 return false;
1854 }
1855 }
Scott Mainb16376f2014-05-21 20:35:47 -07001856 // If Google results are showing, return true to allow ajax search to execute
Scott Main0e76e7e2013-03-12 10:24:07 -07001857 else if ($("#searchResults").is(":visible")) {
Scott Mainb16376f2014-05-21 20:35:47 -07001858 // Also, if search_results is scrolled out of view, scroll to top to make results visible
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001859 if ((sticky ) && (search.value != "")) {
1860 $('body,html').animate({scrollTop:0}, '500', 'swing');
1861 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001862 return true;
1863 }
1864 // 38 UP ARROW
Scott Mainf5089842012-08-14 16:31:07 -07001865 else if (kd && (e.keyCode == 38)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001866 // if the next item is a header, skip it
1867 if ($($("li", $selectedUl)[gSelectedIndex-1]).hasClass("header")) {
Scott Mainf5089842012-08-14 16:31:07 -07001868 gSelectedIndex--;
Scott Main7e447ed2013-02-19 17:22:37 -08001869 }
1870 if (gSelectedIndex >= 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001871 $('li', $selectedUl).removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001872 gSelectedIndex--;
Scott Main0e76e7e2013-03-12 10:24:07 -07001873 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
1874 // If user reaches top, reset selected column
1875 if (gSelectedIndex < 0) {
1876 gSelectedColumn = -1;
1877 }
Scott Mainf5089842012-08-14 16:31:07 -07001878 }
1879 return false;
1880 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001881 // 40 DOWN ARROW
Scott Mainf5089842012-08-14 16:31:07 -07001882 else if (kd && (e.keyCode == 40)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001883 // if the next item is a header, skip it
1884 if ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header")) {
Scott Mainf5089842012-08-14 16:31:07 -07001885 gSelectedIndex++;
Scott Main7e447ed2013-02-19 17:22:37 -08001886 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001887 if ((gSelectedIndex < $("li", $selectedUl).length-1) ||
1888 ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header"))) {
1889 $('li', $selectedUl).removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001890 gSelectedIndex++;
Scott Main0e76e7e2013-03-12 10:24:07 -07001891 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
Scott Mainf5089842012-08-14 16:31:07 -07001892 }
1893 return false;
1894 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001895 // Consider left/right arrow navigation
1896 // NOTE: Order of suggest columns are reverse order (index position 0 is on right)
1897 else if (kd && $columns.length > 1 && gSelectedColumn >= 0) {
1898 // 37 LEFT ARROW
1899 // go left only if current column is not left-most column (last column)
1900 if (e.keyCode == 37 && gSelectedColumn < $columns.length - 1) {
1901 $('li', $selectedUl).removeClass('jd-selected');
1902 gSelectedColumn++;
1903 $selectedUl = $columns[gSelectedColumn];
1904 // keep or reset the selected item to last item as appropriate
1905 gSelectedIndex = gSelectedIndex >
1906 $("li", $selectedUl).length-1 ?
1907 $("li", $selectedUl).length-1 : gSelectedIndex;
1908 // if the corresponding item is a header, move down
1909 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
1910 gSelectedIndex++;
1911 }
Scott Main3b90aff2013-08-01 18:09:35 -07001912 // set item selected
Scott Main0e76e7e2013-03-12 10:24:07 -07001913 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
1914 return false;
1915 }
1916 // 39 RIGHT ARROW
1917 // go right only if current column is not the right-most column (first column)
1918 else if (e.keyCode == 39 && gSelectedColumn > 0) {
1919 $('li', $selectedUl).removeClass('jd-selected');
1920 gSelectedColumn--;
1921 $selectedUl = $columns[gSelectedColumn];
1922 // keep or reset the selected item to last item as appropriate
1923 gSelectedIndex = gSelectedIndex >
1924 $("li", $selectedUl).length-1 ?
1925 $("li", $selectedUl).length-1 : gSelectedIndex;
1926 // if the corresponding item is a header, move down
1927 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
1928 gSelectedIndex++;
1929 }
Scott Main3b90aff2013-08-01 18:09:35 -07001930 // set item selected
Scott Main0e76e7e2013-03-12 10:24:07 -07001931 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
1932 return false;
1933 }
1934 }
1935
Scott Main719acb42013-12-05 16:05:09 -08001936 // if key-up event and not arrow down/up/left/right,
1937 // read the search query and add suggestions to gMatches
Scott Main0e76e7e2013-03-12 10:24:07 -07001938 else if (!kd && (e.keyCode != 40)
1939 && (e.keyCode != 38)
1940 && (e.keyCode != 37)
1941 && (e.keyCode != 39)) {
1942 gSelectedIndex = -1;
Scott Mainf5089842012-08-14 16:31:07 -07001943 gMatches = new Array();
1944 matchedCount = 0;
Scott Main7e447ed2013-02-19 17:22:37 -08001945 gGoogleMatches = new Array();
1946 matchedCountGoogle = 0;
Scott Main0e76e7e2013-03-12 10:24:07 -07001947 gDocsMatches = new Array();
1948 matchedCountDocs = 0;
Scott Main7e447ed2013-02-19 17:22:37 -08001949
1950 // Search for Android matches
Scott Mainf5089842012-08-14 16:31:07 -07001951 for (var i=0; i<DATA.length; i++) {
1952 var s = DATA[i];
1953 if (text.length != 0 &&
1954 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
1955 gMatches[matchedCount] = s;
1956 matchedCount++;
1957 }
1958 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001959 rank_autocomplete_api_results(text, gMatches);
Scott Mainf5089842012-08-14 16:31:07 -07001960 for (var i=0; i<gMatches.length; i++) {
1961 var s = gMatches[i];
Scott Main7e447ed2013-02-19 17:22:37 -08001962 }
1963
1964
1965 // Search for Google matches
1966 for (var i=0; i<GOOGLE_DATA.length; i++) {
1967 var s = GOOGLE_DATA[i];
1968 if (text.length != 0 &&
1969 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
1970 gGoogleMatches[matchedCountGoogle] = s;
1971 matchedCountGoogle++;
Scott Mainf5089842012-08-14 16:31:07 -07001972 }
1973 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001974 rank_autocomplete_api_results(text, gGoogleMatches);
Scott Main7e447ed2013-02-19 17:22:37 -08001975 for (var i=0; i<gGoogleMatches.length; i++) {
1976 var s = gGoogleMatches[i];
1977 }
1978
Scott Mainf5089842012-08-14 16:31:07 -07001979 highlight_autocomplete_result_labels(text);
Scott Main0e76e7e2013-03-12 10:24:07 -07001980
1981
1982
Scott Main719acb42013-12-05 16:05:09 -08001983 // Search for matching JD docs
Scott Main0e76e7e2013-03-12 10:24:07 -07001984 if (text.length >= 3) {
Scott Main719acb42013-12-05 16:05:09 -08001985 // Regex to match only the beginning of a word
1986 var textRegex = new RegExp("\\b" + text.toLowerCase(), "g");
1987
1988
1989 // Search for Training classes
1990 for (var i=0; i<TRAINING_RESOURCES.length; i++) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001991 // current search comparison, with counters for tag and title,
1992 // used later to improve ranking
Scott Main719acb42013-12-05 16:05:09 -08001993 var s = TRAINING_RESOURCES[i];
Scott Main0e76e7e2013-03-12 10:24:07 -07001994 s.matched_tag = 0;
1995 s.matched_title = 0;
1996 var matched = false;
1997
1998 // Check if query matches any tags; work backwards toward 1 to assist ranking
Scott Main719acb42013-12-05 16:05:09 -08001999 for (var j = s.keywords.length - 1; j >= 0; j--) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002000 // it matches a tag
Scott Main719acb42013-12-05 16:05:09 -08002001 if (s.keywords[j].toLowerCase().match(textRegex)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002002 matched = true;
2003 s.matched_tag = j + 1; // add 1 to index position
2004 }
2005 }
Scott Main719acb42013-12-05 16:05:09 -08002006 // Don't consider doc title for lessons (only for class landing pages),
2007 // unless the lesson has a tag that already matches
2008 if ((s.lang == currentLang) &&
2009 (!(s.type == "training" && s.url.indexOf("index.html") == -1) || matched)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002010 // it matches the doc title
Scott Main719acb42013-12-05 16:05:09 -08002011 if (s.title.toLowerCase().match(textRegex)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002012 matched = true;
2013 s.matched_title = 1;
2014 }
2015 }
2016 if (matched) {
2017 gDocsMatches[matchedCountDocs] = s;
2018 matchedCountDocs++;
2019 }
2020 }
Scott Main719acb42013-12-05 16:05:09 -08002021
2022
2023 // Search for API Guides
2024 for (var i=0; i<GUIDE_RESOURCES.length; i++) {
2025 // current search comparison, with counters for tag and title,
2026 // used later to improve ranking
2027 var s = GUIDE_RESOURCES[i];
2028 s.matched_tag = 0;
2029 s.matched_title = 0;
2030 var matched = false;
2031
2032 // Check if query matches any tags; work backwards toward 1 to assist ranking
2033 for (var j = s.keywords.length - 1; j >= 0; j--) {
2034 // it matches a tag
2035 if (s.keywords[j].toLowerCase().match(textRegex)) {
2036 matched = true;
2037 s.matched_tag = j + 1; // add 1 to index position
2038 }
2039 }
2040 // Check if query matches the doc title, but only for current language
2041 if (s.lang == currentLang) {
2042 // if query matches the doc title
2043 if (s.title.toLowerCase().match(textRegex)) {
2044 matched = true;
2045 s.matched_title = 1;
2046 }
2047 }
2048 if (matched) {
2049 gDocsMatches[matchedCountDocs] = s;
2050 matchedCountDocs++;
2051 }
2052 }
2053
2054
2055 // Search for Tools Guides
2056 for (var i=0; i<TOOLS_RESOURCES.length; i++) {
2057 // current search comparison, with counters for tag and title,
2058 // used later to improve ranking
2059 var s = TOOLS_RESOURCES[i];
2060 s.matched_tag = 0;
2061 s.matched_title = 0;
2062 var matched = false;
2063
2064 // Check if query matches any tags; work backwards toward 1 to assist ranking
2065 for (var j = s.keywords.length - 1; j >= 0; j--) {
2066 // it matches a tag
2067 if (s.keywords[j].toLowerCase().match(textRegex)) {
2068 matched = true;
2069 s.matched_tag = j + 1; // add 1 to index position
2070 }
2071 }
2072 // Check if query matches the doc title, but only for current language
2073 if (s.lang == currentLang) {
2074 // if query matches the doc title
2075 if (s.title.toLowerCase().match(textRegex)) {
2076 matched = true;
2077 s.matched_title = 1;
2078 }
2079 }
2080 if (matched) {
2081 gDocsMatches[matchedCountDocs] = s;
2082 matchedCountDocs++;
2083 }
2084 }
2085
2086
2087 // Search for About docs
2088 for (var i=0; i<ABOUT_RESOURCES.length; i++) {
2089 // current search comparison, with counters for tag and title,
2090 // used later to improve ranking
2091 var s = ABOUT_RESOURCES[i];
2092 s.matched_tag = 0;
2093 s.matched_title = 0;
2094 var matched = false;
2095
2096 // Check if query matches any tags; work backwards toward 1 to assist ranking
2097 for (var j = s.keywords.length - 1; j >= 0; j--) {
2098 // it matches a tag
2099 if (s.keywords[j].toLowerCase().match(textRegex)) {
2100 matched = true;
2101 s.matched_tag = j + 1; // add 1 to index position
2102 }
2103 }
2104 // Check if query matches the doc title, but only for current language
2105 if (s.lang == currentLang) {
2106 // if query matches the doc title
2107 if (s.title.toLowerCase().match(textRegex)) {
2108 matched = true;
2109 s.matched_title = 1;
2110 }
2111 }
2112 if (matched) {
2113 gDocsMatches[matchedCountDocs] = s;
2114 matchedCountDocs++;
2115 }
2116 }
2117
2118
2119 // Search for Design guides
2120 for (var i=0; i<DESIGN_RESOURCES.length; i++) {
2121 // current search comparison, with counters for tag and title,
2122 // used later to improve ranking
2123 var s = DESIGN_RESOURCES[i];
2124 s.matched_tag = 0;
2125 s.matched_title = 0;
2126 var matched = false;
2127
2128 // Check if query matches any tags; work backwards toward 1 to assist ranking
2129 for (var j = s.keywords.length - 1; j >= 0; j--) {
2130 // it matches a tag
2131 if (s.keywords[j].toLowerCase().match(textRegex)) {
2132 matched = true;
2133 s.matched_tag = j + 1; // add 1 to index position
2134 }
2135 }
2136 // Check if query matches the doc title, but only for current language
2137 if (s.lang == currentLang) {
2138 // if query matches the doc title
2139 if (s.title.toLowerCase().match(textRegex)) {
2140 matched = true;
2141 s.matched_title = 1;
2142 }
2143 }
2144 if (matched) {
2145 gDocsMatches[matchedCountDocs] = s;
2146 matchedCountDocs++;
2147 }
2148 }
2149
2150
2151 // Search for Distribute guides
2152 for (var i=0; i<DISTRIBUTE_RESOURCES.length; i++) {
2153 // current search comparison, with counters for tag and title,
2154 // used later to improve ranking
2155 var s = DISTRIBUTE_RESOURCES[i];
2156 s.matched_tag = 0;
2157 s.matched_title = 0;
2158 var matched = false;
2159
2160 // Check if query matches any tags; work backwards toward 1 to assist ranking
2161 for (var j = s.keywords.length - 1; j >= 0; j--) {
2162 // it matches a tag
2163 if (s.keywords[j].toLowerCase().match(textRegex)) {
2164 matched = true;
2165 s.matched_tag = j + 1; // add 1 to index position
2166 }
2167 }
2168 // Check if query matches the doc title, but only for current language
2169 if (s.lang == currentLang) {
2170 // if query matches the doc title
2171 if (s.title.toLowerCase().match(textRegex)) {
2172 matched = true;
2173 s.matched_title = 1;
2174 }
2175 }
2176 if (matched) {
2177 gDocsMatches[matchedCountDocs] = s;
2178 matchedCountDocs++;
2179 }
2180 }
2181
2182
2183 // Search for Google guides
2184 for (var i=0; i<GOOGLE_RESOURCES.length; i++) {
2185 // current search comparison, with counters for tag and title,
2186 // used later to improve ranking
2187 var s = GOOGLE_RESOURCES[i];
2188 s.matched_tag = 0;
2189 s.matched_title = 0;
2190 var matched = false;
2191
2192 // Check if query matches any tags; work backwards toward 1 to assist ranking
2193 for (var j = s.keywords.length - 1; j >= 0; j--) {
2194 // it matches a tag
2195 if (s.keywords[j].toLowerCase().match(textRegex)) {
2196 matched = true;
2197 s.matched_tag = j + 1; // add 1 to index position
2198 }
2199 }
2200 // Check if query matches the doc title, but only for current language
2201 if (s.lang == currentLang) {
2202 // if query matches the doc title
2203 if (s.title.toLowerCase().match(textRegex)) {
2204 matched = true;
2205 s.matched_title = 1;
2206 }
2207 }
2208 if (matched) {
2209 gDocsMatches[matchedCountDocs] = s;
2210 matchedCountDocs++;
2211 }
2212 }
2213
2214
2215 // Search for Samples
2216 for (var i=0; i<SAMPLES_RESOURCES.length; i++) {
2217 // current search comparison, with counters for tag and title,
2218 // used later to improve ranking
2219 var s = SAMPLES_RESOURCES[i];
2220 s.matched_tag = 0;
2221 s.matched_title = 0;
2222 var matched = false;
2223 // Check if query matches any tags; work backwards toward 1 to assist ranking
2224 for (var j = s.keywords.length - 1; j >= 0; j--) {
2225 // it matches a tag
2226 if (s.keywords[j].toLowerCase().match(textRegex)) {
2227 matched = true;
2228 s.matched_tag = j + 1; // add 1 to index position
2229 }
2230 }
2231 // Check if query matches the doc title, but only for current language
2232 if (s.lang == currentLang) {
2233 // if query matches the doc title.t
2234 if (s.title.toLowerCase().match(textRegex)) {
2235 matched = true;
2236 s.matched_title = 1;
2237 }
2238 }
2239 if (matched) {
2240 gDocsMatches[matchedCountDocs] = s;
2241 matchedCountDocs++;
2242 }
2243 }
2244
2245 // Rank/sort all the matched pages
Scott Main0e76e7e2013-03-12 10:24:07 -07002246 rank_autocomplete_doc_results(text, gDocsMatches);
2247 }
2248
2249 // draw the suggestions
Scott Mainf5089842012-08-14 16:31:07 -07002250 sync_selection_table(toroot);
2251 return true; // allow the event to bubble up to the search api
2252 }
2253}
2254
Scott Main0e76e7e2013-03-12 10:24:07 -07002255/* Order the jd doc result list based on match quality */
2256function rank_autocomplete_doc_results(query, matches) {
2257 query = query || '';
2258 if (!matches || !matches.length)
2259 return;
2260
2261 var _resultScoreFn = function(match) {
2262 var score = 1.0;
2263
2264 // if the query matched a tag
2265 if (match.matched_tag > 0) {
2266 // multiply score by factor relative to position in tags list (max of 3)
2267 score *= 3 / match.matched_tag;
2268
2269 // if it also matched the title
2270 if (match.matched_title > 0) {
2271 score *= 2;
2272 }
2273 } else if (match.matched_title > 0) {
2274 score *= 3;
2275 }
2276
2277 return score;
2278 };
2279
2280 for (var i=0; i<matches.length; i++) {
2281 matches[i].__resultScore = _resultScoreFn(matches[i]);
2282 }
2283
2284 matches.sort(function(a,b){
2285 var n = b.__resultScore - a.__resultScore;
2286 if (n == 0) // lexicographical sort if scores are the same
2287 n = (a.label < b.label) ? -1 : 1;
2288 return n;
2289 });
2290}
2291
Scott Main7e447ed2013-02-19 17:22:37 -08002292/* Order the result list based on match quality */
Scott Main0e76e7e2013-03-12 10:24:07 -07002293function rank_autocomplete_api_results(query, matches) {
Scott Mainf5089842012-08-14 16:31:07 -07002294 query = query || '';
Scott Main7e447ed2013-02-19 17:22:37 -08002295 if (!matches || !matches.length)
Scott Mainf5089842012-08-14 16:31:07 -07002296 return;
2297
2298 // helper function that gets the last occurence index of the given regex
2299 // in the given string, or -1 if not found
2300 var _lastSearch = function(s, re) {
2301 if (s == '')
2302 return -1;
2303 var l = -1;
2304 var tmp;
2305 while ((tmp = s.search(re)) >= 0) {
2306 if (l < 0) l = 0;
2307 l += tmp;
2308 s = s.substr(tmp + 1);
2309 }
2310 return l;
2311 };
2312
2313 // helper function that counts the occurrences of a given character in
2314 // a given string
2315 var _countChar = function(s, c) {
2316 var n = 0;
2317 for (var i=0; i<s.length; i++)
2318 if (s.charAt(i) == c) ++n;
2319 return n;
2320 };
2321
2322 var queryLower = query.toLowerCase();
2323 var queryAlnum = (queryLower.match(/\w+/) || [''])[0];
2324 var partPrefixAlnumRE = new RegExp('\\b' + queryAlnum);
2325 var partExactAlnumRE = new RegExp('\\b' + queryAlnum + '\\b');
2326
2327 var _resultScoreFn = function(result) {
2328 // scores are calculated based on exact and prefix matches,
2329 // and then number of path separators (dots) from the last
2330 // match (i.e. favoring classes and deep package names)
2331 var score = 1.0;
2332 var labelLower = result.label.toLowerCase();
2333 var t;
2334 t = _lastSearch(labelLower, partExactAlnumRE);
2335 if (t >= 0) {
2336 // exact part match
2337 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2338 score *= 200 / (partsAfter + 1);
2339 } else {
2340 t = _lastSearch(labelLower, partPrefixAlnumRE);
2341 if (t >= 0) {
2342 // part prefix match
2343 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2344 score *= 20 / (partsAfter + 1);
2345 }
2346 }
2347
2348 return score;
2349 };
2350
Scott Main7e447ed2013-02-19 17:22:37 -08002351 for (var i=0; i<matches.length; i++) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002352 // if the API is deprecated, default score is 0; otherwise, perform scoring
2353 if (matches[i].deprecated == "true") {
2354 matches[i].__resultScore = 0;
2355 } else {
2356 matches[i].__resultScore = _resultScoreFn(matches[i]);
2357 }
Scott Mainf5089842012-08-14 16:31:07 -07002358 }
2359
Scott Main7e447ed2013-02-19 17:22:37 -08002360 matches.sort(function(a,b){
Scott Mainf5089842012-08-14 16:31:07 -07002361 var n = b.__resultScore - a.__resultScore;
2362 if (n == 0) // lexicographical sort if scores are the same
2363 n = (a.label < b.label) ? -1 : 1;
2364 return n;
2365 });
2366}
2367
Scott Main7e447ed2013-02-19 17:22:37 -08002368/* Add emphasis to part of string that matches query */
Scott Mainf5089842012-08-14 16:31:07 -07002369function highlight_autocomplete_result_labels(query) {
2370 query = query || '';
Scott Main7e447ed2013-02-19 17:22:37 -08002371 if ((!gMatches || !gMatches.length) && (!gGoogleMatches || !gGoogleMatches.length))
Scott Mainf5089842012-08-14 16:31:07 -07002372 return;
2373
2374 var queryLower = query.toLowerCase();
2375 var queryAlnumDot = (queryLower.match(/[\w\.]+/) || [''])[0];
2376 var queryRE = new RegExp(
2377 '(' + queryAlnumDot.replace(/\./g, '\\.') + ')', 'ig');
2378 for (var i=0; i<gMatches.length; i++) {
2379 gMatches[i].__hilabel = gMatches[i].label.replace(
2380 queryRE, '<b>$1</b>');
2381 }
Scott Main7e447ed2013-02-19 17:22:37 -08002382 for (var i=0; i<gGoogleMatches.length; i++) {
2383 gGoogleMatches[i].__hilabel = gGoogleMatches[i].label.replace(
2384 queryRE, '<b>$1</b>');
2385 }
Scott Mainf5089842012-08-14 16:31:07 -07002386}
2387
2388function search_focus_changed(obj, focused)
2389{
Scott Main3b90aff2013-08-01 18:09:35 -07002390 if (!focused) {
Scott Mainf5089842012-08-14 16:31:07 -07002391 if(obj.value == ""){
2392 $(".search .close").addClass("hide");
2393 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002394 $(".suggest-card").hide();
Scott Mainf5089842012-08-14 16:31:07 -07002395 }
2396}
2397
2398function submit_search() {
2399 var query = document.getElementById('search_autocomplete').value;
2400 location.hash = 'q=' + query;
2401 loadSearchResults();
Dirk Doughertyc3921652014-05-13 16:55:26 -07002402 $("#searchResults").slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002403 return false;
2404}
2405
2406
2407function hideResults() {
Dirk Doughertyc3921652014-05-13 16:55:26 -07002408 $("#searchResults").slideUp('fast', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002409 $(".search .close").addClass("hide");
2410 location.hash = '';
Scott Main3b90aff2013-08-01 18:09:35 -07002411
Scott Mainf5089842012-08-14 16:31:07 -07002412 $("#search_autocomplete").val("").blur();
Scott Main3b90aff2013-08-01 18:09:35 -07002413
Scott Mainf5089842012-08-14 16:31:07 -07002414 // reset the ajax search callback to nothing, so results don't appear unless ENTER
2415 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {});
Scott Main0e76e7e2013-03-12 10:24:07 -07002416
2417 // forcefully regain key-up event control (previously jacked by search api)
2418 $("#search_autocomplete").keyup(function(event) {
2419 return search_changed(event, false, toRoot);
2420 });
2421
Scott Mainf5089842012-08-14 16:31:07 -07002422 return false;
2423}
2424
2425
2426
2427/* ########################################################## */
2428/* ################ CUSTOM SEARCH ENGINE ################## */
2429/* ########################################################## */
2430
Scott Mainf5089842012-08-14 16:31:07 -07002431var searchControl;
Scott Main0e76e7e2013-03-12 10:24:07 -07002432google.load('search', '1', {"callback" : function() {
2433 searchControl = new google.search.SearchControl();
2434 } });
Scott Mainf5089842012-08-14 16:31:07 -07002435
2436function loadSearchResults() {
2437 document.getElementById("search_autocomplete").style.color = "#000";
2438
Scott Mainf5089842012-08-14 16:31:07 -07002439 searchControl = new google.search.SearchControl();
2440
2441 // use our existing search form and use tabs when multiple searchers are used
2442 drawOptions = new google.search.DrawOptions();
2443 drawOptions.setDrawMode(google.search.SearchControl.DRAW_MODE_TABBED);
2444 drawOptions.setInput(document.getElementById("search_autocomplete"));
2445
2446 // configure search result options
2447 searchOptions = new google.search.SearcherOptions();
2448 searchOptions.setExpandMode(GSearchControl.EXPAND_MODE_OPEN);
2449
2450 // configure each of the searchers, for each tab
2451 devSiteSearcher = new google.search.WebSearch();
2452 devSiteSearcher.setUserDefinedLabel("All");
2453 devSiteSearcher.setSiteRestriction("001482626316274216503:zu90b7s047u");
2454
2455 designSearcher = new google.search.WebSearch();
2456 designSearcher.setUserDefinedLabel("Design");
2457 designSearcher.setSiteRestriction("http://developer.android.com/design/");
2458
2459 trainingSearcher = new google.search.WebSearch();
2460 trainingSearcher.setUserDefinedLabel("Training");
2461 trainingSearcher.setSiteRestriction("http://developer.android.com/training/");
2462
2463 guidesSearcher = new google.search.WebSearch();
2464 guidesSearcher.setUserDefinedLabel("Guides");
2465 guidesSearcher.setSiteRestriction("http://developer.android.com/guide/");
2466
2467 referenceSearcher = new google.search.WebSearch();
2468 referenceSearcher.setUserDefinedLabel("Reference");
2469 referenceSearcher.setSiteRestriction("http://developer.android.com/reference/");
2470
Scott Maindf08ada2012-12-03 08:54:37 -08002471 googleSearcher = new google.search.WebSearch();
2472 googleSearcher.setUserDefinedLabel("Google Services");
2473 googleSearcher.setSiteRestriction("http://developer.android.com/google/");
2474
Scott Mainf5089842012-08-14 16:31:07 -07002475 blogSearcher = new google.search.WebSearch();
2476 blogSearcher.setUserDefinedLabel("Blog");
2477 blogSearcher.setSiteRestriction("http://android-developers.blogspot.com");
2478
2479 // add each searcher to the search control
2480 searchControl.addSearcher(devSiteSearcher, searchOptions);
2481 searchControl.addSearcher(designSearcher, searchOptions);
2482 searchControl.addSearcher(trainingSearcher, searchOptions);
2483 searchControl.addSearcher(guidesSearcher, searchOptions);
2484 searchControl.addSearcher(referenceSearcher, searchOptions);
Scott Maindf08ada2012-12-03 08:54:37 -08002485 searchControl.addSearcher(googleSearcher, searchOptions);
Scott Mainf5089842012-08-14 16:31:07 -07002486 searchControl.addSearcher(blogSearcher, searchOptions);
2487
2488 // configure result options
2489 searchControl.setResultSetSize(google.search.Search.LARGE_RESULTSET);
2490 searchControl.setLinkTarget(google.search.Search.LINK_TARGET_SELF);
2491 searchControl.setTimeoutInterval(google.search.SearchControl.TIMEOUT_SHORT);
2492 searchControl.setNoResultsString(google.search.SearchControl.NO_RESULTS_DEFAULT_STRING);
2493
2494 // upon ajax search, refresh the url and search title
2495 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {
2496 updateResultTitle(query);
2497 var query = document.getElementById('search_autocomplete').value;
2498 location.hash = 'q=' + query;
2499 });
2500
Scott Mainde295272013-03-25 15:48:35 -07002501 // once search results load, set up click listeners
2502 searchControl.setSearchCompleteCallback(this, function(control, searcher, query) {
2503 addResultClickListeners();
2504 });
2505
Scott Mainf5089842012-08-14 16:31:07 -07002506 // draw the search results box
2507 searchControl.draw(document.getElementById("leftSearchControl"), drawOptions);
2508
2509 // get query and execute the search
2510 searchControl.execute(decodeURI(getQuery(location.hash)));
2511
2512 document.getElementById("search_autocomplete").focus();
2513 addTabListeners();
2514}
2515// End of loadSearchResults
2516
2517
2518google.setOnLoadCallback(function(){
2519 if (location.hash.indexOf("q=") == -1) {
2520 // if there's no query in the url, don't search and make sure results are hidden
2521 $('#searchResults').hide();
2522 return;
2523 } else {
2524 // first time loading search results for this page
Dirk Doughertyc3921652014-05-13 16:55:26 -07002525 $('#searchResults').slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002526 $(".search .close").removeClass("hide");
2527 loadSearchResults();
2528 }
2529}, true);
2530
Scott Mainb16376f2014-05-21 20:35:47 -07002531/* Adjust the scroll position to account for sticky header, only if the hash matches an id */
2532function offsetScrollForSticky() {
2533 var hash = location.hash;
2534 var $matchingElement = $(hash);
2535 // If there's no element with the hash as an ID, then look for an <a name=''> with it.
2536 if ($matchingElement.length < 1) {
2537 $matchingElement = $('a[name="' + hash.substr(1) + '"]');
2538 }
2539 // Sanity check that hash is a real hash and that there's an element with that ID on the page
2540 if ((hash.indexOf("#") == 0) && $matchingElement.length) {
2541 // If the position of the target element is near the top of the page (<20px, where we expect it
2542 // to be because we need to move it down 60px to become in view), then move it down 60px
2543 if (Math.abs($matchingElement.offset().top - $(window).scrollTop()) < 20) {
2544 $(window).scrollTop($(window).scrollTop() - 60);
Scott Mainb16376f2014-05-21 20:35:47 -07002545 }
2546 }
2547}
2548
Scott Mainf5089842012-08-14 16:31:07 -07002549// when an event on the browser history occurs (back, forward, load) requery hash and do search
2550$(window).hashchange( function(){
Dirk Doughertyc3921652014-05-13 16:55:26 -07002551 // If the hash isn't a search query or there's an error in the query,
2552 // then adjust the scroll position to account for sticky header, then exit.
Scott Mainf5089842012-08-14 16:31:07 -07002553 if ((location.hash.indexOf("q=") == -1) || (query == "undefined")) {
2554 // If the results pane is open, close it.
2555 if (!$("#searchResults").is(":hidden")) {
2556 hideResults();
2557 }
Scott Mainb16376f2014-05-21 20:35:47 -07002558 offsetScrollForSticky();
Scott Mainf5089842012-08-14 16:31:07 -07002559 return;
2560 }
2561
2562 // Otherwise, we have a search to do
2563 var query = decodeURI(getQuery(location.hash));
2564 searchControl.execute(query);
Dirk Doughertyc3921652014-05-13 16:55:26 -07002565 $('#searchResults').slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002566 $("#search_autocomplete").focus();
2567 $(".search .close").removeClass("hide");
2568
2569 updateResultTitle(query);
2570});
2571
2572function updateResultTitle(query) {
2573 $("#searchTitle").html("Results for <em>" + escapeHTML(query) + "</em>");
2574}
2575
2576// forcefully regain key-up event control (previously jacked by search api)
2577$("#search_autocomplete").keyup(function(event) {
2578 return search_changed(event, false, toRoot);
2579});
2580
2581// add event listeners to each tab so we can track the browser history
2582function addTabListeners() {
2583 var tabHeaders = $(".gsc-tabHeader");
2584 for (var i = 0; i < tabHeaders.length; i++) {
2585 $(tabHeaders[i]).attr("id",i).click(function() {
2586 /*
2587 // make a copy of the page numbers for the search left pane
2588 setTimeout(function() {
2589 // remove any residual page numbers
2590 $('#searchResults .gsc-tabsArea .gsc-cursor-box.gs-bidi-start-align').remove();
Scott Main3b90aff2013-08-01 18:09:35 -07002591 // move the page numbers to the left position; make a clone,
Scott Mainf5089842012-08-14 16:31:07 -07002592 // because the element is drawn to the DOM only once
Scott Main3b90aff2013-08-01 18:09:35 -07002593 // and because we're going to remove it (previous line),
2594 // we need it to be available to move again as the user navigates
Scott Mainf5089842012-08-14 16:31:07 -07002595 $('#searchResults .gsc-webResult .gsc-cursor-box.gs-bidi-start-align:visible')
2596 .clone().appendTo('#searchResults .gsc-tabsArea');
2597 }, 200);
2598 */
2599 });
2600 }
2601 setTimeout(function(){$(tabHeaders[0]).click()},200);
2602}
2603
Scott Mainde295272013-03-25 15:48:35 -07002604// add analytics tracking events to each result link
2605function addResultClickListeners() {
2606 $("#searchResults a.gs-title").each(function(index, link) {
2607 // When user clicks enter for Google search results, track it
2608 $(link).click(function() {
2609 _gaq.push(['_trackEvent', 'Google Click', 'clicked: ' + $(this).text(),
2610 'from: ' + $("#search_autocomplete").val()]);
2611 });
2612 });
2613}
2614
Scott Mainf5089842012-08-14 16:31:07 -07002615
2616function getQuery(hash) {
2617 var queryParts = hash.split('=');
2618 return queryParts[1];
2619}
2620
2621/* returns the given string with all HTML brackets converted to entities
2622 TODO: move this to the site's JS library */
2623function escapeHTML(string) {
2624 return string.replace(/</g,"&lt;")
2625 .replace(/>/g,"&gt;");
2626}
2627
2628
2629
2630
2631
2632
2633
2634/* ######################################################## */
2635/* ################# JAVADOC REFERENCE ################### */
2636/* ######################################################## */
2637
Scott Main65511c02012-09-07 15:51:32 -07002638/* Initialize some droiddoc stuff, but only if we're in the reference */
Scott Main52dd2062013-08-15 12:22:28 -07002639if (location.pathname.indexOf("/reference") == 0) {
2640 if(!(location.pathname.indexOf("/reference-gms/packages.html") == 0)
2641 && !(location.pathname.indexOf("/reference-gcm/packages.html") == 0)
2642 && !(location.pathname.indexOf("/reference/com/google") == 0)) {
Robert Ly67d75f12012-12-03 12:53:42 -08002643 $(document).ready(function() {
2644 // init available apis based on user pref
2645 changeApiLevel();
2646 initSidenavHeightResize()
2647 });
2648 }
Scott Main65511c02012-09-07 15:51:32 -07002649}
Scott Mainf5089842012-08-14 16:31:07 -07002650
2651var API_LEVEL_COOKIE = "api_level";
2652var minLevel = 1;
2653var maxLevel = 1;
2654
2655/******* SIDENAV DIMENSIONS ************/
Scott Main3b90aff2013-08-01 18:09:35 -07002656
Scott Mainf5089842012-08-14 16:31:07 -07002657 function initSidenavHeightResize() {
2658 // Change the drag bar size to nicely fit the scrollbar positions
2659 var $dragBar = $(".ui-resizable-s");
2660 $dragBar.css({'width': $dragBar.parent().width() - 5 + "px"});
Scott Main3b90aff2013-08-01 18:09:35 -07002661
2662 $( "#resize-packages-nav" ).resizable({
Scott Mainf5089842012-08-14 16:31:07 -07002663 containment: "#nav-panels",
2664 handles: "s",
2665 alsoResize: "#packages-nav",
2666 resize: function(event, ui) { resizeNav(); }, /* resize the nav while dragging */
2667 stop: function(event, ui) { saveNavPanels(); } /* once stopped, save the sizes to cookie */
2668 });
Scott Main3b90aff2013-08-01 18:09:35 -07002669
Scott Mainf5089842012-08-14 16:31:07 -07002670 }
Scott Main3b90aff2013-08-01 18:09:35 -07002671
Scott Mainf5089842012-08-14 16:31:07 -07002672function updateSidenavFixedWidth() {
Scott Mainf5257812014-05-22 17:26:38 -07002673 if (!sticky) return;
Scott Mainf5089842012-08-14 16:31:07 -07002674 $('#devdoc-nav').css({
2675 'width' : $('#side-nav').css('width'),
2676 'margin' : $('#side-nav').css('margin')
2677 });
2678 $('#devdoc-nav a.totop').css({'display':'block','width':$("#nav").innerWidth()+'px'});
Scott Main3b90aff2013-08-01 18:09:35 -07002679
Scott Mainf5089842012-08-14 16:31:07 -07002680 initSidenavHeightResize();
2681}
2682
2683function updateSidenavFullscreenWidth() {
Scott Mainf5257812014-05-22 17:26:38 -07002684 if (!sticky) return;
Scott Mainf5089842012-08-14 16:31:07 -07002685 $('#devdoc-nav').css({
2686 'width' : $('#side-nav').css('width'),
2687 'margin' : $('#side-nav').css('margin')
2688 });
2689 $('#devdoc-nav .totop').css({'left': 'inherit'});
Scott Main3b90aff2013-08-01 18:09:35 -07002690
Scott Mainf5089842012-08-14 16:31:07 -07002691 initSidenavHeightResize();
2692}
2693
2694function buildApiLevelSelector() {
2695 maxLevel = SINCE_DATA.length;
2696 var userApiLevel = parseInt(readCookie(API_LEVEL_COOKIE));
2697 userApiLevel = userApiLevel == 0 ? maxLevel : userApiLevel; // If there's no cookie (zero), use the max by default
2698
2699 minLevel = parseInt($("#doc-api-level").attr("class"));
2700 // Handle provisional api levels; the provisional level will always be the highest possible level
2701 // Provisional api levels will also have a length; other stuff that's just missing a level won't,
2702 // so leave those kinds of entities at the default level of 1 (for example, the R.styleable class)
2703 if (isNaN(minLevel) && minLevel.length) {
2704 minLevel = maxLevel;
2705 }
2706 var select = $("#apiLevelSelector").html("").change(changeApiLevel);
2707 for (var i = maxLevel-1; i >= 0; i--) {
2708 var option = $("<option />").attr("value",""+SINCE_DATA[i]).append(""+SINCE_DATA[i]);
2709 // if (SINCE_DATA[i] < minLevel) option.addClass("absent"); // always false for strings (codenames)
2710 select.append(option);
2711 }
2712
2713 // get the DOM element and use setAttribute cuz IE6 fails when using jquery .attr('selected',true)
2714 var selectedLevelItem = $("#apiLevelSelector option[value='"+userApiLevel+"']").get(0);
2715 selectedLevelItem.setAttribute('selected',true);
2716}
2717
2718function changeApiLevel() {
2719 maxLevel = SINCE_DATA.length;
2720 var selectedLevel = maxLevel;
2721
2722 selectedLevel = parseInt($("#apiLevelSelector option:selected").val());
2723 toggleVisisbleApis(selectedLevel, "body");
2724
2725 var date = new Date();
2726 date.setTime(date.getTime()+(10*365*24*60*60*1000)); // keep this for 10 years
2727 var expiration = date.toGMTString();
2728 writeCookie(API_LEVEL_COOKIE, selectedLevel, null, expiration);
2729
2730 if (selectedLevel < minLevel) {
2731 var thing = ($("#jd-header").html().indexOf("package") != -1) ? "package" : "class";
Scott Main8f24ca82012-11-16 10:34:22 -08002732 $("#naMessage").show().html("<div><p><strong>This " + thing
2733 + " requires API level " + minLevel + " or higher.</strong></p>"
2734 + "<p>This document is hidden because your selected API level for the documentation is "
2735 + selectedLevel + ". You can change the documentation API level with the selector "
2736 + "above the left navigation.</p>"
2737 + "<p>For more information about specifying the API level your app requires, "
2738 + "read <a href='" + toRoot + "training/basics/supporting-devices/platforms.html'"
2739 + ">Supporting Different Platform Versions</a>.</p>"
2740 + "<input type='button' value='OK, make this page visible' "
2741 + "title='Change the API level to " + minLevel + "' "
2742 + "onclick='$(\"#apiLevelSelector\").val(\"" + minLevel + "\");changeApiLevel();' />"
2743 + "</div>");
Scott Mainf5089842012-08-14 16:31:07 -07002744 } else {
2745 $("#naMessage").hide();
2746 }
2747}
2748
2749function toggleVisisbleApis(selectedLevel, context) {
2750 var apis = $(".api",context);
2751 apis.each(function(i) {
2752 var obj = $(this);
2753 var className = obj.attr("class");
2754 var apiLevelIndex = className.lastIndexOf("-")+1;
2755 var apiLevelEndIndex = className.indexOf(" ", apiLevelIndex);
2756 apiLevelEndIndex = apiLevelEndIndex != -1 ? apiLevelEndIndex : className.length;
2757 var apiLevel = className.substring(apiLevelIndex, apiLevelEndIndex);
2758 if (apiLevel.length == 0) { // for odd cases when the since data is actually missing, just bail
2759 return;
2760 }
2761 apiLevel = parseInt(apiLevel);
2762
2763 // Handle provisional api levels; if this item's level is the provisional one, set it to the max
2764 var selectedLevelNum = parseInt(selectedLevel)
2765 var apiLevelNum = parseInt(apiLevel);
2766 if (isNaN(apiLevelNum)) {
2767 apiLevelNum = maxLevel;
2768 }
2769
2770 // Grey things out that aren't available and give a tooltip title
2771 if (apiLevelNum > selectedLevelNum) {
2772 obj.addClass("absent").attr("title","Requires API Level \""
Scott Main641c2c22013-10-31 14:48:45 -07002773 + apiLevel + "\" or higher. To reveal, change the target API level "
2774 + "above the left navigation.");
Scott Main3b90aff2013-08-01 18:09:35 -07002775 }
Scott Mainf5089842012-08-14 16:31:07 -07002776 else obj.removeClass("absent").removeAttr("title");
2777 });
2778}
2779
2780
2781
2782
2783/* ################# SIDENAV TREE VIEW ################### */
2784
2785function new_node(me, mom, text, link, children_data, api_level)
2786{
2787 var node = new Object();
2788 node.children = Array();
2789 node.children_data = children_data;
2790 node.depth = mom.depth + 1;
2791
2792 node.li = document.createElement("li");
2793 mom.get_children_ul().appendChild(node.li);
2794
2795 node.label_div = document.createElement("div");
2796 node.label_div.className = "label";
2797 if (api_level != null) {
2798 $(node.label_div).addClass("api");
2799 $(node.label_div).addClass("api-level-"+api_level);
2800 }
2801 node.li.appendChild(node.label_div);
2802
2803 if (children_data != null) {
2804 node.expand_toggle = document.createElement("a");
2805 node.expand_toggle.href = "javascript:void(0)";
2806 node.expand_toggle.onclick = function() {
2807 if (node.expanded) {
2808 $(node.get_children_ul()).slideUp("fast");
2809 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2810 node.expanded = false;
2811 } else {
2812 expand_node(me, node);
2813 }
2814 };
2815 node.label_div.appendChild(node.expand_toggle);
2816
2817 node.plus_img = document.createElement("img");
2818 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2819 node.plus_img.className = "plus";
2820 node.plus_img.width = "8";
2821 node.plus_img.border = "0";
2822 node.expand_toggle.appendChild(node.plus_img);
2823
2824 node.expanded = false;
2825 }
2826
2827 var a = document.createElement("a");
2828 node.label_div.appendChild(a);
2829 node.label = document.createTextNode(text);
2830 a.appendChild(node.label);
2831 if (link) {
2832 a.href = me.toroot + link;
2833 } else {
2834 if (children_data != null) {
2835 a.className = "nolink";
2836 a.href = "javascript:void(0)";
2837 a.onclick = node.expand_toggle.onclick;
2838 // This next line shouldn't be necessary. I'll buy a beer for the first
2839 // person who figures out how to remove this line and have the link
2840 // toggle shut on the first try. --joeo@android.com
2841 node.expanded = false;
2842 }
2843 }
Scott Main3b90aff2013-08-01 18:09:35 -07002844
Scott Mainf5089842012-08-14 16:31:07 -07002845
2846 node.children_ul = null;
2847 node.get_children_ul = function() {
2848 if (!node.children_ul) {
2849 node.children_ul = document.createElement("ul");
2850 node.children_ul.className = "children_ul";
2851 node.children_ul.style.display = "none";
2852 node.li.appendChild(node.children_ul);
2853 }
2854 return node.children_ul;
2855 };
2856
2857 return node;
2858}
2859
Robert Lyd2dd6e52012-11-29 21:28:48 -08002860
2861
2862
Scott Mainf5089842012-08-14 16:31:07 -07002863function expand_node(me, node)
2864{
2865 if (node.children_data && !node.expanded) {
2866 if (node.children_visited) {
2867 $(node.get_children_ul()).slideDown("fast");
2868 } else {
2869 get_node(me, node);
2870 if ($(node.label_div).hasClass("absent")) {
2871 $(node.get_children_ul()).addClass("absent");
Scott Main3b90aff2013-08-01 18:09:35 -07002872 }
Scott Mainf5089842012-08-14 16:31:07 -07002873 $(node.get_children_ul()).slideDown("fast");
2874 }
2875 node.plus_img.src = me.toroot + "assets/images/triangle-opened-small.png";
2876 node.expanded = true;
2877
2878 // perform api level toggling because new nodes are new to the DOM
2879 var selectedLevel = $("#apiLevelSelector option:selected").val();
2880 toggleVisisbleApis(selectedLevel, "#side-nav");
2881 }
2882}
2883
2884function get_node(me, mom)
2885{
2886 mom.children_visited = true;
2887 for (var i in mom.children_data) {
2888 var node_data = mom.children_data[i];
2889 mom.children[i] = new_node(me, mom, node_data[0], node_data[1],
2890 node_data[2], node_data[3]);
2891 }
2892}
2893
2894function this_page_relative(toroot)
2895{
2896 var full = document.location.pathname;
2897 var file = "";
2898 if (toroot.substr(0, 1) == "/") {
2899 if (full.substr(0, toroot.length) == toroot) {
2900 return full.substr(toroot.length);
2901 } else {
2902 // the file isn't under toroot. Fail.
2903 return null;
2904 }
2905 } else {
2906 if (toroot != "./") {
2907 toroot = "./" + toroot;
2908 }
2909 do {
2910 if (toroot.substr(toroot.length-3, 3) == "../" || toroot == "./") {
2911 var pos = full.lastIndexOf("/");
2912 file = full.substr(pos) + file;
2913 full = full.substr(0, pos);
2914 toroot = toroot.substr(0, toroot.length-3);
2915 }
2916 } while (toroot != "" && toroot != "/");
2917 return file.substr(1);
2918 }
2919}
2920
2921function find_page(url, data)
2922{
2923 var nodes = data;
2924 var result = null;
2925 for (var i in nodes) {
2926 var d = nodes[i];
2927 if (d[1] == url) {
2928 return new Array(i);
2929 }
2930 else if (d[2] != null) {
2931 result = find_page(url, d[2]);
2932 if (result != null) {
2933 return (new Array(i).concat(result));
2934 }
2935 }
2936 }
2937 return null;
2938}
2939
Scott Mainf5089842012-08-14 16:31:07 -07002940function init_default_navtree(toroot) {
Scott Main25e73002013-03-27 15:24:06 -07002941 // load json file for navtree data
2942 $.getScript(toRoot + 'navtree_data.js', function(data, textStatus, jqxhr) {
2943 // when the file is loaded, initialize the tree
2944 if(jqxhr.status === 200) {
2945 init_navtree("tree-list", toroot, NAVTREE_DATA);
2946 }
2947 });
Scott Main3b90aff2013-08-01 18:09:35 -07002948
Scott Mainf5089842012-08-14 16:31:07 -07002949 // perform api level toggling because because the whole tree is new to the DOM
2950 var selectedLevel = $("#apiLevelSelector option:selected").val();
2951 toggleVisisbleApis(selectedLevel, "#side-nav");
2952}
2953
2954function init_navtree(navtree_id, toroot, root_nodes)
2955{
2956 var me = new Object();
2957 me.toroot = toroot;
2958 me.node = new Object();
2959
2960 me.node.li = document.getElementById(navtree_id);
2961 me.node.children_data = root_nodes;
2962 me.node.children = new Array();
2963 me.node.children_ul = document.createElement("ul");
2964 me.node.get_children_ul = function() { return me.node.children_ul; };
2965 //me.node.children_ul.className = "children_ul";
2966 me.node.li.appendChild(me.node.children_ul);
2967 me.node.depth = 0;
2968
2969 get_node(me, me.node);
2970
2971 me.this_page = this_page_relative(toroot);
2972 me.breadcrumbs = find_page(me.this_page, root_nodes);
2973 if (me.breadcrumbs != null && me.breadcrumbs.length != 0) {
2974 var mom = me.node;
2975 for (var i in me.breadcrumbs) {
2976 var j = me.breadcrumbs[i];
2977 mom = mom.children[j];
2978 expand_node(me, mom);
2979 }
2980 mom.label_div.className = mom.label_div.className + " selected";
2981 addLoadEvent(function() {
2982 scrollIntoView("nav-tree");
2983 });
2984 }
2985}
2986
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07002987
2988
2989
2990
2991
2992
2993
Robert Lyd2dd6e52012-11-29 21:28:48 -08002994/* TODO: eliminate redundancy with non-google functions */
2995function init_google_navtree(navtree_id, toroot, root_nodes)
2996{
2997 var me = new Object();
2998 me.toroot = toroot;
2999 me.node = new Object();
3000
3001 me.node.li = document.getElementById(navtree_id);
3002 me.node.children_data = root_nodes;
3003 me.node.children = new Array();
3004 me.node.children_ul = document.createElement("ul");
3005 me.node.get_children_ul = function() { return me.node.children_ul; };
3006 //me.node.children_ul.className = "children_ul";
3007 me.node.li.appendChild(me.node.children_ul);
3008 me.node.depth = 0;
3009
3010 get_google_node(me, me.node);
Robert Lyd2dd6e52012-11-29 21:28:48 -08003011}
3012
3013function new_google_node(me, mom, text, link, children_data, api_level)
3014{
3015 var node = new Object();
3016 var child;
3017 node.children = Array();
3018 node.children_data = children_data;
3019 node.depth = mom.depth + 1;
3020 node.get_children_ul = function() {
3021 if (!node.children_ul) {
Scott Main3b90aff2013-08-01 18:09:35 -07003022 node.children_ul = document.createElement("ul");
3023 node.children_ul.className = "tree-list-children";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003024 node.li.appendChild(node.children_ul);
3025 }
3026 return node.children_ul;
3027 };
3028 node.li = document.createElement("li");
3029
3030 mom.get_children_ul().appendChild(node.li);
Scott Main3b90aff2013-08-01 18:09:35 -07003031
3032
Robert Lyd2dd6e52012-11-29 21:28:48 -08003033 if(link) {
3034 child = document.createElement("a");
3035
3036 }
3037 else {
3038 child = document.createElement("span");
Scott Mainac71b2b2012-11-30 14:40:58 -08003039 child.className = "tree-list-subtitle";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003040
3041 }
3042 if (children_data != null) {
3043 node.li.className="nav-section";
3044 node.label_div = document.createElement("div");
Scott Main3b90aff2013-08-01 18:09:35 -07003045 node.label_div.className = "nav-section-header-ref";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003046 node.li.appendChild(node.label_div);
3047 get_google_node(me, node);
3048 node.label_div.appendChild(child);
3049 }
3050 else {
3051 node.li.appendChild(child);
3052 }
3053 if(link) {
3054 child.href = me.toroot + link;
3055 }
3056 node.label = document.createTextNode(text);
3057 child.appendChild(node.label);
3058
3059 node.children_ul = null;
3060
3061 return node;
3062}
3063
3064function get_google_node(me, mom)
3065{
3066 mom.children_visited = true;
3067 var linkText;
3068 for (var i in mom.children_data) {
3069 var node_data = mom.children_data[i];
3070 linkText = node_data[0];
3071
3072 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3073 linkText = linkText.substr(19, linkText.length);
3074 }
3075 mom.children[i] = new_google_node(me, mom, linkText, node_data[1],
3076 node_data[2], node_data[3]);
3077 }
3078}
Scott Mainad08f072013-08-20 16:49:57 -07003079
3080
3081
3082
3083
3084
3085/****** NEW version of script to build google and sample navs dynamically ******/
3086// TODO: update Google reference docs to tolerate this new implementation
3087
Scott Maine624b3f2013-09-12 12:56:41 -07003088var NODE_NAME = 0;
3089var NODE_HREF = 1;
3090var NODE_GROUP = 2;
3091var NODE_TAGS = 3;
3092var NODE_CHILDREN = 4;
3093
Scott Mainad08f072013-08-20 16:49:57 -07003094function init_google_navtree2(navtree_id, data)
3095{
3096 var $containerUl = $("#"+navtree_id);
Scott Mainad08f072013-08-20 16:49:57 -07003097 for (var i in data) {
3098 var node_data = data[i];
3099 $containerUl.append(new_google_node2(node_data));
3100 }
3101
Scott Main70557ee2013-10-30 14:47:40 -07003102 // Make all third-generation list items 'sticky' to prevent them from collapsing
3103 $containerUl.find('li li li.nav-section').addClass('sticky');
3104
Scott Mainad08f072013-08-20 16:49:57 -07003105 initExpandableNavItems("#"+navtree_id);
3106}
3107
3108function new_google_node2(node_data)
3109{
Scott Maine624b3f2013-09-12 12:56:41 -07003110 var linkText = node_data[NODE_NAME];
Scott Mainad08f072013-08-20 16:49:57 -07003111 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3112 linkText = linkText.substr(19, linkText.length);
3113 }
3114 var $li = $('<li>');
3115 var $a;
Scott Maine624b3f2013-09-12 12:56:41 -07003116 if (node_data[NODE_HREF] != null) {
Scott Main70557ee2013-10-30 14:47:40 -07003117 $a = $('<a href="' + toRoot + node_data[NODE_HREF] + '" title="' + linkText + '" >'
3118 + linkText + '</a>');
Scott Mainad08f072013-08-20 16:49:57 -07003119 } else {
Scott Main70557ee2013-10-30 14:47:40 -07003120 $a = $('<a href="#" onclick="return false;" title="' + linkText + '" >'
3121 + linkText + '/</a>');
Scott Mainad08f072013-08-20 16:49:57 -07003122 }
3123 var $childUl = $('<ul>');
Scott Maine624b3f2013-09-12 12:56:41 -07003124 if (node_data[NODE_CHILDREN] != null) {
Scott Mainad08f072013-08-20 16:49:57 -07003125 $li.addClass("nav-section");
3126 $a = $('<div class="nav-section-header">').append($a);
Scott Maine624b3f2013-09-12 12:56:41 -07003127 if (node_data[NODE_HREF] == null) $a.addClass('empty');
Scott Mainad08f072013-08-20 16:49:57 -07003128
Scott Maine624b3f2013-09-12 12:56:41 -07003129 for (var i in node_data[NODE_CHILDREN]) {
3130 var child_node_data = node_data[NODE_CHILDREN][i];
Scott Mainad08f072013-08-20 16:49:57 -07003131 $childUl.append(new_google_node2(child_node_data));
3132 }
3133 $li.append($childUl);
3134 }
3135 $li.prepend($a);
3136
3137 return $li;
3138}
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
Robert Lyd2dd6e52012-11-29 21:28:48 -08003150function showGoogleRefTree() {
3151 init_default_google_navtree(toRoot);
3152 init_default_gcm_navtree(toRoot);
Robert Lyd2dd6e52012-11-29 21:28:48 -08003153}
3154
3155function init_default_google_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07003156 // load json file for navtree data
3157 $.getScript(toRoot + 'gms_navtree_data.js', function(data, textStatus, jqxhr) {
3158 // when the file is loaded, initialize the tree
3159 if(jqxhr.status === 200) {
3160 init_google_navtree("gms-tree-list", toroot, GMS_NAVTREE_DATA);
3161 highlightSidenav();
3162 resizeNav();
3163 }
3164 });
Robert Lyd2dd6e52012-11-29 21:28:48 -08003165}
3166
3167function init_default_gcm_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07003168 // load json file for navtree data
3169 $.getScript(toRoot + 'gcm_navtree_data.js', function(data, textStatus, jqxhr) {
3170 // when the file is loaded, initialize the tree
3171 if(jqxhr.status === 200) {
3172 init_google_navtree("gcm-tree-list", toroot, GCM_NAVTREE_DATA);
3173 highlightSidenav();
3174 resizeNav();
3175 }
3176 });
Robert Lyd2dd6e52012-11-29 21:28:48 -08003177}
3178
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003179function showSamplesRefTree() {
3180 init_default_samples_navtree(toRoot);
3181}
3182
3183function init_default_samples_navtree(toroot) {
3184 // load json file for navtree data
3185 $.getScript(toRoot + 'samples_navtree_data.js', function(data, textStatus, jqxhr) {
3186 // when the file is loaded, initialize the tree
3187 if(jqxhr.status === 200) {
Scott Mainf1435b72013-10-30 16:27:38 -07003188 // hack to remove the "about the samples" link then put it back in
3189 // after we nuke the list to remove the dummy static list of samples
3190 var $firstLi = $("#nav.samples-nav > li:first-child").clone();
3191 $("#nav.samples-nav").empty();
3192 $("#nav.samples-nav").append($firstLi);
3193
Scott Mainad08f072013-08-20 16:49:57 -07003194 init_google_navtree2("nav.samples-nav", SAMPLES_NAVTREE_DATA);
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003195 highlightSidenav();
3196 resizeNav();
Scott Main03aca9a2013-10-31 07:20:55 -07003197 if ($("#jd-content #samples").length) {
3198 showSamples();
3199 }
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003200 }
3201 });
3202}
3203
Scott Mainf5089842012-08-14 16:31:07 -07003204/* TOGGLE INHERITED MEMBERS */
3205
3206/* Toggle an inherited class (arrow toggle)
3207 * @param linkObj The link that was clicked.
3208 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3209 * 'null' to simply toggle.
3210 */
3211function toggleInherited(linkObj, expand) {
3212 var base = linkObj.getAttribute("id");
3213 var list = document.getElementById(base + "-list");
3214 var summary = document.getElementById(base + "-summary");
3215 var trigger = document.getElementById(base + "-trigger");
3216 var a = $(linkObj);
3217 if ( (expand == null && a.hasClass("closed")) || expand ) {
3218 list.style.display = "none";
3219 summary.style.display = "block";
3220 trigger.src = toRoot + "assets/images/triangle-opened.png";
3221 a.removeClass("closed");
3222 a.addClass("opened");
3223 } else if ( (expand == null && a.hasClass("opened")) || (expand == false) ) {
3224 list.style.display = "block";
3225 summary.style.display = "none";
3226 trigger.src = toRoot + "assets/images/triangle-closed.png";
3227 a.removeClass("opened");
3228 a.addClass("closed");
3229 }
3230 return false;
3231}
3232
3233/* Toggle all inherited classes in a single table (e.g. all inherited methods)
3234 * @param linkObj The link that was clicked.
3235 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3236 * 'null' to simply toggle.
3237 */
3238function toggleAllInherited(linkObj, expand) {
3239 var a = $(linkObj);
3240 var table = $(a.parent().parent().parent()); // ugly way to get table/tbody
3241 var expandos = $(".jd-expando-trigger", table);
3242 if ( (expand == null && a.text() == "[Expand]") || expand ) {
3243 expandos.each(function(i) {
3244 toggleInherited(this, true);
3245 });
3246 a.text("[Collapse]");
3247 } else if ( (expand == null && a.text() == "[Collapse]") || (expand == false) ) {
3248 expandos.each(function(i) {
3249 toggleInherited(this, false);
3250 });
3251 a.text("[Expand]");
3252 }
3253 return false;
3254}
3255
3256/* Toggle all inherited members in the class (link in the class title)
3257 */
3258function toggleAllClassInherited() {
3259 var a = $("#toggleAllClassInherited"); // get toggle link from class title
3260 var toggles = $(".toggle-all", $("#body-content"));
3261 if (a.text() == "[Expand All]") {
3262 toggles.each(function(i) {
3263 toggleAllInherited(this, true);
3264 });
3265 a.text("[Collapse All]");
3266 } else {
3267 toggles.each(function(i) {
3268 toggleAllInherited(this, false);
3269 });
3270 a.text("[Expand All]");
3271 }
3272 return false;
3273}
3274
3275/* Expand all inherited members in the class. Used when initiating page search */
3276function ensureAllInheritedExpanded() {
3277 var toggles = $(".toggle-all", $("#body-content"));
3278 toggles.each(function(i) {
3279 toggleAllInherited(this, true);
3280 });
3281 $("#toggleAllClassInherited").text("[Collapse All]");
3282}
3283
3284
3285/* HANDLE KEY EVENTS
3286 * - Listen for Ctrl+F (Cmd on Mac) and expand all inherited members (to aid page search)
3287 */
3288var agent = navigator['userAgent'].toLowerCase();
3289var mac = agent.indexOf("macintosh") != -1;
3290
3291$(document).keydown( function(e) {
3292var control = mac ? e.metaKey && !e.ctrlKey : e.ctrlKey; // get ctrl key
3293 if (control && e.which == 70) { // 70 is "F"
3294 ensureAllInheritedExpanded();
3295 }
3296});
Scott Main498d7102013-08-21 15:47:38 -07003297
3298
3299
3300
3301
3302
3303/* On-demand functions */
3304
3305/** Move sample code line numbers out of PRE block and into non-copyable column */
3306function initCodeLineNumbers() {
3307 var numbers = $("#codesample-block a.number");
3308 if (numbers.length) {
3309 $("#codesample-line-numbers").removeClass("hidden").append(numbers);
3310 }
3311
3312 $(document).ready(function() {
3313 // select entire line when clicked
3314 $("span.code-line").click(function() {
3315 if (!shifted) {
3316 selectText(this);
3317 }
3318 });
3319 // invoke line link on double click
3320 $(".code-line").dblclick(function() {
3321 document.location.hash = $(this).attr('id');
3322 });
3323 // highlight the line when hovering on the number
3324 $("#codesample-line-numbers a.number").mouseover(function() {
3325 var id = $(this).attr('href');
3326 $(id).css('background','#e7e7e7');
3327 });
3328 $("#codesample-line-numbers a.number").mouseout(function() {
3329 var id = $(this).attr('href');
3330 $(id).css('background','none');
3331 });
3332 });
3333}
3334
3335// create SHIFT key binder to avoid the selectText method when selecting multiple lines
3336var shifted = false;
3337$(document).bind('keyup keydown', function(e){shifted = e.shiftKey; return true;} );
3338
3339// courtesy of jasonedelman.com
3340function selectText(element) {
3341 var doc = document
3342 , range, selection
3343 ;
3344 if (doc.body.createTextRange) { //ms
3345 range = doc.body.createTextRange();
3346 range.moveToElementText(element);
3347 range.select();
3348 } else if (window.getSelection) { //all others
Scott Main70557ee2013-10-30 14:47:40 -07003349 selection = window.getSelection();
Scott Main498d7102013-08-21 15:47:38 -07003350 range = doc.createRange();
3351 range.selectNodeContents(element);
3352 selection.removeAllRanges();
3353 selection.addRange(range);
3354 }
Scott Main285f0772013-08-22 23:22:09 +00003355}
Scott Main03aca9a2013-10-31 07:20:55 -07003356
3357
3358
3359
3360/** Display links and other information about samples that match the
3361 group specified by the URL */
3362function showSamples() {
3363 var group = $("#samples").attr('class');
3364 $("#samples").html("<p>Here are some samples for <b>" + group + "</b> apps:</p>");
3365
3366 var $ul = $("<ul>");
3367 $selectedLi = $("#nav li.selected");
3368
3369 $selectedLi.children("ul").children("li").each(function() {
3370 var $li = $("<li>").append($(this).find("a").first().clone());
3371 $ul.append($li);
3372 });
3373
3374 $("#samples").append($ul);
3375
3376}
Dirk Doughertyc3921652014-05-13 16:55:26 -07003377
3378
3379
3380/* ########################################################## */
3381/* ################### RESOURCE CARDS ##################### */
3382/* ########################################################## */
3383
3384/** Handle resource queries, collections, and grids (sections). Requires
3385 jd_tag_helpers.js and the *_unified_data.js to be loaded. */
3386
3387(function() {
3388 // Prevent the same resource from being loaded more than once per page.
3389 var addedPageResources = {};
3390
3391 $(document).ready(function() {
3392 $('.resource-widget').each(function() {
3393 initResourceWidget(this);
3394 });
3395
3396 /* Pass the line height to ellipsisfade() to adjust the height of the
3397 text container to show the max number of lines possible, without
3398 showing lines that are cut off. This works with the css ellipsis
3399 classes to fade last text line and apply an ellipsis char. */
3400
Scott Mainb16376f2014-05-21 20:35:47 -07003401 //card text currently uses 15px line height.
Dirk Doughertyc3921652014-05-13 16:55:26 -07003402 var lineHeight = 15;
3403 $('.card-info .text').ellipsisfade(lineHeight);
3404 });
3405
3406 /*
3407 Three types of resource layouts:
3408 Flow - Uses a fixed row-height flow using float left style.
3409 Carousel - Single card slideshow all same dimension absolute.
3410 Stack - Uses fixed columns and flexible element height.
3411 */
3412 function initResourceWidget(widget) {
3413 var $widget = $(widget);
3414 var isFlow = $widget.hasClass('resource-flow-layout'),
3415 isCarousel = $widget.hasClass('resource-carousel-layout'),
3416 isStack = $widget.hasClass('resource-stack-layout');
3417
3418 // find size of widget by pulling out its class name
3419 var sizeCols = 1;
3420 var m = $widget.get(0).className.match(/\bcol-(\d+)\b/);
3421 if (m) {
3422 sizeCols = parseInt(m[1], 10);
3423 }
3424
3425 var opts = {
3426 cardSizes: ($widget.data('cardsizes') || '').split(','),
3427 maxResults: parseInt($widget.data('maxresults') || '100', 10),
3428 itemsPerPage: $widget.data('itemsperpage'),
3429 sortOrder: $widget.data('sortorder'),
3430 query: $widget.data('query'),
3431 section: $widget.data('section'),
Robert Lye7eeb402014-06-03 19:35:24 -07003432 sizeCols: sizeCols,
3433 /* Added by LFL 6/6/14 */
3434 resourceStyle: $widget.data('resourcestyle') || 'card',
3435 stackSort: $widget.data('stacksort') || 'true'
Dirk Doughertyc3921652014-05-13 16:55:26 -07003436 };
3437
3438 // run the search for the set of resources to show
3439
3440 var resources = buildResourceList(opts);
3441
3442 if (isFlow) {
3443 drawResourcesFlowWidget($widget, opts, resources);
3444 } else if (isCarousel) {
3445 drawResourcesCarouselWidget($widget, opts, resources);
3446 } else if (isStack) {
Robert Lye7eeb402014-06-03 19:35:24 -07003447 /* Looks like this got removed and is not used, so repurposing for the
3448 homepage style layout.
3449 Modified by LFL 6/6/14
3450 */
3451 //var sections = buildSectionList(opts);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003452 opts['numStacks'] = $widget.data('numstacks');
Robert Lye7eeb402014-06-03 19:35:24 -07003453 drawResourcesStackWidget($widget, opts, resources/*, sections*/);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003454 }
3455 }
3456
3457 /* Initializes a Resource Carousel Widget */
3458 function drawResourcesCarouselWidget($widget, opts, resources) {
3459 $widget.empty();
3460 var plusone = true; //always show plusone on carousel
3461
3462 $widget.addClass('resource-card slideshow-container')
3463 .append($('<a>').addClass('slideshow-prev').text('Prev'))
3464 .append($('<a>').addClass('slideshow-next').text('Next'));
3465
3466 var css = { 'width': $widget.width() + 'px',
3467 'height': $widget.height() + 'px' };
3468
3469 var $ul = $('<ul>');
3470
3471 for (var i = 0; i < resources.length; ++i) {
Dirk Doughertyc3921652014-05-13 16:55:26 -07003472 var $card = $('<a>')
Robert Lye7eeb402014-06-03 19:35:24 -07003473 .attr('href', cleanUrl(resources[i].url))
Dirk Doughertyc3921652014-05-13 16:55:26 -07003474 .decorateResourceCard(resources[i],plusone);
3475
3476 $('<li>').css(css)
3477 .append($card)
3478 .appendTo($ul);
3479 }
3480
3481 $('<div>').addClass('frame')
3482 .append($ul)
3483 .appendTo($widget);
3484
3485 $widget.dacSlideshow({
3486 auto: true,
3487 btnPrev: '.slideshow-prev',
3488 btnNext: '.slideshow-next'
3489 });
3490 };
3491
Robert Lye7eeb402014-06-03 19:35:24 -07003492 /* Initializes a Resource Card Stack Widget (column-based layout)
3493 Modified by LFL 6/6/14
3494 */
Dirk Doughertyc3921652014-05-13 16:55:26 -07003495 function drawResourcesStackWidget($widget, opts, resources, sections) {
3496 // Don't empty widget, grab all items inside since they will be the first
3497 // items stacked, followed by the resource query
3498 var plusone = true; //by default show plusone on section cards
3499 var cards = $widget.find('.resource-card').detach().toArray();
3500 var numStacks = opts.numStacks || 1;
3501 var $stacks = [];
3502 var urlString;
3503
3504 for (var i = 0; i < numStacks; ++i) {
3505 $stacks[i] = $('<div>').addClass('resource-card-stack')
3506 .appendTo($widget);
3507 }
3508
3509 var sectionResources = [];
3510
3511 // Extract any subsections that are actually resource cards
Robert Lye7eeb402014-06-03 19:35:24 -07003512 if (sections) {
3513 for (var i = 0; i < sections.length; ++i) {
3514 if (!sections[i].sections || !sections[i].sections.length) {
3515 // Render it as a resource card
3516 sectionResources.push(
3517 $('<a>')
3518 .addClass('resource-card section-card')
3519 .attr('href', cleanUrl(sections[i].resource.url))
3520 .decorateResourceCard(sections[i].resource,plusone)[0]
3521 );
Dirk Doughertyc3921652014-05-13 16:55:26 -07003522
Robert Lye7eeb402014-06-03 19:35:24 -07003523 } else {
3524 cards.push(
3525 $('<div>')
3526 .addClass('resource-card section-card-menu')
3527 .decorateResourceSection(sections[i],plusone)[0]
3528 );
3529 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003530 }
3531 }
3532
3533 cards = cards.concat(sectionResources);
3534
3535 for (var i = 0; i < resources.length; ++i) {
Robert Lye7eeb402014-06-03 19:35:24 -07003536 var $card = createResourceElement(resources[i], opts);
3537
3538 if (opts.resourceStyle.indexOf('related') > -1) {
3539 $card.addClass('related-card');
3540 }
3541
Dirk Doughertyc3921652014-05-13 16:55:26 -07003542 cards.push($card[0]);
3543 }
3544
Robert Lye7eeb402014-06-03 19:35:24 -07003545 if (opts.stackSort != 'false') {
3546 for (var i = 0; i < cards.length; ++i) {
3547 // Find the stack with the shortest height, but give preference to
3548 // left to right order.
3549 var minHeight = $stacks[0].height();
3550 var minIndex = 0;
Dirk Doughertyc3921652014-05-13 16:55:26 -07003551
Robert Lye7eeb402014-06-03 19:35:24 -07003552 for (var j = 1; j < numStacks; ++j) {
3553 var height = $stacks[j].height();
3554 if (height < minHeight - 45) {
3555 minHeight = height;
3556 minIndex = j;
3557 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003558 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003559
Robert Lye7eeb402014-06-03 19:35:24 -07003560 $stacks[minIndex].append($(cards[i]));
3561 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003562 }
3563
3564 };
Robert Lye7eeb402014-06-03 19:35:24 -07003565
3566 /*
3567 Create a resource card using the given resource object and a list of html
3568 configured options. Returns a jquery object containing the element.
3569 */
3570 function createResourceElement(resource, opts, plusone) {
3571 var $el;
3572
3573 // The difference here is that generic cards are not entirely clickable
3574 // so its a div instead of an a tag, also the generic one is not given
3575 // the resource-card class so it appears with a transparent background
3576 // and can be styled in whatever way the css setup.
3577 if (opts.resourceStyle == 'generic') {
3578 $el = $('<div>')
3579 .addClass('resource')
3580 .attr('href', cleanUrl(resource.url))
3581 .decorateResource(resource, opts);
3582 } else {
3583 var cls = 'resource resource-card';
3584
3585 $el = $('<a>')
3586 .addClass(cls)
3587 .attr('href', cleanUrl(resource.url))
3588 .decorateResourceCard(resource, plusone);
3589 }
3590
3591 return $el;
3592 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003593
3594 /* Initializes a flow widget, see distribute.scss for generating accompanying css */
3595 function drawResourcesFlowWidget($widget, opts, resources) {
3596 $widget.empty();
3597 var cardSizes = opts.cardSizes || ['6x6'];
3598 var i = 0, j = 0;
3599 var plusone = true; // by default show plusone on resource cards
3600
3601 while (i < resources.length) {
3602 var cardSize = cardSizes[j++ % cardSizes.length];
3603 cardSize = cardSize.replace(/^\s+|\s+$/,'');
Dirk Doughertyc3921652014-05-13 16:55:26 -07003604 // Some card sizes do not get a plusone button, such as where space is constrained
3605 // or for cards commonly embedded in docs (to improve overall page speed).
3606 plusone = !((cardSize == "6x2") || (cardSize == "6x3") ||
3607 (cardSize == "9x2") || (cardSize == "9x3") ||
3608 (cardSize == "12x2") || (cardSize == "12x3"));
3609
3610 // A stack has a third dimension which is the number of stacked items
3611 var isStack = cardSize.match(/(\d+)x(\d+)x(\d+)/);
3612 var stackCount = 0;
3613 var $stackDiv = null;
3614
3615 if (isStack) {
3616 // Create a stack container which should have the dimensions defined
3617 // by the product of the items inside.
3618 $stackDiv = $('<div>').addClass('resource-card-stack resource-card-' + isStack[1]
3619 + 'x' + isStack[2] * isStack[3]) .appendTo($widget);
3620 }
3621
3622 // Build each stack item or just a single item
3623 do {
3624 var resource = resources[i];
Dirk Doughertyc3921652014-05-13 16:55:26 -07003625
Robert Lye7eeb402014-06-03 19:35:24 -07003626 var $card = createResourceElement(resources[i], opts, plusone);
3627
3628 $card.addClass('resource-card-' + cardSize +
3629 ' resource-card-' + resource.type);
3630
Dirk Doughertyc3921652014-05-13 16:55:26 -07003631 if (isStack) {
3632 $card.addClass('resource-card-' + isStack[1] + 'x' + isStack[2]);
3633 if (++stackCount == parseInt(isStack[3])) {
3634 $card.addClass('resource-card-row-stack-last');
3635 stackCount = 0;
3636 }
3637 } else {
3638 stackCount = 0;
3639 }
3640
Robert Lye7eeb402014-06-03 19:35:24 -07003641 $card.appendTo($stackDiv || $widget);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003642
3643 } while (++i < resources.length && stackCount > 0);
3644 }
3645 }
3646
3647 /* Build a site map of resources using a section as a root. */
3648 function buildSectionList(opts) {
3649 if (opts.section && SECTION_BY_ID[opts.section]) {
3650 return SECTION_BY_ID[opts.section].sections || [];
3651 }
3652 return [];
3653 }
3654
3655 function buildResourceList(opts) {
3656 var maxResults = opts.maxResults || 100;
3657
3658 var query = opts.query || '';
3659 var expressions = parseResourceQuery(query);
3660 var addedResourceIndices = {};
3661 var results = [];
3662
3663 for (var i = 0; i < expressions.length; i++) {
3664 var clauses = expressions[i];
3665
3666 // build initial set of resources from first clause
3667 var firstClause = clauses[0];
3668 var resources = [];
3669 switch (firstClause.attr) {
3670 case 'type':
3671 resources = ALL_RESOURCES_BY_TYPE[firstClause.value];
3672 break;
3673 case 'lang':
3674 resources = ALL_RESOURCES_BY_LANG[firstClause.value];
3675 break;
3676 case 'tag':
3677 resources = ALL_RESOURCES_BY_TAG[firstClause.value];
3678 break;
3679 case 'collection':
3680 var urls = RESOURCE_COLLECTIONS[firstClause.value].resources || [];
3681 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3682 break;
3683 case 'section':
3684 var urls = SITE_MAP[firstClause.value].sections || [];
3685 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3686 break;
3687 }
3688 // console.log(firstClause.attr + ':' + firstClause.value);
3689 resources = resources || [];
3690
3691 // use additional clauses to filter corpus
3692 if (clauses.length > 1) {
3693 var otherClauses = clauses.slice(1);
3694 resources = resources.filter(getResourceMatchesClausesFilter(otherClauses));
3695 }
3696
3697 // filter out resources already added
3698 if (i > 1) {
3699 resources = resources.filter(getResourceNotAlreadyAddedFilter(addedResourceIndices));
3700 }
3701
3702 // add to list of already added indices
3703 for (var j = 0; j < resources.length; j++) {
3704 // console.log(resources[j].title);
3705 addedResourceIndices[resources[j].index] = 1;
3706 }
3707
3708 // concat to final results list
3709 results = results.concat(resources);
3710 }
3711
3712 if (opts.sortOrder && results.length) {
3713 var attr = opts.sortOrder;
3714
3715 if (opts.sortOrder == 'random') {
3716 var i = results.length, j, temp;
3717 while (--i) {
3718 j = Math.floor(Math.random() * (i + 1));
3719 temp = results[i];
3720 results[i] = results[j];
3721 results[j] = temp;
3722 }
3723 } else {
3724 var desc = attr.charAt(0) == '-';
3725 if (desc) {
3726 attr = attr.substring(1);
3727 }
3728 results = results.sort(function(x,y) {
3729 return (desc ? -1 : 1) * (parseInt(x[attr], 10) - parseInt(y[attr], 10));
3730 });
3731 }
3732 }
3733
3734 results = results.filter(getResourceNotAlreadyAddedFilter(addedPageResources));
3735 results = results.slice(0, maxResults);
3736
3737 for (var j = 0; j < results.length; ++j) {
3738 addedPageResources[results[j].index] = 1;
3739 }
3740
3741 return results;
3742 }
3743
3744
3745 function getResourceNotAlreadyAddedFilter(addedResourceIndices) {
3746 return function(resource) {
3747 return !addedResourceIndices[resource.index];
3748 };
3749 }
3750
3751
3752 function getResourceMatchesClausesFilter(clauses) {
3753 return function(resource) {
3754 return doesResourceMatchClauses(resource, clauses);
3755 };
3756 }
3757
3758
3759 function doesResourceMatchClauses(resource, clauses) {
3760 for (var i = 0; i < clauses.length; i++) {
3761 var map;
3762 switch (clauses[i].attr) {
3763 case 'type':
3764 map = IS_RESOURCE_OF_TYPE[clauses[i].value];
3765 break;
3766 case 'lang':
3767 map = IS_RESOURCE_IN_LANG[clauses[i].value];
3768 break;
3769 case 'tag':
3770 map = IS_RESOURCE_TAGGED[clauses[i].value];
3771 break;
3772 }
3773
3774 if (!map || (!!clauses[i].negative ? map[resource.index] : !map[resource.index])) {
3775 return clauses[i].negative;
3776 }
3777 }
3778 return true;
3779 }
Robert Lye7eeb402014-06-03 19:35:24 -07003780
3781 function cleanUrl(url)
3782 {
3783 if (url && url.indexOf('//') === -1) {
3784 url = toRoot + url;
3785 }
3786
3787 return url;
3788 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003789
3790
3791 function parseResourceQuery(query) {
3792 // Parse query into array of expressions (expression e.g. 'tag:foo + type:video')
3793 var expressions = [];
3794 var expressionStrs = query.split(',') || [];
3795 for (var i = 0; i < expressionStrs.length; i++) {
3796 var expr = expressionStrs[i] || '';
3797
3798 // Break expression into clauses (clause e.g. 'tag:foo')
3799 var clauses = [];
3800 var clauseStrs = expr.split(/(?=[\+\-])/);
3801 for (var j = 0; j < clauseStrs.length; j++) {
3802 var clauseStr = clauseStrs[j] || '';
3803
3804 // Get attribute and value from clause (e.g. attribute='tag', value='foo')
3805 var parts = clauseStr.split(':');
3806 var clause = {};
3807
3808 clause.attr = parts[0].replace(/^\s+|\s+$/g,'');
3809 if (clause.attr) {
3810 if (clause.attr.charAt(0) == '+') {
3811 clause.attr = clause.attr.substring(1);
3812 } else if (clause.attr.charAt(0) == '-') {
3813 clause.negative = true;
3814 clause.attr = clause.attr.substring(1);
3815 }
3816 }
3817
3818 if (parts.length > 1) {
3819 clause.value = parts[1].replace(/^\s+|\s+$/g,'');
3820 }
3821
3822 clauses.push(clause);
3823 }
3824
3825 if (!clauses.length) {
3826 continue;
3827 }
3828
3829 expressions.push(clauses);
3830 }
3831
3832 return expressions;
3833 }
3834})();
3835
3836(function($) {
Robert Lye7eeb402014-06-03 19:35:24 -07003837
3838 /*
3839 Utility method for creating dom for the description area of a card.
3840 Used in decorateResourceCard and decorateResource.
3841 */
3842 function buildResourceCardDescription(resource, plusone) {
3843 var $description = $('<div>').addClass('description ellipsis');
3844
3845 $description.append($('<div>').addClass('text').html(resource.summary));
3846
3847 if (resource.cta) {
3848 $description.append($('<a>').addClass('cta').html(resource.cta));
3849 }
3850
3851 if (plusone) {
3852 var plusurl = resource.url.indexOf("//") > -1 ? resource.url :
3853 "//developer.android.com/" + resource.url;
3854
3855 $description.append($('<div>').addClass('util')
3856 .append($('<div>').addClass('g-plusone')
3857 .attr('data-size', 'small')
3858 .attr('data-align', 'right')
3859 .attr('data-href', plusurl)));
3860 }
3861
3862 return $description;
3863 }
3864
3865
Dirk Doughertyc3921652014-05-13 16:55:26 -07003866 /* Simple jquery function to create dom for a standard resource card */
3867 $.fn.decorateResourceCard = function(resource,plusone) {
3868 var section = resource.group || resource.type;
Robert Lye7eeb402014-06-03 19:35:24 -07003869 var imgUrl = resource.image ||
3870 'assets/images/resource-card-default-android.jpg';
3871
3872 if (imgUrl.indexOf('//') === -1) {
3873 imgUrl = toRoot + imgUrl;
Dirk Doughertyc3921652014-05-13 16:55:26 -07003874 }
Robert Lye7eeb402014-06-03 19:35:24 -07003875
3876 $('<div>').addClass('card-bg')
3877 .css('background-image', 'url(' + (imgUrl || toRoot +
3878 'assets/images/resource-card-default-android.jpg') + ')')
Dirk Doughertyc3921652014-05-13 16:55:26 -07003879 .appendTo(this);
Robert Lye7eeb402014-06-03 19:35:24 -07003880
3881 $('<div>').addClass('card-info' + (!resource.summary ? ' empty-desc' : ''))
3882 .append($('<div>').addClass('section').text(section))
3883 .append($('<div>').addClass('title').html(resource.title))
3884 .append(buildResourceCardDescription(resource, plusone))
3885 .appendTo(this);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003886
3887 return this;
3888 };
3889
3890 /* Simple jquery function to create dom for a resource section card (menu) */
3891 $.fn.decorateResourceSection = function(section,plusone) {
3892 var resource = section.resource;
3893 //keep url clean for matching and offline mode handling
3894 var urlPrefix = resource.image.indexOf("//") > -1 ? "" : toRoot;
3895 var $base = $('<a>')
3896 .addClass('card-bg')
3897 .attr('href', resource.url)
3898 .append($('<div>').addClass('card-section-icon')
3899 .append($('<div>').addClass('icon'))
3900 .append($('<div>').addClass('section').html(resource.title)))
3901 .appendTo(this);
3902
3903 var $cardInfo = $('<div>').addClass('card-info').appendTo(this);
3904
3905 if (section.sections && section.sections.length) {
3906 // Recurse the section sub-tree to find a resource image.
3907 var stack = [section];
3908
3909 while (stack.length) {
3910 if (stack[0].resource.image) {
3911 $base.css('background-image', 'url(' + urlPrefix + stack[0].resource.image + ')');
3912 break;
3913 }
3914
3915 if (stack[0].sections) {
3916 stack = stack.concat(stack[0].sections);
3917 }
3918
3919 stack.shift();
3920 }
3921
3922 var $ul = $('<ul>')
3923 .appendTo($cardInfo);
3924
3925 var max = section.sections.length > 3 ? 3 : section.sections.length;
3926
3927 for (var i = 0; i < max; ++i) {
3928
3929 var subResource = section.sections[i];
3930 if (!plusone) {
3931 $('<li>')
3932 .append($('<a>').attr('href', subResource.url)
3933 .append($('<div>').addClass('title').html(subResource.title))
3934 .append($('<div>').addClass('description ellipsis')
3935 .append($('<div>').addClass('text').html(subResource.summary))
3936 .append($('<div>').addClass('util'))))
3937 .appendTo($ul);
3938 } else {
3939 $('<li>')
3940 .append($('<a>').attr('href', subResource.url)
3941 .append($('<div>').addClass('title').html(subResource.title))
3942 .append($('<div>').addClass('description ellipsis')
3943 .append($('<div>').addClass('text').html(subResource.summary))
3944 .append($('<div>').addClass('util')
3945 .append($('<div>').addClass('g-plusone')
3946 .attr('data-size', 'small')
3947 .attr('data-align', 'right')
3948 .attr('data-href', resource.url)))))
3949 .appendTo($ul);
3950 }
3951 }
3952
3953 // Add a more row
3954 if (max < section.sections.length) {
3955 $('<li>')
3956 .append($('<a>').attr('href', resource.url)
3957 .append($('<div>')
3958 .addClass('title')
3959 .text('More')))
3960 .appendTo($ul);
3961 }
3962 } else {
3963 // No sub-resources, just render description?
3964 }
3965
3966 return this;
3967 };
Robert Lye7eeb402014-06-03 19:35:24 -07003968
3969
3970
3971
3972 /* Render other types of resource styles that are not cards. */
3973 $.fn.decorateResource = function(resource, opts) {
3974 var imgUrl = resource.image ||
3975 'assets/images/resource-card-default-android.jpg';
3976 var linkUrl = resource.url;
3977
3978 if (imgUrl.indexOf('//') === -1) {
3979 imgUrl = toRoot + imgUrl;
3980 }
3981
3982 if (linkUrl && linkUrl.indexOf('//') === -1) {
3983 linkUrl = toRoot + linkUrl;
3984 }
3985
3986 $(this).append(
3987 $('<div>').addClass('image')
3988 .css('background-image', 'url(' + imgUrl + ')'),
3989 $('<div>').addClass('info').append(
3990 $('<h4>').addClass('title').html(resource.title),
3991 $('<p>').addClass('summary').html(resource.summary),
3992 $('<a>').attr('href', linkUrl).addClass('cta').html('Learn More')
3993 )
3994 );
3995
3996 return this;
3997 };
Dirk Doughertyc3921652014-05-13 16:55:26 -07003998})(jQuery);
Robert Lye7eeb402014-06-03 19:35:24 -07003999
4000
Dirk Doughertyc3921652014-05-13 16:55:26 -07004001/* Calculate the vertical area remaining */
4002(function($) {
4003 $.fn.ellipsisfade= function(lineHeight) {
4004 this.each(function() {
4005 // get element text
4006 var $this = $(this);
4007 var remainingHeight = $this.parent().parent().height();
4008 $this.parent().siblings().each(function ()
4009 {
4010 var h = $(this).height();
4011 remainingHeight = remainingHeight - h;
4012 });
4013
4014 adjustedRemainingHeight = ((remainingHeight)/lineHeight>>0)*lineHeight
4015 $this.parent().css({'height': adjustedRemainingHeight});
4016 $this.css({'height': "auto"});
4017 });
4018
4019 return this;
4020 };
4021}) (jQuery);
Robert Lye7eeb402014-06-03 19:35:24 -07004022
4023/*
4024 Fullscreen Carousel
4025
4026 The following allows for an area at the top of the page that takes over the
4027 entire browser height except for its top offset and an optional bottom
4028 padding specified as a data attribute.
4029
4030 HTML:
4031
4032 <div class="fullscreen-carousel">
4033 <div class="fullscreen-carousel-content">
4034 <!-- content here -->
4035 </div>
4036 <div class="fullscreen-carousel-content">
4037 <!-- content here -->
4038 </div>
4039
4040 etc ...
4041
4042 </div>
4043
4044 Control over how the carousel takes over the screen can mostly be defined in
4045 a css file. Setting min-height on the .fullscreen-carousel-content elements
4046 will prevent them from shrinking to far vertically when the browser is very
4047 short, and setting max-height on the .fullscreen-carousel itself will prevent
4048 the area from becoming to long in the case that the browser is stretched very
4049 tall.
4050
4051 There is limited functionality for having multiple sections since that request
4052 was removed, but it is possible to add .next-arrow and .prev-arrow elements to
4053 scroll between multiple content areas.
4054*/
4055
4056(function() {
4057 $(document).ready(function() {
4058 $('.fullscreen-carousel').each(function() {
4059 initWidget(this);
4060 });
4061 });
4062
4063 function initWidget(widget) {
4064 var $widget = $(widget);
4065
4066 var topOffset = $widget.offset().top;
4067 var padBottom = parseInt($widget.data('paddingbottom')) || 0;
4068 var maxHeight = 0;
4069 var minHeight = 0;
4070 var $content = $widget.find('.fullscreen-carousel-content');
4071 var $nextArrow = $widget.find('.next-arrow');
4072 var $prevArrow = $widget.find('.prev-arrow');
4073 var $curSection = $($content[0]);
4074
4075 if ($content.length <= 1) {
4076 $nextArrow.hide();
4077 $prevArrow.hide();
4078 } else {
4079 $nextArrow.click(function() {
4080 var index = ($content.index($curSection) + 1);
4081 $curSection.hide();
4082 $curSection = $($content[index >= $content.length ? 0 : index]);
4083 $curSection.show();
4084 });
4085
4086 $prevArrow.click(function() {
4087 var index = ($content.index($curSection) - 1);
4088 $curSection.hide();
4089 $curSection = $($content[index < 0 ? $content.length - 1 : 0]);
4090 $curSection.show();
4091 });
4092 }
4093
4094 // Just hide all content sections except first.
4095 $content.each(function(index) {
4096 if ($(this).height() > minHeight) minHeight = $(this).height();
4097 $(this).css({position: 'absolute', display: index > 0 ? 'none' : ''});
4098 });
4099
4100 // Register for changes to window size, and trigger.
4101 $(window).resize(resizeWidget);
4102 resizeWidget();
4103
4104 function resizeWidget() {
4105 var height = $(window).height() - topOffset - padBottom;
4106 $widget.width($(window).width());
4107 $widget.height(height < minHeight ? minHeight :
4108 (maxHeight && height > maxHeight ? maxHeight : height));
4109 }
4110 }
4111})();
4112
4113
4114
4115
4116
4117/*
4118 Tab Carousel
4119
4120 The following allows tab widgets to be installed via the html below. Each
4121 tab content section should have a data-tab attribute matching one of the
4122 nav items'. Also each tab content section should have a width matching the
4123 tab carousel.
4124
4125 HTML:
4126
4127 <div class="tab-carousel">
4128 <ul class="tab-nav">
4129 <li><a href="#" data-tab="handsets">Handsets</a>
4130 <li><a href="#" data-tab="wearable">Wearable</a>
4131 <li><a href="#" data-tab="tv">TV</a>
4132 </ul>
4133
4134 <div class="tab-carousel-content">
4135 <div data-tab="handsets">
4136 <!--Full width content here-->
4137 </div>
4138
4139 <div data-tab="wearable">
4140 <!--Full width content here-->
4141 </div>
4142
4143 <div data-tab="tv">
4144 <!--Full width content here-->
4145 </div>
4146 </div>
4147 </div>
4148
4149*/
4150(function() {
4151 $(document).ready(function() {
4152 $('.tab-carousel').each(function() {
4153 initWidget(this);
4154 });
4155 });
4156
4157 function initWidget(widget) {
4158 var $widget = $(widget);
4159 var $nav = $widget.find('.tab-nav');
4160 var $anchors = $nav.find('[data-tab]');
4161 var $li = $nav.find('li');
4162 var $contentContainer = $widget.find('.tab-carousel-content');
4163 var $tabs = $contentContainer.find('[data-tab]');
4164 var $curTab = $($tabs[0]); // Current tab is first tab.
4165 var width = $widget.width();
4166
4167 // Setup nav interactivity.
4168 $anchors.click(function(evt) {
4169 evt.preventDefault();
4170 var query = '[data-tab=' + $(this).data('tab') + ']';
4171 transitionWidget($tabs.filter(query));
4172 });
4173
4174 // Add highlight for navigation on first item.
4175 var $highlight = $('<div>').addClass('highlight')
4176 .css({left:$li.position().left + 'px', width:$li.outerWidth() + 'px'})
4177 .appendTo($nav);
4178
4179 // Store height since we will change contents to absolute.
4180 $contentContainer.height($contentContainer.height());
4181
4182 // Absolutely position tabs so they're ready for transition.
4183 $tabs.each(function(index) {
4184 $(this).css({position: 'absolute', left: index > 0 ? width + 'px' : '0'});
4185 });
4186
4187 function transitionWidget($toTab) {
4188 if (!$curTab.is($toTab)) {
4189 var curIndex = $tabs.index($curTab[0]);
4190 var toIndex = $tabs.index($toTab[0]);
4191 var dir = toIndex > curIndex ? 1 : -1;
4192
4193 // Animate content sections.
4194 $toTab.css({left:(width * dir) + 'px'});
4195 $curTab.animate({left:(width * -dir) + 'px'});
4196 $toTab.animate({left:'0'});
4197
4198 // Animate navigation highlight.
4199 $highlight.animate({left:$($li[toIndex]).position().left + 'px',
4200 width:$($li[toIndex]).outerWidth() + 'px'})
4201
4202 // Store new current section.
4203 $curTab = $toTab;
4204 }
4205 }
4206 }
4207})();