blob: 69090a3da9ee9653eb1414268f9a16990b749673 [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() {
smain@google.com698fff02014-11-20 20:39:33 -080023
Dirk Doughertyb87e3002014-11-18 19:34:34 -080024 // show lang dialog if the URL includes /intl/
25 //if (location.pathname.substring(0,6) == "/intl/") {
26 // var lang = location.pathname.split('/')[2];
27 // if (lang != getLangPref()) {
28 // $("#langMessage a.yes").attr("onclick","changeLangPref('" + lang
29 // + "', true); $('#langMessage').hide(); return false;");
30 // $("#langMessage .lang." + lang).show();
31 // $("#langMessage").show();
32 // }
33 //}
Scott Main7e447ed2013-02-19 17:22:37 -080034
Scott Main0e76e7e2013-03-12 10:24:07 -070035 // load json file for JD doc search suggestions
Scott Main719acb42013-12-05 16:05:09 -080036 $.getScript(toRoot + 'jd_lists_unified.js');
Scott Main7e447ed2013-02-19 17:22:37 -080037 // load json file for Android API search suggestions
38 $.getScript(toRoot + 'reference/lists.js');
39 // load json files for Google services API suggestions
Scott Main9f2971d2013-02-26 13:07:41 -080040 $.getScript(toRoot + 'reference/gcm_lists.js', function(data, textStatus, jqxhr) {
Scott Main7e447ed2013-02-19 17:22:37 -080041 // once the GCM json (GCM_DATA) is loaded, load the GMS json (GMS_DATA) and merge the data
42 if(jqxhr.status === 200) {
Scott Main9f2971d2013-02-26 13:07:41 -080043 $.getScript(toRoot + 'reference/gms_lists.js', function(data, textStatus, jqxhr) {
Scott Main7e447ed2013-02-19 17:22:37 -080044 if(jqxhr.status === 200) {
45 // combine GCM and GMS data
46 GOOGLE_DATA = GMS_DATA;
47 var start = GOOGLE_DATA.length;
48 for (var i=0; i<GCM_DATA.length; i++) {
49 GOOGLE_DATA.push({id:start+i, label:GCM_DATA[i].label,
50 link:GCM_DATA[i].link, type:GCM_DATA[i].type});
51 }
52 }
53 });
54 }
55 });
56
Scott Main0e76e7e2013-03-12 10:24:07 -070057 // setup keyboard listener for search shortcut
58 $('body').keyup(function(event) {
59 if (event.which == 191) {
60 $('#search_autocomplete').focus();
61 }
62 });
Scott Main015d6162013-01-29 09:01:52 -080063
Scott Maine4d8f1b2012-06-21 18:03:05 -070064 // init the fullscreen toggle click event
65 $('#nav-swap .fullscreen').click(function(){
66 if ($(this).hasClass('disabled')) {
67 toggleFullscreen(true);
68 } else {
69 toggleFullscreen(false);
70 }
71 });
Scott Main3b90aff2013-08-01 18:09:35 -070072
Scott Maine4d8f1b2012-06-21 18:03:05 -070073 // initialize the divs with custom scrollbars
74 $('.scroll-pane').jScrollPane( {verticalGutter:0} );
Scott Main3b90aff2013-08-01 18:09:35 -070075
Scott Maine4d8f1b2012-06-21 18:03:05 -070076 // add HRs below all H2s (except for a few other h2 variants)
Scott Mainc29b3f52014-05-30 21:18:30 -070077 $('h2').not('#qv h2')
78 .not('#tb h2')
79 .not('.sidebox h2')
80 .not('#devdoc-nav h2')
81 .not('h2.norule').css({marginBottom:0})
82 .after('<hr/>');
Scott Maine4d8f1b2012-06-21 18:03:05 -070083
84 // set up the search close button
85 $('.search .close').click(function() {
86 $searchInput = $('#search_autocomplete');
87 $searchInput.attr('value', '');
88 $(this).addClass("hide");
89 $("#search-container").removeClass('active');
90 $("#search_autocomplete").blur();
Scott Main0e76e7e2013-03-12 10:24:07 -070091 search_focus_changed($searchInput.get(), false);
92 hideResults();
Scott Maine4d8f1b2012-06-21 18:03:05 -070093 });
94
95 // Set up quicknav
Scott Main3b90aff2013-08-01 18:09:35 -070096 var quicknav_open = false;
Scott Maine4d8f1b2012-06-21 18:03:05 -070097 $("#btn-quicknav").click(function() {
98 if (quicknav_open) {
99 $(this).removeClass('active');
100 quicknav_open = false;
101 collapse();
102 } else {
103 $(this).addClass('active');
104 quicknav_open = true;
105 expand();
106 }
107 })
Scott Main3b90aff2013-08-01 18:09:35 -0700108
Scott Maine4d8f1b2012-06-21 18:03:05 -0700109 var expand = function() {
110 $('#header-wrap').addClass('quicknav');
111 $('#quicknav').stop().show().animate({opacity:'1'});
112 }
Scott Main3b90aff2013-08-01 18:09:35 -0700113
Scott Maine4d8f1b2012-06-21 18:03:05 -0700114 var collapse = function() {
115 $('#quicknav').stop().animate({opacity:'0'}, 100, function() {
116 $(this).hide();
117 $('#header-wrap').removeClass('quicknav');
118 });
119 }
Scott Main3b90aff2013-08-01 18:09:35 -0700120
121
Scott Maine4d8f1b2012-06-21 18:03:05 -0700122 //Set up search
123 $("#search_autocomplete").focus(function() {
124 $("#search-container").addClass('active');
125 })
126 $("#search-container").mouseover(function() {
127 $("#search-container").addClass('active');
128 $("#search_autocomplete").focus();
129 })
130 $("#search-container").mouseout(function() {
131 if ($("#search_autocomplete").is(":focus")) return;
132 if ($("#search_autocomplete").val() == '') {
133 setTimeout(function(){
134 $("#search-container").removeClass('active');
135 $("#search_autocomplete").blur();
136 },250);
137 }
138 })
139 $("#search_autocomplete").blur(function() {
140 if ($("#search_autocomplete").val() == '') {
141 $("#search-container").removeClass('active');
142 }
143 })
144
Scott Main3b90aff2013-08-01 18:09:35 -0700145
Scott Maine4d8f1b2012-06-21 18:03:05 -0700146 // prep nav expandos
147 var pagePath = document.location.pathname;
148 // account for intl docs by removing the intl/*/ path
149 if (pagePath.indexOf("/intl/") == 0) {
150 pagePath = pagePath.substr(pagePath.indexOf("/",6)); // start after intl/ to get last /
151 }
Scott Mainac2aef52013-02-12 14:15:23 -0800152
Scott Maine4d8f1b2012-06-21 18:03:05 -0700153 if (pagePath.indexOf(SITE_ROOT) == 0) {
154 if (pagePath == '' || pagePath.charAt(pagePath.length - 1) == '/') {
155 pagePath += 'index.html';
156 }
157 }
158
Scott Main01a25452013-02-12 17:32:27 -0800159 // Need a copy of the pagePath before it gets changed in the next block;
160 // it's needed to perform proper tab highlighting in offline docs (see rootDir below)
161 var pagePathOriginal = pagePath;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700162 if (SITE_ROOT.match(/\.\.\//) || SITE_ROOT == '') {
163 // If running locally, SITE_ROOT will be a relative path, so account for that by
164 // finding the relative URL to this page. This will allow us to find links on the page
165 // leading back to this page.
166 var pathParts = pagePath.split('/');
167 var relativePagePathParts = [];
168 var upDirs = (SITE_ROOT.match(/(\.\.\/)+/) || [''])[0].length / 3;
169 for (var i = 0; i < upDirs; i++) {
170 relativePagePathParts.push('..');
171 }
172 for (var i = 0; i < upDirs; i++) {
173 relativePagePathParts.push(pathParts[pathParts.length - (upDirs - i) - 1]);
174 }
175 relativePagePathParts.push(pathParts[pathParts.length - 1]);
176 pagePath = relativePagePathParts.join('/');
177 } else {
178 // Otherwise the page path is already an absolute URL
179 }
180
Scott Mainac2aef52013-02-12 14:15:23 -0800181 // Highlight the header tabs...
182 // highlight Design tab
183 if ($("body").hasClass("design")) {
184 $("#header li.design a").addClass("selected");
Dirk Doughertyc3921652014-05-13 16:55:26 -0700185 $("#sticky-header").addClass("design");
Scott Mainac2aef52013-02-12 14:15:23 -0800186
smain@google.com6040ffa2014-06-13 15:06:23 -0700187 // highlight About tabs
188 } else if ($("body").hasClass("about")) {
189 var rootDir = pagePathOriginal.substring(1,pagePathOriginal.indexOf('/', 1));
190 if (rootDir == "about") {
191 $("#nav-x li.about a").addClass("selected");
192 } else if (rootDir == "wear") {
193 $("#nav-x li.wear a").addClass("selected");
194 } else if (rootDir == "tv") {
195 $("#nav-x li.tv a").addClass("selected");
196 } else if (rootDir == "auto") {
197 $("#nav-x li.auto a").addClass("selected");
198 }
Scott Mainac2aef52013-02-12 14:15:23 -0800199 // highlight Develop tab
200 } else if ($("body").hasClass("develop") || $("body").hasClass("google")) {
201 $("#header li.develop a").addClass("selected");
Dirk Doughertyc3921652014-05-13 16:55:26 -0700202 $("#sticky-header").addClass("develop");
Scott Mainac2aef52013-02-12 14:15:23 -0800203 // In Develop docs, also highlight appropriate sub-tab
Scott Main01a25452013-02-12 17:32:27 -0800204 var rootDir = pagePathOriginal.substring(1,pagePathOriginal.indexOf('/', 1));
Scott Mainac2aef52013-02-12 14:15:23 -0800205 if (rootDir == "training") {
206 $("#nav-x li.training a").addClass("selected");
207 } else if (rootDir == "guide") {
208 $("#nav-x li.guide a").addClass("selected");
209 } else if (rootDir == "reference") {
210 // If the root is reference, but page is also part of Google Services, select Google
211 if ($("body").hasClass("google")) {
212 $("#nav-x li.google a").addClass("selected");
213 } else {
214 $("#nav-x li.reference a").addClass("selected");
215 }
216 } else if ((rootDir == "tools") || (rootDir == "sdk")) {
217 $("#nav-x li.tools a").addClass("selected");
218 } else if ($("body").hasClass("google")) {
219 $("#nav-x li.google a").addClass("selected");
Dirk Dougherty4f7e5152010-09-16 10:43:40 -0700220 } else if ($("body").hasClass("samples")) {
221 $("#nav-x li.samples a").addClass("selected");
Scott Mainac2aef52013-02-12 14:15:23 -0800222 }
223
224 // highlight Distribute tab
225 } else if ($("body").hasClass("distribute")) {
226 $("#header li.distribute a").addClass("selected");
Dirk Doughertyc3921652014-05-13 16:55:26 -0700227 $("#sticky-header").addClass("distribute");
228
229 var baseFrag = pagePathOriginal.indexOf('/', 1) + 1;
230 var secondFrag = pagePathOriginal.substring(baseFrag, pagePathOriginal.indexOf('/', baseFrag));
231 if (secondFrag == "users") {
232 $("#nav-x li.users a").addClass("selected");
233 } else if (secondFrag == "engage") {
234 $("#nav-x li.engage a").addClass("selected");
235 } else if (secondFrag == "monetize") {
236 $("#nav-x li.monetize a").addClass("selected");
237 } else if (secondFrag == "tools") {
238 $("#nav-x li.disttools a").addClass("selected");
239 } else if (secondFrag == "stories") {
240 $("#nav-x li.stories a").addClass("selected");
241 } else if (secondFrag == "essentials") {
242 $("#nav-x li.essentials a").addClass("selected");
243 } else if (secondFrag == "googleplay") {
244 $("#nav-x li.googleplay a").addClass("selected");
245 }
246 } else if ($("body").hasClass("about")) {
247 $("#sticky-header").addClass("about");
Scott Mainb16376f2014-05-21 20:35:47 -0700248 }
Scott Mainac2aef52013-02-12 14:15:23 -0800249
Scott Mainf6145542013-04-01 16:38:11 -0700250 // set global variable so we can highlight the sidenav a bit later (such as for google reference)
251 // and highlight the sidenav
252 mPagePath = pagePath;
253 highlightSidenav();
Dirk Doughertyc3921652014-05-13 16:55:26 -0700254 buildBreadcrumbs();
Scott Mainac2aef52013-02-12 14:15:23 -0800255
Scott Mainf6145542013-04-01 16:38:11 -0700256 // set up prev/next links if they exist
Scott Maine4d8f1b2012-06-21 18:03:05 -0700257 var $selNavLink = $('#nav').find('a[href="' + pagePath + '"]');
Scott Main5a1123e2012-09-26 12:51:28 -0700258 var $selListItem;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700259 if ($selNavLink.length) {
Scott Mainac2aef52013-02-12 14:15:23 -0800260 $selListItem = $selNavLink.closest('li');
Scott Maine4d8f1b2012-06-21 18:03:05 -0700261
262 // set up prev links
263 var $prevLink = [];
264 var $prevListItem = $selListItem.prev('li');
Scott Main3b90aff2013-08-01 18:09:35 -0700265
Scott Maine4d8f1b2012-06-21 18:03:05 -0700266 var crossBoundaries = ($("body.design").length > 0) || ($("body.guide").length > 0) ? true :
267false; // navigate across topic boundaries only in design docs
268 if ($prevListItem.length) {
smain@google.comc91ecb72014-06-23 10:22:23 -0700269 if ($prevListItem.hasClass('nav-section') || crossBoundaries) {
Scott Main5a1123e2012-09-26 12:51:28 -0700270 // jump to last topic of previous section
271 $prevLink = $prevListItem.find('a:last');
272 } else if (!$selListItem.hasClass('nav-section')) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700273 // jump to previous topic in this section
274 $prevLink = $prevListItem.find('a:eq(0)');
275 }
276 } else {
277 // jump to this section's index page (if it exists)
278 var $parentListItem = $selListItem.parents('li');
279 $prevLink = $selListItem.parents('li').find('a');
Scott Main3b90aff2013-08-01 18:09:35 -0700280
Scott Maine4d8f1b2012-06-21 18:03:05 -0700281 // except if cross boundaries aren't allowed, and we're at the top of a section already
282 // (and there's another parent)
Scott Main3b90aff2013-08-01 18:09:35 -0700283 if (!crossBoundaries && $parentListItem.hasClass('nav-section')
Scott Maine4d8f1b2012-06-21 18:03:05 -0700284 && $selListItem.hasClass('nav-section')) {
285 $prevLink = [];
286 }
287 }
288
Scott Maine4d8f1b2012-06-21 18:03:05 -0700289 // set up next links
290 var $nextLink = [];
Scott Maine4d8f1b2012-06-21 18:03:05 -0700291 var startClass = false;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700292 var isCrossingBoundary = false;
Scott Main3b90aff2013-08-01 18:09:35 -0700293
Scott Main1a00f7f2013-10-29 11:11:19 -0700294 if ($selListItem.hasClass('nav-section') && $selListItem.children('div.empty').length == 0) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700295 // we're on an index page, jump to the first topic
Scott Mainb505ca62012-07-26 18:00:14 -0700296 $nextLink = $selListItem.find('ul:eq(0)').find('a:eq(0)');
Scott Maine4d8f1b2012-06-21 18:03:05 -0700297
298 // if there aren't any children, go to the next section (required for About pages)
299 if($nextLink.length == 0) {
300 $nextLink = $selListItem.next('li').find('a');
Scott Mainb505ca62012-07-26 18:00:14 -0700301 } else if ($('.topic-start-link').length) {
302 // as long as there's a child link and there is a "topic start link" (we're on a landing)
303 // then set the landing page "start link" text to be the first doc title
304 $('.topic-start-link').text($nextLink.text().toUpperCase());
Scott Maine4d8f1b2012-06-21 18:03:05 -0700305 }
Scott Main3b90aff2013-08-01 18:09:35 -0700306
Scott Main5a1123e2012-09-26 12:51:28 -0700307 // If the selected page has a description, then it's a class or article homepage
308 if ($selListItem.find('a[description]').length) {
309 // this means we're on a class landing page
Scott Maine4d8f1b2012-06-21 18:03:05 -0700310 startClass = true;
311 }
312 } else {
313 // jump to the next topic in this section (if it exists)
314 $nextLink = $selListItem.next('li').find('a:eq(0)');
Scott Main1a00f7f2013-10-29 11:11:19 -0700315 if ($nextLink.length == 0) {
Scott Main5a1123e2012-09-26 12:51:28 -0700316 isCrossingBoundary = true;
317 // no more topics in this section, jump to the first topic in the next section
smain@google.comabf34112014-06-23 11:39:02 -0700318 $nextLink = $selListItem.parents('li:eq(0)').next('li').find('a:eq(0)');
Scott Main5a1123e2012-09-26 12:51:28 -0700319 if (!$nextLink.length) { // Go up another layer to look for next page (lesson > class > course)
320 $nextLink = $selListItem.parents('li:eq(1)').next('li.nav-section').find('a:eq(0)');
Scott Main1a00f7f2013-10-29 11:11:19 -0700321 if ($nextLink.length == 0) {
322 // if that doesn't work, we're at the end of the list, so disable NEXT link
323 $('.next-page-link').attr('href','').addClass("disabled")
324 .click(function() { return false; });
smain@google.comc91ecb72014-06-23 10:22:23 -0700325 // and completely hide the one in the footer
326 $('.content-footer .next-page-link').hide();
Scott Main1a00f7f2013-10-29 11:11:19 -0700327 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700328 }
329 }
330 }
Scott Main5a1123e2012-09-26 12:51:28 -0700331
332 if (startClass) {
333 $('.start-class-link').attr('href', $nextLink.attr('href')).removeClass("hide");
334
Scott Main3b90aff2013-08-01 18:09:35 -0700335 // if there's no training bar (below the start button),
Scott Main5a1123e2012-09-26 12:51:28 -0700336 // then we need to add a bottom border to button
337 if (!$("#tb").length) {
338 $('.start-class-link').css({'border-bottom':'1px solid #DADADA'});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700339 }
Scott Main5a1123e2012-09-26 12:51:28 -0700340 } else if (isCrossingBoundary && !$('body.design').length) { // Design always crosses boundaries
341 $('.content-footer.next-class').show();
342 $('.next-page-link').attr('href','')
343 .removeClass("hide").addClass("disabled")
344 .click(function() { return false; });
smain@google.comc91ecb72014-06-23 10:22:23 -0700345 // and completely hide the one in the footer
346 $('.content-footer .next-page-link').hide();
Scott Main1a00f7f2013-10-29 11:11:19 -0700347 if ($nextLink.length) {
348 $('.next-class-link').attr('href',$nextLink.attr('href'))
smain@google.com5bc3a1a2014-06-17 20:02:53 -0700349 .removeClass("hide")
350 .append(": " + $nextLink.html());
Scott Main1a00f7f2013-10-29 11:11:19 -0700351 $('.next-class-link').find('.new').empty();
352 }
Scott Main5a1123e2012-09-26 12:51:28 -0700353 } else {
smain@google.com5bc3a1a2014-06-17 20:02:53 -0700354 $('.next-page-link').attr('href', $nextLink.attr('href'))
355 .removeClass("hide");
356 // for the footer link, also add the next page title
357 $('.content-footer .next-page-link').append(": " + $nextLink.html());
Scott Main5a1123e2012-09-26 12:51:28 -0700358 }
359
360 if (!startClass && $prevLink.length) {
361 var prevHref = $prevLink.attr('href');
362 if (prevHref == SITE_ROOT + 'index.html') {
363 // Don't show Previous when it leads to the homepage
364 } else {
365 $('.prev-page-link').attr('href', $prevLink.attr('href')).removeClass("hide");
366 }
Scott Main3b90aff2013-08-01 18:09:35 -0700367 }
Scott Main5a1123e2012-09-26 12:51:28 -0700368
Scott Maine4d8f1b2012-06-21 18:03:05 -0700369 }
Scott Main3b90aff2013-08-01 18:09:35 -0700370
371
372
Scott Main5a1123e2012-09-26 12:51:28 -0700373 // Set up the course landing pages for Training with class names and descriptions
374 if ($('body.trainingcourse').length) {
375 var $classLinks = $selListItem.find('ul li a').not('#nav .nav-section .nav-section ul a');
Scott Maine7d75352014-05-22 15:50:56 -0700376
377 // create an array for all the class descriptions
378 var $classDescriptions = new Array($classLinks.length);
379 var lang = getLangPref();
380 $classLinks.each(function(index) {
381 var langDescr = $(this).attr(lang + "-description");
382 if (typeof langDescr !== 'undefined' && langDescr !== false) {
383 // if there's a class description in the selected language, use that
384 $classDescriptions[index] = langDescr;
385 } else {
386 // otherwise, use the default english description
387 $classDescriptions[index] = $(this).attr("description");
388 }
389 });
Scott Main3b90aff2013-08-01 18:09:35 -0700390
Scott Main5a1123e2012-09-26 12:51:28 -0700391 var $olClasses = $('<ol class="class-list"></ol>');
392 var $liClass;
393 var $imgIcon;
394 var $h2Title;
395 var $pSummary;
396 var $olLessons;
397 var $liLesson;
398 $classLinks.each(function(index) {
399 $liClass = $('<li></li>');
400 $h2Title = $('<a class="title" href="'+$(this).attr('href')+'"><h2>' + $(this).html()+'</h2><span></span></a>');
Scott Maine7d75352014-05-22 15:50:56 -0700401 $pSummary = $('<p class="description">' + $classDescriptions[index] + '</p>');
Scott Main3b90aff2013-08-01 18:09:35 -0700402
Scott Main5a1123e2012-09-26 12:51:28 -0700403 $olLessons = $('<ol class="lesson-list"></ol>');
Scott Main3b90aff2013-08-01 18:09:35 -0700404
Scott Main5a1123e2012-09-26 12:51:28 -0700405 $lessons = $(this).closest('li').find('ul li a');
Scott Main3b90aff2013-08-01 18:09:35 -0700406
Scott Main5a1123e2012-09-26 12:51:28 -0700407 if ($lessons.length) {
Scott Main3b90aff2013-08-01 18:09:35 -0700408 $imgIcon = $('<img src="'+toRoot+'assets/images/resource-tutorial.png" '
409 + ' width="64" height="64" alt=""/>');
Scott Main5a1123e2012-09-26 12:51:28 -0700410 $lessons.each(function(index) {
411 $olLessons.append('<li><a href="'+$(this).attr('href')+'">' + $(this).html()+'</a></li>');
412 });
413 } else {
Scott Main3b90aff2013-08-01 18:09:35 -0700414 $imgIcon = $('<img src="'+toRoot+'assets/images/resource-article.png" '
415 + ' width="64" height="64" alt=""/>');
Scott Main5a1123e2012-09-26 12:51:28 -0700416 $pSummary.addClass('article');
417 }
418
419 $liClass.append($h2Title).append($imgIcon).append($pSummary).append($olLessons);
420 $olClasses.append($liClass);
421 });
422 $('.jd-descr').append($olClasses);
423 }
424
Scott Maine4d8f1b2012-06-21 18:03:05 -0700425 // Set up expand/collapse behavior
Scott Mainad08f072013-08-20 16:49:57 -0700426 initExpandableNavItems("#nav");
Scott Main3b90aff2013-08-01 18:09:35 -0700427
Scott Main3b90aff2013-08-01 18:09:35 -0700428
Scott Maine4d8f1b2012-06-21 18:03:05 -0700429 $(".scroll-pane").scroll(function(event) {
430 event.preventDefault();
431 return false;
432 });
433
434 /* Resize nav height when window height changes */
435 $(window).resize(function() {
436 if ($('#side-nav').length == 0) return;
437 var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
438 setNavBarLeftPos(); // do this even if sidenav isn't fixed because it could become fixed
439 // make sidenav behave when resizing the window and side-scolling is a concern
Scott Mainf5257812014-05-22 17:26:38 -0700440 if (sticky) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700441 if ((stylesheet.attr("disabled") == "disabled") || stylesheet.length == 0) {
442 updateSideNavPosition();
443 } else {
444 updateSidenavFullscreenWidth();
445 }
446 }
447 resizeNav();
448 });
449
450
Scott Maine4d8f1b2012-06-21 18:03:05 -0700451 var navBarLeftPos;
452 if ($('#devdoc-nav').length) {
453 setNavBarLeftPos();
454 }
455
456
Scott Maine4d8f1b2012-06-21 18:03:05 -0700457 // Set up play-on-hover <video> tags.
458 $('video.play-on-hover').bind('click', function(){
459 $(this).get(0).load(); // in case the video isn't seekable
460 $(this).get(0).play();
461 });
462
463 // Set up tooltips
464 var TOOLTIP_MARGIN = 10;
Scott Maindb3678b2012-10-23 14:13:41 -0700465 $('acronym,.tooltip-link').each(function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700466 var $target = $(this);
467 var $tooltip = $('<div>')
468 .addClass('tooltip-box')
Scott Maindb3678b2012-10-23 14:13:41 -0700469 .append($target.attr('title'))
Scott Maine4d8f1b2012-06-21 18:03:05 -0700470 .hide()
471 .appendTo('body');
472 $target.removeAttr('title');
473
474 $target.hover(function() {
475 // in
476 var targetRect = $target.offset();
477 targetRect.width = $target.width();
478 targetRect.height = $target.height();
479
480 $tooltip.css({
481 left: targetRect.left,
482 top: targetRect.top + targetRect.height + TOOLTIP_MARGIN
483 });
484 $tooltip.addClass('below');
485 $tooltip.show();
486 }, function() {
487 // out
488 $tooltip.hide();
489 });
490 });
491
492 // Set up <h2> deeplinks
493 $('h2').click(function() {
494 var id = $(this).attr('id');
495 if (id) {
496 document.location.hash = id;
497 }
498 });
499
500 //Loads the +1 button
501 var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
502 po.src = 'https://apis.google.com/js/plusone.js';
503 var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
504
505
Scott Main3b90aff2013-08-01 18:09:35 -0700506 // Revise the sidenav widths to make room for the scrollbar
Scott Maine4d8f1b2012-06-21 18:03:05 -0700507 // which avoids the visible width from changing each time the bar appears
508 var $sidenav = $("#side-nav");
509 var sidenav_width = parseInt($sidenav.innerWidth());
Scott Main3b90aff2013-08-01 18:09:35 -0700510
Scott Maine4d8f1b2012-06-21 18:03:05 -0700511 $("#devdoc-nav #nav").css("width", sidenav_width - 4 + "px"); // 4px is scrollbar width
512
513
514 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
Scott Main3b90aff2013-08-01 18:09:35 -0700515
Scott Maine4d8f1b2012-06-21 18:03:05 -0700516 if ($(".scroll-pane").length > 1) {
517 // Check if there's a user preference for the panel heights
518 var cookieHeight = readCookie("reference_height");
519 if (cookieHeight) {
520 restoreHeight(cookieHeight);
521 }
522 }
Scott Main3b90aff2013-08-01 18:09:35 -0700523
Scott Main06f3f2c2014-05-30 11:23:00 -0700524 // Resize once loading is finished
Scott Maine4d8f1b2012-06-21 18:03:05 -0700525 resizeNav();
Scott Main06f3f2c2014-05-30 11:23:00 -0700526 // Check if there's an anchor that we need to scroll into view.
527 // A delay is needed, because some browsers do not immediately scroll down to the anchor
528 window.setTimeout(offsetScrollForSticky, 100);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700529
Scott Main015d6162013-01-29 09:01:52 -0800530 /* init the language selector based on user cookie for lang */
531 loadLangPref();
532 changeNavLang(getLangPref());
533
534 /* setup event handlers to ensure the overflow menu is visible while picking lang */
535 $("#language select")
536 .mousedown(function() {
537 $("div.morehover").addClass("hover"); })
538 .blur(function() {
539 $("div.morehover").removeClass("hover"); });
540
541 /* some global variable setup */
542 resizePackagesNav = $("#resize-packages-nav");
543 classesNav = $("#classes-nav");
544 devdocNav = $("#devdoc-nav");
545
546 var cookiePath = "";
547 if (location.href.indexOf("/reference/") != -1) {
548 cookiePath = "reference_";
549 } else if (location.href.indexOf("/guide/") != -1) {
550 cookiePath = "guide_";
551 } else if (location.href.indexOf("/tools/") != -1) {
552 cookiePath = "tools_";
553 } else if (location.href.indexOf("/training/") != -1) {
554 cookiePath = "training_";
555 } else if (location.href.indexOf("/design/") != -1) {
556 cookiePath = "design_";
557 } else if (location.href.indexOf("/distribute/") != -1) {
558 cookiePath = "distribute_";
559 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700560
smain@google.com698fff02014-11-20 20:39:33 -0800561
562 /* setup shadowbox for any videos that want it */
smain@google.comf75ee212014-11-24 09:42:59 -0800563 var $videoLinks = $("a.video-shadowbox-button, a.notice-developers-video");
smain@google.com698fff02014-11-20 20:39:33 -0800564 if ($videoLinks.length) {
565 // if there's at least one, add the shadowbox HTML to the body
566 $('body').prepend(
567'<div id="video-container">'+
568 '<div id="video-frame">'+
569 '<div class="video-close">'+
570 '<span id="icon-video-close" onclick="closeVideo()">&nbsp;</span>'+
571 '</div>'+
572 '<div id="youTubePlayer"></div>'+
573 '</div>'+
574'</div>');
575
576 // loads the IFrame Player API code asynchronously.
577 $.getScript("https://www.youtube.com/iframe_api");
578
579 $videoLinks.each(function() {
580 var videoId = $(this).attr('href').split('?v=')[1];
581 $(this).click(function(event) {
582 event.preventDefault();
583 startYouTubePlayer(videoId);
584 });
585 });
smain@google.com698fff02014-11-20 20:39:33 -0800586 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700587});
Scott Main7e447ed2013-02-19 17:22:37 -0800588// END of the onload event
Scott Maine4d8f1b2012-06-21 18:03:05 -0700589
590
smain@google.com698fff02014-11-20 20:39:33 -0800591var youTubePlayer;
592function onYouTubeIframeAPIReady() {
593}
594
smain@google.com3de83c12014-12-12 19:06:52 -0800595/* Returns the height the shadowbox video should be. It's based on the current
596 height of the "video-frame" element, which is 100% height for the window.
597 Then minus the margin so the video isn't actually the full window height. */
598function getVideoHeight() {
599 var frameHeight = $("#video-frame").height();
600 var marginTop = $("#video-frame").css('margin-top').split('px')[0];
601 return frameHeight - (marginTop * 2);
602}
603
smain@google.com698fff02014-11-20 20:39:33 -0800604function startYouTubePlayer(videoId) {
smain@google.com3de83c12014-12-12 19:06:52 -0800605 $("#video-container").show();
606 $("#video-frame").show();
607
608 // compute the size of the player so it's centered in window
609 var maxWidth = 940; // the width of the web site content
610 var videoAspect = .5625; // based on 1280x720 resolution
611 var maxHeight = maxWidth * videoAspect;
612 var videoHeight = getVideoHeight();
613 var videoWidth = videoHeight / videoAspect;
614 if (videoWidth > maxWidth) {
615 videoWidth = maxWidth;
616 videoHeight = maxHeight;
smain@google.com570c2212014-11-25 19:01:40 -0800617 }
smain@google.com3de83c12014-12-12 19:06:52 -0800618 $("#video-frame").css('width', videoWidth);
619
620 // check if we've already created this player
smain@google.com698fff02014-11-20 20:39:33 -0800621 if (youTubePlayer == null) {
smain@google.com3de83c12014-12-12 19:06:52 -0800622 // check if there's a start time specified
623 var idAndHash = videoId.split("#");
624 var startTime = 0;
625 if (idAndHash.length > 1) {
626 startTime = idAndHash[1].split("t=")[1] != undefined ? idAndHash[1].split("t=")[1] : 0;
627 }
628 // enable localized player
629 var lang = getLangPref();
630 var captionsOn = lang == 'en' ? 0 : 1;
631
smain@google.com698fff02014-11-20 20:39:33 -0800632 youTubePlayer = new YT.Player('youTubePlayer', {
smain@google.com3de83c12014-12-12 19:06:52 -0800633 height: videoHeight,
634 width: videoWidth,
smain@google.com570c2212014-11-25 19:01:40 -0800635 videoId: idAndHash[0],
smain@google.com481f15c2014-12-12 11:57:27 -0800636 playerVars: {start: startTime, hl: lang, cc_load_policy: captionsOn},
smain@google.com698fff02014-11-20 20:39:33 -0800637 events: {
smain@google.comf75ee212014-11-24 09:42:59 -0800638 'onReady': onPlayerReady,
639 'onStateChange': onPlayerStateChange
smain@google.com698fff02014-11-20 20:39:33 -0800640 }
641 });
642 } else {
smain@google.com3de83c12014-12-12 19:06:52 -0800643 // reset the size in case the user adjusted the window since last play
644 youTubePlayer.setSize(videoWidth, videoHeight);
smain@google.com698fff02014-11-20 20:39:33 -0800645 youTubePlayer.playVideo();
646 }
smain@google.com698fff02014-11-20 20:39:33 -0800647}
648
649function onPlayerReady(event) {
650 event.target.playVideo();
smain@google.comd24088c2014-12-12 11:31:13 -0800651 // track the start playing event so we know from which page the video was selected
652 ga('send', 'event', 'Videos', 'Start: ' +
653 youTubePlayer.getVideoUrl().split('?v=')[1], 'on: ' + document.location.href);
smain@google.com698fff02014-11-20 20:39:33 -0800654}
655
656function closeVideo() {
657 try {
smain@google.comf75ee212014-11-24 09:42:59 -0800658 youTubePlayer.pauseVideo();
smain@google.com698fff02014-11-20 20:39:33 -0800659 } catch(e) {
660 console.log('Video not available');
smain@google.com698fff02014-11-20 20:39:33 -0800661 }
smain@google.com3de83c12014-12-12 19:06:52 -0800662 $("#video-container").fadeOut(200);
smain@google.com698fff02014-11-20 20:39:33 -0800663}
664
smain@google.comf75ee212014-11-24 09:42:59 -0800665/* Track youtube playback for analytics */
666function onPlayerStateChange(event) {
667 // Video starts, send the video ID
668 if (event.data == YT.PlayerState.PLAYING) {
smain@google.comd24088c2014-12-12 11:31:13 -0800669 ga('send', 'event', 'Videos', 'Play',
670 youTubePlayer.getVideoUrl().split('?v=')[1]);
smain@google.comf75ee212014-11-24 09:42:59 -0800671 }
672 // Video paused, send video ID and video elapsed time
673 if (event.data == YT.PlayerState.PAUSED) {
smain@google.comd24088c2014-12-12 11:31:13 -0800674 ga('send', 'event', 'Videos', 'Paused',
675 youTubePlayer.getVideoUrl().split('?v=')[1], youTubePlayer.getCurrentTime());
smain@google.comf75ee212014-11-24 09:42:59 -0800676 }
677 // Video finished, send video ID and video elapsed time
678 if (event.data == YT.PlayerState.ENDED) {
smain@google.comd24088c2014-12-12 11:31:13 -0800679 ga('send', 'event', 'Videos', 'Finished',
680 youTubePlayer.getVideoUrl().split('?v=')[1], youTubePlayer.getCurrentTime());
smain@google.comf75ee212014-11-24 09:42:59 -0800681 }
682}
683
smain@google.com698fff02014-11-20 20:39:33 -0800684
685
Scott Mainad08f072013-08-20 16:49:57 -0700686function initExpandableNavItems(rootTag) {
687 $(rootTag + ' li.nav-section .nav-section-header').click(function() {
688 var section = $(this).closest('li.nav-section');
689 if (section.hasClass('expanded')) {
Scott Mainf0093852013-08-22 11:37:11 -0700690 /* hide me and descendants */
691 section.find('ul').slideUp(250, function() {
692 // remove 'expanded' class from my section and any children
Scott Mainad08f072013-08-20 16:49:57 -0700693 section.closest('li').removeClass('expanded');
Scott Mainf0093852013-08-22 11:37:11 -0700694 $('li.nav-section', section).removeClass('expanded');
Scott Mainad08f072013-08-20 16:49:57 -0700695 resizeNav();
696 });
697 } else {
698 /* show me */
699 // first hide all other siblings
Scott Main70557ee2013-10-30 14:47:40 -0700700 var $others = $('li.nav-section.expanded', $(this).closest('ul')).not('.sticky');
Scott Mainad08f072013-08-20 16:49:57 -0700701 $others.removeClass('expanded').children('ul').slideUp(250);
702
703 // now expand me
704 section.closest('li').addClass('expanded');
705 section.children('ul').slideDown(250, function() {
706 resizeNav();
707 });
708 }
709 });
Scott Mainf0093852013-08-22 11:37:11 -0700710
711 // Stop expand/collapse behavior when clicking on nav section links
712 // (since we're navigating away from the page)
713 // This selector captures the first instance of <a>, but not those with "#" as the href.
714 $('.nav-section-header').find('a:eq(0)').not('a[href="#"]').click(function(evt) {
715 window.location.href = $(this).attr('href');
716 return false;
717 });
Scott Mainad08f072013-08-20 16:49:57 -0700718}
719
Dirk Doughertyc3921652014-05-13 16:55:26 -0700720
721/** Create the list of breadcrumb links in the sticky header */
722function buildBreadcrumbs() {
723 var $breadcrumbUl = $("#sticky-header ul.breadcrumb");
724 // Add the secondary horizontal nav item, if provided
725 var $selectedSecondNav = $("div#nav-x ul.nav-x a.selected").clone().removeClass("selected");
726 if ($selectedSecondNav.length) {
727 $breadcrumbUl.prepend($("<li>").append($selectedSecondNav))
728 }
729 // Add the primary horizontal nav
730 var $selectedFirstNav = $("div#header-wrap ul.nav-x a.selected").clone().removeClass("selected");
731 // If there's no header nav item, use the logo link and title from alt text
732 if ($selectedFirstNav.length < 1) {
733 $selectedFirstNav = $("<a>")
734 .attr('href', $("div#header .logo a").attr('href'))
735 .text($("div#header .logo img").attr('alt'));
736 }
737 $breadcrumbUl.prepend($("<li>").append($selectedFirstNav));
738}
739
740
741
Scott Maine624b3f2013-09-12 12:56:41 -0700742/** Highlight the current page in sidenav, expanding children as appropriate */
Scott Mainf6145542013-04-01 16:38:11 -0700743function highlightSidenav() {
Scott Maine624b3f2013-09-12 12:56:41 -0700744 // if something is already highlighted, undo it. This is for dynamic navigation (Samples index)
745 if ($("ul#nav li.selected").length) {
746 unHighlightSidenav();
747 }
748 // look for URL in sidenav, including the hash
749 var $selNavLink = $('#nav').find('a[href="' + mPagePath + location.hash + '"]');
750
751 // If the selNavLink is still empty, look for it without the hash
752 if ($selNavLink.length == 0) {
753 $selNavLink = $('#nav').find('a[href="' + mPagePath + '"]');
754 }
755
Scott Mainf6145542013-04-01 16:38:11 -0700756 var $selListItem;
757 if ($selNavLink.length) {
Scott Mainf6145542013-04-01 16:38:11 -0700758 // Find this page's <li> in sidenav and set selected
759 $selListItem = $selNavLink.closest('li');
760 $selListItem.addClass('selected');
Scott Main3b90aff2013-08-01 18:09:35 -0700761
Scott Mainf6145542013-04-01 16:38:11 -0700762 // Traverse up the tree and expand all parent nav-sections
763 $selNavLink.parents('li.nav-section').each(function() {
764 $(this).addClass('expanded');
765 $(this).children('ul').show();
766 });
767 }
768}
769
Scott Maine624b3f2013-09-12 12:56:41 -0700770function unHighlightSidenav() {
771 $("ul#nav li.selected").removeClass("selected");
772 $('ul#nav li.nav-section.expanded').removeClass('expanded').children('ul').hide();
773}
Scott Maine4d8f1b2012-06-21 18:03:05 -0700774
775function toggleFullscreen(enable) {
776 var delay = 20;
777 var enabled = true;
778 var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
779 if (enable) {
780 // Currently NOT USING fullscreen; enable fullscreen
781 stylesheet.removeAttr('disabled');
782 $('#nav-swap .fullscreen').removeClass('disabled');
783 $('#devdoc-nav').css({left:''});
784 setTimeout(updateSidenavFullscreenWidth,delay); // need to wait a moment for css to switch
785 enabled = true;
786 } else {
787 // Currently USING fullscreen; disable fullscreen
788 stylesheet.attr('disabled', 'disabled');
789 $('#nav-swap .fullscreen').addClass('disabled');
790 setTimeout(updateSidenavFixedWidth,delay); // need to wait a moment for css to switch
791 enabled = false;
792 }
smain@google.com6bdcb982014-11-14 11:53:07 -0800793 writeCookie("fullscreen", enabled, null);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700794 setNavBarLeftPos();
795 resizeNav(delay);
796 updateSideNavPosition();
797 setTimeout(initSidenavHeightResize,delay);
798}
799
800
801function setNavBarLeftPos() {
802 navBarLeftPos = $('#body-content').offset().left;
803}
804
805
806function updateSideNavPosition() {
807 var newLeft = $(window).scrollLeft() - navBarLeftPos;
808 $('#devdoc-nav').css({left: -newLeft});
809 $('#devdoc-nav .totop').css({left: -(newLeft - parseInt($('#side-nav').css('margin-left')))});
810}
Scott Main3b90aff2013-08-01 18:09:35 -0700811
Scott Maine4d8f1b2012-06-21 18:03:05 -0700812// TODO: use $(document).ready instead
813function addLoadEvent(newfun) {
814 var current = window.onload;
815 if (typeof window.onload != 'function') {
816 window.onload = newfun;
817 } else {
818 window.onload = function() {
819 current();
820 newfun();
821 }
822 }
823}
824
825var agent = navigator['userAgent'].toLowerCase();
826// If a mobile phone, set flag and do mobile setup
827if ((agent.indexOf("mobile") != -1) || // android, iphone, ipod
828 (agent.indexOf("blackberry") != -1) ||
829 (agent.indexOf("webos") != -1) ||
830 (agent.indexOf("mini") != -1)) { // opera mini browsers
831 isMobile = true;
832}
833
834
Scott Main498d7102013-08-21 15:47:38 -0700835$(document).ready(function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700836 $("pre:not(.no-pretty-print)").addClass("prettyprint");
837 prettyPrint();
Scott Main498d7102013-08-21 15:47:38 -0700838});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700839
Scott Maine4d8f1b2012-06-21 18:03:05 -0700840
841
842
843/* ######### RESIZE THE SIDENAV HEIGHT ########## */
844
845function resizeNav(delay) {
846 var $nav = $("#devdoc-nav");
847 var $window = $(window);
848 var navHeight;
Scott Main3b90aff2013-08-01 18:09:35 -0700849
Scott Maine4d8f1b2012-06-21 18:03:05 -0700850 // Get the height of entire window and the total header height.
851 // Then figure out based on scroll position whether the header is visible
852 var windowHeight = $window.height();
853 var scrollTop = $window.scrollTop();
Dirk Doughertyc3921652014-05-13 16:55:26 -0700854 var headerHeight = $('#header-wrapper').outerHeight();
855 var headerVisible = scrollTop < stickyTop;
Scott Main3b90aff2013-08-01 18:09:35 -0700856
857 // get the height of space between nav and top of window.
Scott Maine4d8f1b2012-06-21 18:03:05 -0700858 // Could be either margin or top position, depending on whether the nav is fixed.
Scott Main3b90aff2013-08-01 18:09:35 -0700859 var topMargin = (parseInt($nav.css('margin-top')) || parseInt($nav.css('top'))) + 1;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700860 // add 1 for the #side-nav bottom margin
Scott Main3b90aff2013-08-01 18:09:35 -0700861
Scott Maine4d8f1b2012-06-21 18:03:05 -0700862 // Depending on whether the header is visible, set the side nav's height.
863 if (headerVisible) {
864 // The sidenav height grows as the header goes off screen
Dirk Doughertyc3921652014-05-13 16:55:26 -0700865 navHeight = windowHeight - (headerHeight - scrollTop) - topMargin;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700866 } else {
867 // Once header is off screen, the nav height is almost full window height
868 navHeight = windowHeight - topMargin;
869 }
Scott Main3b90aff2013-08-01 18:09:35 -0700870
871
872
Scott Maine4d8f1b2012-06-21 18:03:05 -0700873 $scrollPanes = $(".scroll-pane");
874 if ($scrollPanes.length > 1) {
875 // subtract the height of the api level widget and nav swapper from the available nav height
876 navHeight -= ($('#api-nav-header').outerHeight(true) + $('#nav-swap').outerHeight(true));
Scott Main3b90aff2013-08-01 18:09:35 -0700877
Scott Maine4d8f1b2012-06-21 18:03:05 -0700878 $("#swapper").css({height:navHeight + "px"});
879 if ($("#nav-tree").is(":visible")) {
880 $("#nav-tree").css({height:navHeight});
881 }
Scott Main3b90aff2013-08-01 18:09:35 -0700882
883 var classesHeight = navHeight - parseInt($("#resize-packages-nav").css("height")) - 10 + "px";
Scott Maine4d8f1b2012-06-21 18:03:05 -0700884 //subtract 10px to account for drag bar
Scott Main3b90aff2013-08-01 18:09:35 -0700885
886 // if the window becomes small enough to make the class panel height 0,
Scott Maine4d8f1b2012-06-21 18:03:05 -0700887 // then the package panel should begin to shrink
888 if (parseInt(classesHeight) <= 0) {
889 $("#resize-packages-nav").css({height:navHeight - 10}); //subtract 10px for drag bar
890 $("#packages-nav").css({height:navHeight - 10});
891 }
Scott Main3b90aff2013-08-01 18:09:35 -0700892
Scott Maine4d8f1b2012-06-21 18:03:05 -0700893 $("#classes-nav").css({'height':classesHeight, 'margin-top':'10px'});
894 $("#classes-nav .jspContainer").css({height:classesHeight});
Scott Main3b90aff2013-08-01 18:09:35 -0700895
896
Scott Maine4d8f1b2012-06-21 18:03:05 -0700897 } else {
898 $nav.height(navHeight);
899 }
Scott Main3b90aff2013-08-01 18:09:35 -0700900
Scott Maine4d8f1b2012-06-21 18:03:05 -0700901 if (delay) {
902 updateFromResize = true;
903 delayedReInitScrollbars(delay);
904 } else {
905 reInitScrollbars();
906 }
Scott Main3b90aff2013-08-01 18:09:35 -0700907
Scott Maine4d8f1b2012-06-21 18:03:05 -0700908}
909
910var updateScrollbars = false;
911var updateFromResize = false;
912
913/* Re-initialize the scrollbars to account for changed nav size.
914 * This method postpones the actual update by a 1/4 second in order to optimize the
915 * scroll performance while the header is still visible, because re-initializing the
916 * scroll panes is an intensive process.
917 */
918function delayedReInitScrollbars(delay) {
919 // If we're scheduled for an update, but have received another resize request
920 // before the scheduled resize has occured, just ignore the new request
921 // (and wait for the scheduled one).
922 if (updateScrollbars && updateFromResize) {
923 updateFromResize = false;
924 return;
925 }
Scott Main3b90aff2013-08-01 18:09:35 -0700926
Scott Maine4d8f1b2012-06-21 18:03:05 -0700927 // We're scheduled for an update and the update request came from this method's setTimeout
928 if (updateScrollbars && !updateFromResize) {
929 reInitScrollbars();
930 updateScrollbars = false;
931 } else {
932 updateScrollbars = true;
933 updateFromResize = false;
934 setTimeout('delayedReInitScrollbars()',delay);
935 }
936}
937
938/* Re-initialize the scrollbars to account for changed nav size. */
939function reInitScrollbars() {
940 var pane = $(".scroll-pane").each(function(){
941 var api = $(this).data('jsp');
942 if (!api) { setTimeout(reInitScrollbars,300); return;}
943 api.reinitialise( {verticalGutter:0} );
Scott Main3b90aff2013-08-01 18:09:35 -0700944 });
Scott Maine4d8f1b2012-06-21 18:03:05 -0700945 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
946}
947
948
949/* Resize the height of the nav panels in the reference,
950 * and save the new size to a cookie */
951function saveNavPanels() {
952 var basePath = getBaseUri(location.pathname);
953 var section = basePath.substring(1,basePath.indexOf("/",1));
smain@google.com6bdcb982014-11-14 11:53:07 -0800954 writeCookie("height", resizePackagesNav.css("height"), section);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700955}
956
957
958
959function restoreHeight(packageHeight) {
960 $("#resize-packages-nav").height(packageHeight);
961 $("#packages-nav").height(packageHeight);
962 // var classesHeight = navHeight - packageHeight;
963 // $("#classes-nav").css({height:classesHeight});
964 // $("#classes-nav .jspContainer").css({height:classesHeight});
965}
966
967
968
969/* ######### END RESIZE THE SIDENAV HEIGHT ########## */
970
971
972
973
974
Scott Main3b90aff2013-08-01 18:09:35 -0700975/** Scroll the jScrollPane to make the currently selected item visible
Scott Maine4d8f1b2012-06-21 18:03:05 -0700976 This is called when the page finished loading. */
977function scrollIntoView(nav) {
978 var $nav = $("#"+nav);
979 var element = $nav.jScrollPane({/* ...settings... */});
980 var api = element.data('jsp');
981
982 if ($nav.is(':visible')) {
983 var $selected = $(".selected", $nav);
Scott Mainbc729572013-07-30 18:00:51 -0700984 if ($selected.length == 0) {
985 // If no selected item found, exit
986 return;
987 }
Scott Main52dd2062013-08-15 12:22:28 -0700988 // get the selected item's offset from its container nav by measuring the item's offset
989 // relative to the document then subtract the container nav's offset relative to the document
990 var selectedOffset = $selected.offset().top - $nav.offset().top;
991 if (selectedOffset > $nav.height() * .8) { // multiply nav height by .8 so we move up the item
992 // if it's more than 80% down the nav
993 // scroll the item up by an amount equal to 80% the container nav's height
994 api.scrollTo(0, selectedOffset - ($nav.height() * .8), false);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700995 }
996 }
997}
998
999
1000
1001
1002
1003
1004/* Show popup dialogs */
1005function showDialog(id) {
1006 $dialog = $("#"+id);
1007 $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>');
1008 $dialog.wrapInner('<div/>');
1009 $dialog.removeClass("hide");
1010}
1011
1012
1013
1014
1015
1016/* ######### COOKIES! ########## */
1017
1018function readCookie(cookie) {
1019 var myCookie = cookie_namespace+"_"+cookie+"=";
1020 if (document.cookie) {
1021 var index = document.cookie.indexOf(myCookie);
1022 if (index != -1) {
1023 var valStart = index + myCookie.length;
1024 var valEnd = document.cookie.indexOf(";", valStart);
1025 if (valEnd == -1) {
1026 valEnd = document.cookie.length;
1027 }
1028 var val = document.cookie.substring(valStart, valEnd);
1029 return val;
1030 }
1031 }
1032 return 0;
1033}
1034
smain@google.com6bdcb982014-11-14 11:53:07 -08001035function writeCookie(cookie, val, section) {
Scott Maine4d8f1b2012-06-21 18:03:05 -07001036 if (val==undefined) return;
1037 section = section == null ? "_" : "_"+section+"_";
smain@google.com6bdcb982014-11-14 11:53:07 -08001038 var age = 2*365*24*60*60; // set max-age to 2 years
Scott Main3b90aff2013-08-01 18:09:35 -07001039 var cookieValue = cookie_namespace + section + cookie + "=" + val
smain@google.com80e38f42014-11-03 10:47:12 -08001040 + "; max-age=" + age +"; path=/";
Scott Maine4d8f1b2012-06-21 18:03:05 -07001041 document.cookie = cookieValue;
1042}
1043
1044/* ######### END COOKIES! ########## */
1045
1046
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001047var sticky = false;
Dirk Doughertyc3921652014-05-13 16:55:26 -07001048var stickyTop;
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001049var prevScrollLeft = 0; // used to compare current position to previous position of horiz scroll
Dirk Doughertyc3921652014-05-13 16:55:26 -07001050/* Sets the vertical scoll position at which the sticky bar should appear.
1051 This method is called to reset the position when search results appear or hide */
1052function setStickyTop() {
1053 stickyTop = $('#header-wrapper').outerHeight() - $('#sticky-header').outerHeight();
1054}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001055
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001056/*
Scott Mainb16376f2014-05-21 20:35:47 -07001057 * Displays sticky nav bar on pages when dac header scrolls out of view
Dirk Doughertyc3921652014-05-13 16:55:26 -07001058 */
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001059$(window).scroll(function(event) {
1060
1061 setStickyTop();
1062 var hiding = false;
1063 var $stickyEl = $('#sticky-header');
1064 var $menuEl = $('.menu-container');
1065 // Exit if there's no sidenav
1066 if ($('#side-nav').length == 0) return;
1067 // Exit if the mouse target is a DIV, because that means the event is coming
1068 // from a scrollable div and so there's no need to make adjustments to our layout
1069 if ($(event.target).nodeName == "DIV") {
1070 return;
1071 }
1072
1073 var top = $(window).scrollTop();
1074 // we set the navbar fixed when the scroll position is beyond the height of the site header...
1075 var shouldBeSticky = top >= stickyTop;
1076 // ... except if the document content is shorter than the sidenav height.
1077 // (this is necessary to avoid crazy behavior on OSX Lion due to overscroll bouncing)
1078 if ($("#doc-col").height() < $("#side-nav").height()) {
1079 shouldBeSticky = false;
1080 }
Scott Mainf5257812014-05-22 17:26:38 -07001081 // Account for horizontal scroll
1082 var scrollLeft = $(window).scrollLeft();
1083 // When the sidenav is fixed and user scrolls horizontally, reposition the sidenav to match
1084 if (sticky && (scrollLeft != prevScrollLeft)) {
1085 updateSideNavPosition();
1086 prevScrollLeft = scrollLeft;
1087 }
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001088
1089 // Don't continue if the header is sufficently far away
1090 // (to avoid intensive resizing that slows scrolling)
1091 if (sticky == shouldBeSticky) {
1092 return;
1093 }
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001094
1095 // If sticky header visible and position is now near top, hide sticky
1096 if (sticky && !shouldBeSticky) {
1097 sticky = false;
1098 hiding = true;
1099 // make the sidenav static again
1100 $('#devdoc-nav')
1101 .removeClass('fixed')
1102 .css({'width':'auto','margin':''})
1103 .prependTo('#side-nav');
1104 // delay hide the sticky
1105 $menuEl.removeClass('sticky-menu');
1106 $stickyEl.fadeOut(250);
1107 hiding = false;
1108
1109 // update the sidenaav position for side scrolling
1110 updateSideNavPosition();
1111 } else if (!sticky && shouldBeSticky) {
1112 sticky = true;
1113 $stickyEl.fadeIn(10);
1114 $menuEl.addClass('sticky-menu');
1115
1116 // make the sidenav fixed
1117 var width = $('#devdoc-nav').width();
1118 $('#devdoc-nav')
1119 .addClass('fixed')
1120 .css({'width':width+'px'})
1121 .prependTo('#body-content');
1122
1123 // update the sidenaav position for side scrolling
1124 updateSideNavPosition();
1125
1126 } else if (hiding && top < 15) {
1127 $menuEl.removeClass('sticky-menu');
1128 $stickyEl.hide();
1129 hiding = false;
1130 }
1131 resizeNav(250); // pass true in order to delay the scrollbar re-initialization for performance
1132});
1133
1134/*
1135 * Manages secion card states and nav resize to conclude loading
1136 */
Dirk Doughertyc3921652014-05-13 16:55:26 -07001137(function() {
1138 $(document).ready(function() {
1139
Dirk Doughertyc3921652014-05-13 16:55:26 -07001140 // Stack hover states
1141 $('.section-card-menu').each(function(index, el) {
1142 var height = $(el).height();
1143 $(el).css({height:height+'px', position:'relative'});
1144 var $cardInfo = $(el).find('.card-info');
1145
1146 $cardInfo.css({position: 'absolute', bottom:'0px', left:'0px', right:'0px', overflow:'visible'});
1147 });
1148
Dirk Doughertyc3921652014-05-13 16:55:26 -07001149 });
1150
1151})();
1152
Scott Maine4d8f1b2012-06-21 18:03:05 -07001153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
Scott Maind7026f72013-06-17 15:08:49 -07001166/* MISC LIBRARY FUNCTIONS */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001167
1168
1169
1170
1171
1172function toggle(obj, slide) {
1173 var ul = $("ul:first", obj);
1174 var li = ul.parent();
1175 if (li.hasClass("closed")) {
1176 if (slide) {
1177 ul.slideDown("fast");
1178 } else {
1179 ul.show();
1180 }
1181 li.removeClass("closed");
1182 li.addClass("open");
1183 $(".toggle-img", li).attr("title", "hide pages");
1184 } else {
1185 ul.slideUp("fast");
1186 li.removeClass("open");
1187 li.addClass("closed");
1188 $(".toggle-img", li).attr("title", "show pages");
1189 }
1190}
1191
1192
Scott Maine4d8f1b2012-06-21 18:03:05 -07001193function buildToggleLists() {
1194 $(".toggle-list").each(
1195 function(i) {
1196 $("div:first", this).append("<a class='toggle-img' href='#' title='show pages' onClick='toggle(this.parentNode.parentNode, true); return false;'></a>");
1197 $(this).addClass("closed");
1198 });
1199}
1200
1201
1202
Scott Maind7026f72013-06-17 15:08:49 -07001203function hideNestedItems(list, toggle) {
1204 $list = $(list);
1205 // hide nested lists
1206 if($list.hasClass('showing')) {
1207 $("li ol", $list).hide('fast');
1208 $list.removeClass('showing');
1209 // show nested lists
1210 } else {
1211 $("li ol", $list).show('fast');
1212 $list.addClass('showing');
1213 }
1214 $(".more,.less",$(toggle)).toggle();
1215}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001216
1217
smain@google.com95948b82014-06-16 19:24:25 -07001218/* Call this to add listeners to a <select> element for Studio/Eclipse/Other docs */
1219function setupIdeDocToggle() {
1220 $( "select.ide" ).change(function() {
1221 var selected = $(this).find("option:selected").attr("value");
1222 $(".select-ide").hide();
1223 $(".select-ide."+selected).show();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001224
smain@google.com95948b82014-06-16 19:24:25 -07001225 $("select.ide").val(selected);
1226 });
1227}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252/* REFERENCE NAV SWAP */
1253
1254
1255function getNavPref() {
1256 var v = readCookie('reference_nav');
1257 if (v != NAV_PREF_TREE) {
1258 v = NAV_PREF_PANELS;
1259 }
1260 return v;
1261}
1262
1263function chooseDefaultNav() {
1264 nav_pref = getNavPref();
1265 if (nav_pref == NAV_PREF_TREE) {
1266 $("#nav-panels").toggle();
1267 $("#panel-link").toggle();
1268 $("#nav-tree").toggle();
1269 $("#tree-link").toggle();
1270 }
1271}
1272
1273function swapNav() {
1274 if (nav_pref == NAV_PREF_TREE) {
1275 nav_pref = NAV_PREF_PANELS;
1276 } else {
1277 nav_pref = NAV_PREF_TREE;
1278 init_default_navtree(toRoot);
1279 }
smain@google.com6bdcb982014-11-14 11:53:07 -08001280 writeCookie("nav", nav_pref, "reference");
Scott Maine4d8f1b2012-06-21 18:03:05 -07001281
1282 $("#nav-panels").toggle();
1283 $("#panel-link").toggle();
1284 $("#nav-tree").toggle();
1285 $("#tree-link").toggle();
Scott Main3b90aff2013-08-01 18:09:35 -07001286
Scott Maine4d8f1b2012-06-21 18:03:05 -07001287 resizeNav();
1288
1289 // Gross nasty hack to make tree view show up upon first swap by setting height manually
1290 $("#nav-tree .jspContainer:visible")
1291 .css({'height':$("#nav-tree .jspContainer .jspPane").height() +'px'});
1292 // Another nasty hack to make the scrollbar appear now that we have height
1293 resizeNav();
Scott Main3b90aff2013-08-01 18:09:35 -07001294
Scott Maine4d8f1b2012-06-21 18:03:05 -07001295 if ($("#nav-tree").is(':visible')) {
1296 scrollIntoView("nav-tree");
1297 } else {
1298 scrollIntoView("packages-nav");
1299 scrollIntoView("classes-nav");
1300 }
1301}
1302
1303
1304
Scott Mainf5089842012-08-14 16:31:07 -07001305/* ############################################ */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001306/* ########## LOCALIZATION ############ */
Scott Mainf5089842012-08-14 16:31:07 -07001307/* ############################################ */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001308
1309function getBaseUri(uri) {
1310 var intlUrl = (uri.substring(0,6) == "/intl/");
1311 if (intlUrl) {
1312 base = uri.substring(uri.indexOf('intl/')+5,uri.length);
1313 base = base.substring(base.indexOf('/')+1, base.length);
1314 //alert("intl, returning base url: /" + base);
1315 return ("/" + base);
1316 } else {
1317 //alert("not intl, returning uri as found.");
1318 return uri;
1319 }
1320}
1321
1322function requestAppendHL(uri) {
1323//append "?hl=<lang> to an outgoing request (such as to blog)
1324 var lang = getLangPref();
1325 if (lang) {
1326 var q = 'hl=' + lang;
1327 uri += '?' + q;
1328 window.location = uri;
1329 return false;
1330 } else {
1331 return true;
1332 }
1333}
1334
1335
Scott Maine4d8f1b2012-06-21 18:03:05 -07001336function changeNavLang(lang) {
Scott Main6eb95f12012-10-02 17:12:23 -07001337 var $links = $("#devdoc-nav,#header,#nav-x,.training-nav-top,.content-footer").find("a["+lang+"-lang]");
1338 $links.each(function(i){ // for each link with a translation
1339 var $link = $(this);
1340 if (lang != "en") { // No need to worry about English, because a language change invokes new request
1341 // put the desired language from the attribute as the text
1342 $link.text($link.attr(lang+"-lang"))
Scott Maine4d8f1b2012-06-21 18:03:05 -07001343 }
Scott Main6eb95f12012-10-02 17:12:23 -07001344 });
Scott Maine4d8f1b2012-06-21 18:03:05 -07001345}
1346
Scott Main015d6162013-01-29 09:01:52 -08001347function changeLangPref(lang, submit) {
smain@google.com6bdcb982014-11-14 11:53:07 -08001348 writeCookie("pref_lang", lang, null);
Scott Main015d6162013-01-29 09:01:52 -08001349
1350 // ####### TODO: Remove this condition once we're stable on devsite #######
1351 // This condition is only needed if we still need to support legacy GAE server
1352 if (devsite) {
1353 // Switch language when on Devsite server
1354 if (submit) {
1355 $("#setlang").submit();
1356 }
1357 } else {
1358 // Switch language when on legacy GAE server
Scott Main015d6162013-01-29 09:01:52 -08001359 if (submit) {
1360 window.location = getBaseUri(location.pathname);
1361 }
Scott Maine4d8f1b2012-06-21 18:03:05 -07001362 }
1363}
1364
1365function loadLangPref() {
1366 var lang = readCookie("pref_lang");
1367 if (lang != 0) {
1368 $("#language").find("option[value='"+lang+"']").attr("selected",true);
1369 }
1370}
1371
1372function getLangPref() {
1373 var lang = $("#language").find(":selected").attr("value");
1374 if (!lang) {
1375 lang = readCookie("pref_lang");
1376 }
1377 return (lang != 0) ? lang : 'en';
1378}
1379
1380/* ########## END LOCALIZATION ############ */
1381
1382
1383
1384
1385
1386
1387/* Used to hide and reveal supplemental content, such as long code samples.
1388 See the companion CSS in android-developer-docs.css */
1389function toggleContent(obj) {
Scott Maindc63dda2013-08-22 16:03:21 -07001390 var div = $(obj).closest(".toggle-content");
1391 var toggleMe = $(".toggle-content-toggleme:eq(0)",div);
Scott Maine4d8f1b2012-06-21 18:03:05 -07001392 if (div.hasClass("closed")) { // if it's closed, open it
1393 toggleMe.slideDown();
Scott Maindc63dda2013-08-22 16:03:21 -07001394 $(".toggle-content-text:eq(0)", obj).toggle();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001395 div.removeClass("closed").addClass("open");
Scott Maindc63dda2013-08-22 16:03:21 -07001396 $(".toggle-content-img:eq(0)", div).attr("title", "hide").attr("src", toRoot
Scott Maine4d8f1b2012-06-21 18:03:05 -07001397 + "assets/images/triangle-opened.png");
1398 } else { // if it's open, close it
1399 toggleMe.slideUp('fast', function() { // Wait until the animation is done before closing arrow
Scott Maindc63dda2013-08-22 16:03:21 -07001400 $(".toggle-content-text:eq(0)", obj).toggle();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001401 div.removeClass("open").addClass("closed");
Scott Maindc63dda2013-08-22 16:03:21 -07001402 div.find(".toggle-content").removeClass("open").addClass("closed")
1403 .find(".toggle-content-toggleme").hide();
Scott Main3b90aff2013-08-01 18:09:35 -07001404 $(".toggle-content-img", div).attr("title", "show").attr("src", toRoot
Scott Maine4d8f1b2012-06-21 18:03:05 -07001405 + "assets/images/triangle-closed.png");
1406 });
1407 }
1408 return false;
1409}
Scott Mainf5089842012-08-14 16:31:07 -07001410
1411
Scott Maindb3678b2012-10-23 14:13:41 -07001412/* New version of expandable content */
1413function toggleExpandable(link,id) {
1414 if($(id).is(':visible')) {
1415 $(id).slideUp();
1416 $(link).removeClass('expanded');
1417 } else {
1418 $(id).slideDown();
1419 $(link).addClass('expanded');
1420 }
1421}
1422
1423function hideExpandable(ids) {
1424 $(ids).slideUp();
Scott Main55d99832012-11-12 23:03:59 -08001425 $(ids).prev('h4').find('a.expandable').removeClass('expanded');
Scott Maindb3678b2012-10-23 14:13:41 -07001426}
1427
Scott Mainf5089842012-08-14 16:31:07 -07001428
1429
1430
1431
Scott Main3b90aff2013-08-01 18:09:35 -07001432/*
Scott Mainf5089842012-08-14 16:31:07 -07001433 * Slideshow 1.0
1434 * Used on /index.html and /develop/index.html for carousel
1435 *
1436 * Sample usage:
1437 * HTML -
1438 * <div class="slideshow-container">
1439 * <a href="" class="slideshow-prev">Prev</a>
1440 * <a href="" class="slideshow-next">Next</a>
1441 * <ul>
1442 * <li class="item"><img src="images/marquee1.jpg"></li>
1443 * <li class="item"><img src="images/marquee2.jpg"></li>
1444 * <li class="item"><img src="images/marquee3.jpg"></li>
1445 * <li class="item"><img src="images/marquee4.jpg"></li>
1446 * </ul>
1447 * </div>
1448 *
1449 * <script type="text/javascript">
1450 * $('.slideshow-container').dacSlideshow({
1451 * auto: true,
1452 * btnPrev: '.slideshow-prev',
1453 * btnNext: '.slideshow-next'
1454 * });
1455 * </script>
1456 *
1457 * Options:
1458 * btnPrev: optional identifier for previous button
1459 * btnNext: optional identifier for next button
Scott Maineb410352013-01-14 19:03:40 -08001460 * btnPause: optional identifier for pause button
Scott Mainf5089842012-08-14 16:31:07 -07001461 * auto: whether or not to auto-proceed
1462 * speed: animation speed
1463 * autoTime: time between auto-rotation
1464 * easing: easing function for transition
1465 * start: item to select by default
1466 * scroll: direction to scroll in
1467 * pagination: whether or not to include dotted pagination
1468 *
1469 */
1470
1471 (function($) {
1472 $.fn.dacSlideshow = function(o) {
Scott Main3b90aff2013-08-01 18:09:35 -07001473
Scott Mainf5089842012-08-14 16:31:07 -07001474 //Options - see above
1475 o = $.extend({
1476 btnPrev: null,
1477 btnNext: null,
Scott Maineb410352013-01-14 19:03:40 -08001478 btnPause: null,
Scott Mainf5089842012-08-14 16:31:07 -07001479 auto: true,
1480 speed: 500,
1481 autoTime: 12000,
1482 easing: null,
1483 start: 0,
1484 scroll: 1,
1485 pagination: true
1486
1487 }, o || {});
Scott Main3b90aff2013-08-01 18:09:35 -07001488
1489 //Set up a carousel for each
Scott Mainf5089842012-08-14 16:31:07 -07001490 return this.each(function() {
1491
1492 var running = false;
1493 var animCss = o.vertical ? "top" : "left";
1494 var sizeCss = o.vertical ? "height" : "width";
1495 var div = $(this);
1496 var ul = $("ul", div);
1497 var tLi = $("li", ul);
Scott Main3b90aff2013-08-01 18:09:35 -07001498 var tl = tLi.size();
Scott Mainf5089842012-08-14 16:31:07 -07001499 var timer = null;
1500
1501 var li = $("li", ul);
1502 var itemLength = li.size();
1503 var curr = o.start;
1504
1505 li.css({float: o.vertical ? "none" : "left"});
1506 ul.css({margin: "0", padding: "0", position: "relative", "list-style-type": "none", "z-index": "1"});
1507 div.css({position: "relative", "z-index": "2", left: "0px"});
1508
1509 var liSize = o.vertical ? height(li) : width(li);
1510 var ulSize = liSize * itemLength;
1511 var divSize = liSize;
1512
1513 li.css({width: li.width(), height: li.height()});
1514 ul.css(sizeCss, ulSize+"px").css(animCss, -(curr*liSize));
1515
1516 div.css(sizeCss, divSize+"px");
Scott Main3b90aff2013-08-01 18:09:35 -07001517
Scott Mainf5089842012-08-14 16:31:07 -07001518 //Pagination
1519 if (o.pagination) {
1520 var pagination = $("<div class='pagination'></div>");
1521 var pag_ul = $("<ul></ul>");
1522 if (tl > 1) {
1523 for (var i=0;i<tl;i++) {
1524 var li = $("<li>"+i+"</li>");
1525 pag_ul.append(li);
1526 if (i==o.start) li.addClass('active');
1527 li.click(function() {
1528 go(parseInt($(this).text()));
1529 })
1530 }
1531 pagination.append(pag_ul);
1532 div.append(pagination);
1533 }
1534 }
Scott Main3b90aff2013-08-01 18:09:35 -07001535
Scott Mainf5089842012-08-14 16:31:07 -07001536 //Previous button
1537 if(o.btnPrev)
1538 $(o.btnPrev).click(function(e) {
1539 e.preventDefault();
1540 return go(curr-o.scroll);
1541 });
1542
1543 //Next button
1544 if(o.btnNext)
1545 $(o.btnNext).click(function(e) {
1546 e.preventDefault();
1547 return go(curr+o.scroll);
1548 });
Scott Maineb410352013-01-14 19:03:40 -08001549
1550 //Pause button
1551 if(o.btnPause)
1552 $(o.btnPause).click(function(e) {
1553 e.preventDefault();
1554 if ($(this).hasClass('paused')) {
1555 startRotateTimer();
1556 } else {
1557 pauseRotateTimer();
1558 }
1559 });
Scott Main3b90aff2013-08-01 18:09:35 -07001560
Scott Mainf5089842012-08-14 16:31:07 -07001561 //Auto rotation
1562 if(o.auto) startRotateTimer();
Scott Main3b90aff2013-08-01 18:09:35 -07001563
Scott Mainf5089842012-08-14 16:31:07 -07001564 function startRotateTimer() {
1565 clearInterval(timer);
1566 timer = setInterval(function() {
1567 if (curr == tl-1) {
1568 go(0);
1569 } else {
Scott Main3b90aff2013-08-01 18:09:35 -07001570 go(curr+o.scroll);
1571 }
Scott Mainf5089842012-08-14 16:31:07 -07001572 }, o.autoTime);
Scott Maineb410352013-01-14 19:03:40 -08001573 $(o.btnPause).removeClass('paused');
1574 }
1575
1576 function pauseRotateTimer() {
1577 clearInterval(timer);
1578 $(o.btnPause).addClass('paused');
Scott Mainf5089842012-08-14 16:31:07 -07001579 }
1580
1581 //Go to an item
1582 function go(to) {
1583 if(!running) {
1584
1585 if(to<0) {
1586 to = itemLength-1;
1587 } else if (to>itemLength-1) {
1588 to = 0;
1589 }
1590 curr = to;
1591
1592 running = true;
1593
1594 ul.animate(
1595 animCss == "left" ? { left: -(curr*liSize) } : { top: -(curr*liSize) } , o.speed, o.easing,
1596 function() {
1597 running = false;
1598 }
1599 );
1600
1601 $(o.btnPrev + "," + o.btnNext).removeClass("disabled");
1602 $( (curr-o.scroll<0 && o.btnPrev)
1603 ||
1604 (curr+o.scroll > itemLength && o.btnNext)
1605 ||
1606 []
1607 ).addClass("disabled");
1608
Scott Main3b90aff2013-08-01 18:09:35 -07001609
Scott Mainf5089842012-08-14 16:31:07 -07001610 var nav_items = $('li', pagination);
1611 nav_items.removeClass('active');
1612 nav_items.eq(to).addClass('active');
Scott Main3b90aff2013-08-01 18:09:35 -07001613
Scott Mainf5089842012-08-14 16:31:07 -07001614
1615 }
1616 if(o.auto) startRotateTimer();
1617 return false;
1618 };
1619 });
1620 };
1621
1622 function css(el, prop) {
1623 return parseInt($.css(el[0], prop)) || 0;
1624 };
1625 function width(el) {
1626 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1627 };
1628 function height(el) {
1629 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1630 };
1631
1632 })(jQuery);
1633
1634
Scott Main3b90aff2013-08-01 18:09:35 -07001635/*
Scott Mainf5089842012-08-14 16:31:07 -07001636 * dacSlideshow 1.0
1637 * Used on develop/index.html for side-sliding tabs
1638 *
1639 * Sample usage:
1640 * HTML -
1641 * <div class="slideshow-container">
1642 * <a href="" class="slideshow-prev">Prev</a>
1643 * <a href="" class="slideshow-next">Next</a>
1644 * <ul>
1645 * <li class="item"><img src="images/marquee1.jpg"></li>
1646 * <li class="item"><img src="images/marquee2.jpg"></li>
1647 * <li class="item"><img src="images/marquee3.jpg"></li>
1648 * <li class="item"><img src="images/marquee4.jpg"></li>
1649 * </ul>
1650 * </div>
1651 *
1652 * <script type="text/javascript">
1653 * $('.slideshow-container').dacSlideshow({
1654 * auto: true,
1655 * btnPrev: '.slideshow-prev',
1656 * btnNext: '.slideshow-next'
1657 * });
1658 * </script>
1659 *
1660 * Options:
1661 * btnPrev: optional identifier for previous button
1662 * btnNext: optional identifier for next button
1663 * auto: whether or not to auto-proceed
1664 * speed: animation speed
1665 * autoTime: time between auto-rotation
1666 * easing: easing function for transition
1667 * start: item to select by default
1668 * scroll: direction to scroll in
1669 * pagination: whether or not to include dotted pagination
1670 *
1671 */
1672 (function($) {
1673 $.fn.dacTabbedList = function(o) {
Scott Main3b90aff2013-08-01 18:09:35 -07001674
Scott Mainf5089842012-08-14 16:31:07 -07001675 //Options - see above
1676 o = $.extend({
1677 speed : 250,
1678 easing: null,
1679 nav_id: null,
1680 frame_id: null
1681 }, o || {});
Scott Main3b90aff2013-08-01 18:09:35 -07001682
1683 //Set up a carousel for each
Scott Mainf5089842012-08-14 16:31:07 -07001684 return this.each(function() {
1685
1686 var curr = 0;
1687 var running = false;
1688 var animCss = "margin-left";
1689 var sizeCss = "width";
1690 var div = $(this);
Scott Main3b90aff2013-08-01 18:09:35 -07001691
Scott Mainf5089842012-08-14 16:31:07 -07001692 var nav = $(o.nav_id, div);
1693 var nav_li = $("li", nav);
Scott Main3b90aff2013-08-01 18:09:35 -07001694 var nav_size = nav_li.size();
Scott Mainf5089842012-08-14 16:31:07 -07001695 var frame = div.find(o.frame_id);
1696 var content_width = $(frame).find('ul').width();
1697 //Buttons
1698 $(nav_li).click(function(e) {
1699 go($(nav_li).index($(this)));
1700 })
Scott Main3b90aff2013-08-01 18:09:35 -07001701
Scott Mainf5089842012-08-14 16:31:07 -07001702 //Go to an item
1703 function go(to) {
1704 if(!running) {
1705 curr = to;
1706 running = true;
1707
1708 frame.animate({ 'margin-left' : -(curr*content_width) }, o.speed, o.easing,
1709 function() {
1710 running = false;
1711 }
1712 );
1713
Scott Main3b90aff2013-08-01 18:09:35 -07001714
Scott Mainf5089842012-08-14 16:31:07 -07001715 nav_li.removeClass('active');
1716 nav_li.eq(to).addClass('active');
Scott Main3b90aff2013-08-01 18:09:35 -07001717
Scott Mainf5089842012-08-14 16:31:07 -07001718
1719 }
1720 return false;
1721 };
1722 });
1723 };
1724
1725 function css(el, prop) {
1726 return parseInt($.css(el[0], prop)) || 0;
1727 };
1728 function width(el) {
1729 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1730 };
1731 function height(el) {
1732 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1733 };
1734
1735 })(jQuery);
1736
1737
1738
1739
1740
1741/* ######################################################## */
1742/* ################ SEARCH SUGGESTIONS ################## */
1743/* ######################################################## */
1744
1745
Scott Main7e447ed2013-02-19 17:22:37 -08001746
Scott Main0e76e7e2013-03-12 10:24:07 -07001747var gSelectedIndex = -1; // the index position of currently highlighted suggestion
1748var gSelectedColumn = -1; // which column of suggestion lists is currently focused
1749
Scott Mainf5089842012-08-14 16:31:07 -07001750var gMatches = new Array();
1751var gLastText = "";
Scott Mainf5089842012-08-14 16:31:07 -07001752var gInitialized = false;
Scott Main7e447ed2013-02-19 17:22:37 -08001753var ROW_COUNT_FRAMEWORK = 20; // max number of results in list
1754var gListLength = 0;
1755
1756
1757var gGoogleMatches = new Array();
1758var ROW_COUNT_GOOGLE = 15; // max number of results in list
1759var gGoogleListLength = 0;
Scott Mainf5089842012-08-14 16:31:07 -07001760
Scott Main0e76e7e2013-03-12 10:24:07 -07001761var gDocsMatches = new Array();
1762var ROW_COUNT_DOCS = 100; // max number of results in list
1763var gDocsListLength = 0;
1764
Scott Mainde295272013-03-25 15:48:35 -07001765function onSuggestionClick(link) {
1766 // When user clicks a suggested document, track it
smain@google.comed677d72014-12-12 10:19:17 -08001767 ga('send', 'event', 'Suggestion Click', 'clicked: ' + $(link).attr('href'),
1768 'query: ' + $("#search_autocomplete").val().toLowerCase());
Scott Mainde295272013-03-25 15:48:35 -07001769}
1770
Scott Mainf5089842012-08-14 16:31:07 -07001771function set_item_selected($li, selected)
1772{
1773 if (selected) {
1774 $li.attr('class','jd-autocomplete jd-selected');
1775 } else {
1776 $li.attr('class','jd-autocomplete');
1777 }
1778}
1779
1780function set_item_values(toroot, $li, match)
1781{
1782 var $link = $('a',$li);
1783 $link.html(match.__hilabel || match.label);
1784 $link.attr('href',toroot + match.link);
1785}
1786
Scott Main719acb42013-12-05 16:05:09 -08001787function set_item_values_jd(toroot, $li, match)
1788{
1789 var $link = $('a',$li);
1790 $link.html(match.title);
1791 $link.attr('href',toroot + match.url);
1792}
1793
Scott Main0e76e7e2013-03-12 10:24:07 -07001794function new_suggestion($list) {
Scott Main7e447ed2013-02-19 17:22:37 -08001795 var $li = $("<li class='jd-autocomplete'></li>");
1796 $list.append($li);
1797
1798 $li.mousedown(function() {
1799 window.location = this.firstChild.getAttribute("href");
1800 });
1801 $li.mouseover(function() {
Scott Main0e76e7e2013-03-12 10:24:07 -07001802 $('.search_filtered_wrapper li').removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001803 $(this).addClass('jd-selected');
Scott Main0e76e7e2013-03-12 10:24:07 -07001804 gSelectedColumn = $(".search_filtered:visible").index($(this).closest('.search_filtered'));
1805 gSelectedIndex = $("li", $(".search_filtered:visible")[gSelectedColumn]).index(this);
Scott Main7e447ed2013-02-19 17:22:37 -08001806 });
Scott Mainde295272013-03-25 15:48:35 -07001807 $li.append("<a onclick='onSuggestionClick(this)'></a>");
Scott Main7e447ed2013-02-19 17:22:37 -08001808 $li.attr('class','show-item');
1809 return $li;
1810}
1811
Scott Mainf5089842012-08-14 16:31:07 -07001812function sync_selection_table(toroot)
1813{
Scott Mainf5089842012-08-14 16:31:07 -07001814 var $li; //list item jquery object
1815 var i; //list item iterator
Scott Main7e447ed2013-02-19 17:22:37 -08001816
Scott Main0e76e7e2013-03-12 10:24:07 -07001817 // if there are NO results at all, hide all columns
1818 if (!(gMatches.length > 0) && !(gGoogleMatches.length > 0) && !(gDocsMatches.length > 0)) {
1819 $('.suggest-card').hide(300);
1820 return;
1821 }
1822
1823 // if there are api results
Scott Main7e447ed2013-02-19 17:22:37 -08001824 if ((gMatches.length > 0) || (gGoogleMatches.length > 0)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001825 // reveal suggestion list
1826 $('.suggest-card.dummy').show();
1827 $('.suggest-card.reference').show();
1828 var listIndex = 0; // list index position
Scott Main7e447ed2013-02-19 17:22:37 -08001829
Scott Main0e76e7e2013-03-12 10:24:07 -07001830 // reset the lists
1831 $(".search_filtered_wrapper.reference li").remove();
Scott Main7e447ed2013-02-19 17:22:37 -08001832
Scott Main0e76e7e2013-03-12 10:24:07 -07001833 // ########### ANDROID RESULTS #############
1834 if (gMatches.length > 0) {
Scott Main7e447ed2013-02-19 17:22:37 -08001835
Scott Main0e76e7e2013-03-12 10:24:07 -07001836 // determine android results to show
1837 gListLength = gMatches.length < ROW_COUNT_FRAMEWORK ?
1838 gMatches.length : ROW_COUNT_FRAMEWORK;
1839 for (i=0; i<gListLength; i++) {
1840 var $li = new_suggestion($(".suggest-card.reference ul"));
1841 set_item_values(toroot, $li, gMatches[i]);
1842 set_item_selected($li, i == gSelectedIndex);
1843 }
1844 }
Scott Main7e447ed2013-02-19 17:22:37 -08001845
Scott Main0e76e7e2013-03-12 10:24:07 -07001846 // ########### GOOGLE RESULTS #############
1847 if (gGoogleMatches.length > 0) {
1848 // show header for list
1849 $(".suggest-card.reference ul").append("<li class='header'>in Google Services:</li>");
Scott Main7e447ed2013-02-19 17:22:37 -08001850
Scott Main0e76e7e2013-03-12 10:24:07 -07001851 // determine google results to show
1852 gGoogleListLength = gGoogleMatches.length < ROW_COUNT_GOOGLE ? gGoogleMatches.length : ROW_COUNT_GOOGLE;
1853 for (i=0; i<gGoogleListLength; i++) {
1854 var $li = new_suggestion($(".suggest-card.reference ul"));
1855 set_item_values(toroot, $li, gGoogleMatches[i]);
1856 set_item_selected($li, i == gSelectedIndex);
1857 }
1858 }
Scott Mainf5089842012-08-14 16:31:07 -07001859 } else {
Scott Main0e76e7e2013-03-12 10:24:07 -07001860 $('.suggest-card.reference').hide();
1861 $('.suggest-card.dummy').hide();
1862 }
1863
1864 // ########### JD DOC RESULTS #############
1865 if (gDocsMatches.length > 0) {
1866 // reset the lists
1867 $(".search_filtered_wrapper.docs li").remove();
1868
1869 // determine google results to show
Scott Main719acb42013-12-05 16:05:09 -08001870 // NOTE: The order of the conditions below for the sugg.type MUST BE SPECIFIC:
1871 // The order must match the reverse order that each section appears as a card in
1872 // the suggestion UI... this may be only for the "develop" grouped items though.
Scott Main0e76e7e2013-03-12 10:24:07 -07001873 gDocsListLength = gDocsMatches.length < ROW_COUNT_DOCS ? gDocsMatches.length : ROW_COUNT_DOCS;
1874 for (i=0; i<gDocsListLength; i++) {
1875 var sugg = gDocsMatches[i];
1876 var $li;
1877 if (sugg.type == "design") {
1878 $li = new_suggestion($(".suggest-card.design ul"));
1879 } else
1880 if (sugg.type == "distribute") {
1881 $li = new_suggestion($(".suggest-card.distribute ul"));
1882 } else
Scott Main719acb42013-12-05 16:05:09 -08001883 if (sugg.type == "samples") {
1884 $li = new_suggestion($(".suggest-card.develop .child-card.samples"));
1885 } else
Scott Main0e76e7e2013-03-12 10:24:07 -07001886 if (sugg.type == "training") {
1887 $li = new_suggestion($(".suggest-card.develop .child-card.training"));
1888 } else
Scott Main719acb42013-12-05 16:05:09 -08001889 if (sugg.type == "about"||"guide"||"tools"||"google") {
Scott Main0e76e7e2013-03-12 10:24:07 -07001890 $li = new_suggestion($(".suggest-card.develop .child-card.guides"));
1891 } else {
1892 continue;
1893 }
1894
Scott Main719acb42013-12-05 16:05:09 -08001895 set_item_values_jd(toroot, $li, sugg);
Scott Main0e76e7e2013-03-12 10:24:07 -07001896 set_item_selected($li, i == gSelectedIndex);
1897 }
1898
1899 // add heading and show or hide card
1900 if ($(".suggest-card.design li").length > 0) {
1901 $(".suggest-card.design ul").prepend("<li class='header'>Design:</li>");
1902 $(".suggest-card.design").show(300);
1903 } else {
1904 $('.suggest-card.design').hide(300);
1905 }
1906 if ($(".suggest-card.distribute li").length > 0) {
1907 $(".suggest-card.distribute ul").prepend("<li class='header'>Distribute:</li>");
1908 $(".suggest-card.distribute").show(300);
1909 } else {
1910 $('.suggest-card.distribute').hide(300);
1911 }
1912 if ($(".child-card.guides li").length > 0) {
1913 $(".child-card.guides").prepend("<li class='header'>Guides:</li>");
1914 $(".child-card.guides li").appendTo(".suggest-card.develop ul");
1915 }
1916 if ($(".child-card.training li").length > 0) {
1917 $(".child-card.training").prepend("<li class='header'>Training:</li>");
1918 $(".child-card.training li").appendTo(".suggest-card.develop ul");
1919 }
Scott Main719acb42013-12-05 16:05:09 -08001920 if ($(".child-card.samples li").length > 0) {
1921 $(".child-card.samples").prepend("<li class='header'>Samples:</li>");
1922 $(".child-card.samples li").appendTo(".suggest-card.develop ul");
1923 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001924
1925 if ($(".suggest-card.develop li").length > 0) {
1926 $(".suggest-card.develop").show(300);
1927 } else {
1928 $('.suggest-card.develop').hide(300);
1929 }
1930
1931 } else {
1932 $('.search_filtered_wrapper.docs .suggest-card:not(.dummy)').hide(300);
Scott Mainf5089842012-08-14 16:31:07 -07001933 }
1934}
1935
Scott Main0e76e7e2013-03-12 10:24:07 -07001936/** Called by the search input's onkeydown and onkeyup events.
1937 * Handles navigation with keyboard arrows, Enter key to invoke search,
1938 * otherwise invokes search suggestions on key-up event.
1939 * @param e The JS event
1940 * @param kd True if the event is key-down
Scott Main3b90aff2013-08-01 18:09:35 -07001941 * @param toroot A string for the site's root path
Scott Main0e76e7e2013-03-12 10:24:07 -07001942 * @returns True if the event should bubble up
1943 */
Scott Mainf5089842012-08-14 16:31:07 -07001944function search_changed(e, kd, toroot)
1945{
Scott Main719acb42013-12-05 16:05:09 -08001946 var currentLang = getLangPref();
Scott Mainf5089842012-08-14 16:31:07 -07001947 var search = document.getElementById("search_autocomplete");
1948 var text = search.value.replace(/(^ +)|( +$)/g, '');
Scott Main0e76e7e2013-03-12 10:24:07 -07001949 // get the ul hosting the currently selected item
1950 gSelectedColumn = gSelectedColumn >= 0 ? gSelectedColumn : 0;
1951 var $columns = $(".search_filtered_wrapper").find(".search_filtered:visible");
1952 var $selectedUl = $columns[gSelectedColumn];
1953
Scott Mainf5089842012-08-14 16:31:07 -07001954 // show/hide the close button
1955 if (text != '') {
1956 $(".search .close").removeClass("hide");
1957 } else {
1958 $(".search .close").addClass("hide");
1959 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001960 // 27 = esc
1961 if (e.keyCode == 27) {
1962 // close all search results
1963 if (kd) $('.search .close').trigger('click');
1964 return true;
1965 }
Scott Mainf5089842012-08-14 16:31:07 -07001966 // 13 = enter
Scott Main0e76e7e2013-03-12 10:24:07 -07001967 else if (e.keyCode == 13) {
1968 if (gSelectedIndex < 0) {
1969 $('.suggest-card').hide();
Scott Main7e447ed2013-02-19 17:22:37 -08001970 if ($("#searchResults").is(":hidden") && (search.value != "")) {
1971 // if results aren't showing (and text not empty), return true to allow search to execute
Dirk Doughertyc3921652014-05-13 16:55:26 -07001972 $('body,html').animate({scrollTop:0}, '500', 'swing');
Scott Mainf5089842012-08-14 16:31:07 -07001973 return true;
1974 } else {
1975 // otherwise, results are already showing, so allow ajax to auto refresh the results
1976 // and ignore this Enter press to avoid the reload.
1977 return false;
1978 }
1979 } else if (kd && gSelectedIndex >= 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001980 // click the link corresponding to selected item
1981 $("a",$("li",$selectedUl)[gSelectedIndex]).get()[0].click();
Scott Mainf5089842012-08-14 16:31:07 -07001982 return false;
1983 }
1984 }
Scott Mainb16376f2014-05-21 20:35:47 -07001985 // If Google results are showing, return true to allow ajax search to execute
Scott Main0e76e7e2013-03-12 10:24:07 -07001986 else if ($("#searchResults").is(":visible")) {
Scott Mainb16376f2014-05-21 20:35:47 -07001987 // Also, if search_results is scrolled out of view, scroll to top to make results visible
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001988 if ((sticky ) && (search.value != "")) {
1989 $('body,html').animate({scrollTop:0}, '500', 'swing');
1990 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001991 return true;
1992 }
1993 // 38 UP ARROW
Scott Mainf5089842012-08-14 16:31:07 -07001994 else if (kd && (e.keyCode == 38)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001995 // if the next item is a header, skip it
1996 if ($($("li", $selectedUl)[gSelectedIndex-1]).hasClass("header")) {
Scott Mainf5089842012-08-14 16:31:07 -07001997 gSelectedIndex--;
Scott Main7e447ed2013-02-19 17:22:37 -08001998 }
1999 if (gSelectedIndex >= 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002000 $('li', $selectedUl).removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08002001 gSelectedIndex--;
Scott Main0e76e7e2013-03-12 10:24:07 -07002002 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
2003 // If user reaches top, reset selected column
2004 if (gSelectedIndex < 0) {
2005 gSelectedColumn = -1;
2006 }
Scott Mainf5089842012-08-14 16:31:07 -07002007 }
2008 return false;
2009 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002010 // 40 DOWN ARROW
Scott Mainf5089842012-08-14 16:31:07 -07002011 else if (kd && (e.keyCode == 40)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002012 // if the next item is a header, skip it
2013 if ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header")) {
Scott Mainf5089842012-08-14 16:31:07 -07002014 gSelectedIndex++;
Scott Main7e447ed2013-02-19 17:22:37 -08002015 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002016 if ((gSelectedIndex < $("li", $selectedUl).length-1) ||
2017 ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header"))) {
2018 $('li', $selectedUl).removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08002019 gSelectedIndex++;
Scott Main0e76e7e2013-03-12 10:24:07 -07002020 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
Scott Mainf5089842012-08-14 16:31:07 -07002021 }
2022 return false;
2023 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002024 // Consider left/right arrow navigation
2025 // NOTE: Order of suggest columns are reverse order (index position 0 is on right)
2026 else if (kd && $columns.length > 1 && gSelectedColumn >= 0) {
2027 // 37 LEFT ARROW
2028 // go left only if current column is not left-most column (last column)
2029 if (e.keyCode == 37 && gSelectedColumn < $columns.length - 1) {
2030 $('li', $selectedUl).removeClass('jd-selected');
2031 gSelectedColumn++;
2032 $selectedUl = $columns[gSelectedColumn];
2033 // keep or reset the selected item to last item as appropriate
2034 gSelectedIndex = gSelectedIndex >
2035 $("li", $selectedUl).length-1 ?
2036 $("li", $selectedUl).length-1 : gSelectedIndex;
2037 // if the corresponding item is a header, move down
2038 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
2039 gSelectedIndex++;
2040 }
Scott Main3b90aff2013-08-01 18:09:35 -07002041 // set item selected
Scott Main0e76e7e2013-03-12 10:24:07 -07002042 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
2043 return false;
2044 }
2045 // 39 RIGHT ARROW
2046 // go right only if current column is not the right-most column (first column)
2047 else if (e.keyCode == 39 && gSelectedColumn > 0) {
2048 $('li', $selectedUl).removeClass('jd-selected');
2049 gSelectedColumn--;
2050 $selectedUl = $columns[gSelectedColumn];
2051 // keep or reset the selected item to last item as appropriate
2052 gSelectedIndex = gSelectedIndex >
2053 $("li", $selectedUl).length-1 ?
2054 $("li", $selectedUl).length-1 : gSelectedIndex;
2055 // if the corresponding item is a header, move down
2056 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
2057 gSelectedIndex++;
2058 }
Scott Main3b90aff2013-08-01 18:09:35 -07002059 // set item selected
Scott Main0e76e7e2013-03-12 10:24:07 -07002060 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
2061 return false;
2062 }
2063 }
2064
Scott Main719acb42013-12-05 16:05:09 -08002065 // if key-up event and not arrow down/up/left/right,
2066 // read the search query and add suggestions to gMatches
Scott Main0e76e7e2013-03-12 10:24:07 -07002067 else if (!kd && (e.keyCode != 40)
2068 && (e.keyCode != 38)
2069 && (e.keyCode != 37)
2070 && (e.keyCode != 39)) {
2071 gSelectedIndex = -1;
Scott Mainf5089842012-08-14 16:31:07 -07002072 gMatches = new Array();
2073 matchedCount = 0;
Scott Main7e447ed2013-02-19 17:22:37 -08002074 gGoogleMatches = new Array();
2075 matchedCountGoogle = 0;
Scott Main0e76e7e2013-03-12 10:24:07 -07002076 gDocsMatches = new Array();
2077 matchedCountDocs = 0;
Scott Main7e447ed2013-02-19 17:22:37 -08002078
2079 // Search for Android matches
Scott Mainf5089842012-08-14 16:31:07 -07002080 for (var i=0; i<DATA.length; i++) {
2081 var s = DATA[i];
2082 if (text.length != 0 &&
2083 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
2084 gMatches[matchedCount] = s;
2085 matchedCount++;
2086 }
2087 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002088 rank_autocomplete_api_results(text, gMatches);
Scott Mainf5089842012-08-14 16:31:07 -07002089 for (var i=0; i<gMatches.length; i++) {
2090 var s = gMatches[i];
Scott Main7e447ed2013-02-19 17:22:37 -08002091 }
2092
2093
2094 // Search for Google matches
2095 for (var i=0; i<GOOGLE_DATA.length; i++) {
2096 var s = GOOGLE_DATA[i];
2097 if (text.length != 0 &&
2098 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
2099 gGoogleMatches[matchedCountGoogle] = s;
2100 matchedCountGoogle++;
Scott Mainf5089842012-08-14 16:31:07 -07002101 }
2102 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002103 rank_autocomplete_api_results(text, gGoogleMatches);
Scott Main7e447ed2013-02-19 17:22:37 -08002104 for (var i=0; i<gGoogleMatches.length; i++) {
2105 var s = gGoogleMatches[i];
2106 }
2107
Scott Mainf5089842012-08-14 16:31:07 -07002108 highlight_autocomplete_result_labels(text);
Scott Main0e76e7e2013-03-12 10:24:07 -07002109
2110
2111
Scott Main719acb42013-12-05 16:05:09 -08002112 // Search for matching JD docs
Dirk Dougherty9b7f8f22014-11-01 17:08:56 -07002113 if (text.length >= 2) {
Scott Main719acb42013-12-05 16:05:09 -08002114 // Regex to match only the beginning of a word
2115 var textRegex = new RegExp("\\b" + text.toLowerCase(), "g");
2116
2117
2118 // Search for Training classes
2119 for (var i=0; i<TRAINING_RESOURCES.length; i++) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002120 // current search comparison, with counters for tag and title,
2121 // used later to improve ranking
Scott Main719acb42013-12-05 16:05:09 -08002122 var s = TRAINING_RESOURCES[i];
Scott Main0e76e7e2013-03-12 10:24:07 -07002123 s.matched_tag = 0;
2124 s.matched_title = 0;
2125 var matched = false;
2126
2127 // Check if query matches any tags; work backwards toward 1 to assist ranking
Scott Main719acb42013-12-05 16:05:09 -08002128 for (var j = s.keywords.length - 1; j >= 0; j--) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002129 // it matches a tag
Scott Main719acb42013-12-05 16:05:09 -08002130 if (s.keywords[j].toLowerCase().match(textRegex)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002131 matched = true;
2132 s.matched_tag = j + 1; // add 1 to index position
2133 }
2134 }
Scott Main719acb42013-12-05 16:05:09 -08002135 // Don't consider doc title for lessons (only for class landing pages),
2136 // unless the lesson has a tag that already matches
2137 if ((s.lang == currentLang) &&
2138 (!(s.type == "training" && s.url.indexOf("index.html") == -1) || matched)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002139 // it matches the doc title
Scott Main719acb42013-12-05 16:05:09 -08002140 if (s.title.toLowerCase().match(textRegex)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002141 matched = true;
2142 s.matched_title = 1;
2143 }
2144 }
2145 if (matched) {
2146 gDocsMatches[matchedCountDocs] = s;
2147 matchedCountDocs++;
2148 }
2149 }
Scott Main719acb42013-12-05 16:05:09 -08002150
2151
2152 // Search for API Guides
2153 for (var i=0; i<GUIDE_RESOURCES.length; i++) {
2154 // current search comparison, with counters for tag and title,
2155 // used later to improve ranking
2156 var s = GUIDE_RESOURCES[i];
2157 s.matched_tag = 0;
2158 s.matched_title = 0;
2159 var matched = false;
2160
2161 // Check if query matches any tags; work backwards toward 1 to assist ranking
2162 for (var j = s.keywords.length - 1; j >= 0; j--) {
2163 // it matches a tag
2164 if (s.keywords[j].toLowerCase().match(textRegex)) {
2165 matched = true;
2166 s.matched_tag = j + 1; // add 1 to index position
2167 }
2168 }
2169 // Check if query matches the doc title, but only for current language
2170 if (s.lang == currentLang) {
2171 // if query matches the doc title
2172 if (s.title.toLowerCase().match(textRegex)) {
2173 matched = true;
2174 s.matched_title = 1;
2175 }
2176 }
2177 if (matched) {
2178 gDocsMatches[matchedCountDocs] = s;
2179 matchedCountDocs++;
2180 }
2181 }
2182
2183
2184 // Search for Tools Guides
2185 for (var i=0; i<TOOLS_RESOURCES.length; i++) {
2186 // current search comparison, with counters for tag and title,
2187 // used later to improve ranking
2188 var s = TOOLS_RESOURCES[i];
2189 s.matched_tag = 0;
2190 s.matched_title = 0;
2191 var matched = false;
2192
2193 // Check if query matches any tags; work backwards toward 1 to assist ranking
2194 for (var j = s.keywords.length - 1; j >= 0; j--) {
2195 // it matches a tag
2196 if (s.keywords[j].toLowerCase().match(textRegex)) {
2197 matched = true;
2198 s.matched_tag = j + 1; // add 1 to index position
2199 }
2200 }
2201 // Check if query matches the doc title, but only for current language
2202 if (s.lang == currentLang) {
2203 // if query matches the doc title
2204 if (s.title.toLowerCase().match(textRegex)) {
2205 matched = true;
2206 s.matched_title = 1;
2207 }
2208 }
2209 if (matched) {
2210 gDocsMatches[matchedCountDocs] = s;
2211 matchedCountDocs++;
2212 }
2213 }
2214
2215
2216 // Search for About docs
2217 for (var i=0; i<ABOUT_RESOURCES.length; i++) {
2218 // current search comparison, with counters for tag and title,
2219 // used later to improve ranking
2220 var s = ABOUT_RESOURCES[i];
2221 s.matched_tag = 0;
2222 s.matched_title = 0;
2223 var matched = false;
2224
2225 // Check if query matches any tags; work backwards toward 1 to assist ranking
2226 for (var j = s.keywords.length - 1; j >= 0; j--) {
2227 // it matches a tag
2228 if (s.keywords[j].toLowerCase().match(textRegex)) {
2229 matched = true;
2230 s.matched_tag = j + 1; // add 1 to index position
2231 }
2232 }
2233 // Check if query matches the doc title, but only for current language
2234 if (s.lang == currentLang) {
2235 // if query matches the doc title
2236 if (s.title.toLowerCase().match(textRegex)) {
2237 matched = true;
2238 s.matched_title = 1;
2239 }
2240 }
2241 if (matched) {
2242 gDocsMatches[matchedCountDocs] = s;
2243 matchedCountDocs++;
2244 }
2245 }
2246
2247
2248 // Search for Design guides
2249 for (var i=0; i<DESIGN_RESOURCES.length; i++) {
2250 // current search comparison, with counters for tag and title,
2251 // used later to improve ranking
2252 var s = DESIGN_RESOURCES[i];
2253 s.matched_tag = 0;
2254 s.matched_title = 0;
2255 var matched = false;
2256
2257 // Check if query matches any tags; work backwards toward 1 to assist ranking
2258 for (var j = s.keywords.length - 1; j >= 0; j--) {
2259 // it matches a tag
2260 if (s.keywords[j].toLowerCase().match(textRegex)) {
2261 matched = true;
2262 s.matched_tag = j + 1; // add 1 to index position
2263 }
2264 }
2265 // Check if query matches the doc title, but only for current language
2266 if (s.lang == currentLang) {
2267 // if query matches the doc title
2268 if (s.title.toLowerCase().match(textRegex)) {
2269 matched = true;
2270 s.matched_title = 1;
2271 }
2272 }
2273 if (matched) {
2274 gDocsMatches[matchedCountDocs] = s;
2275 matchedCountDocs++;
2276 }
2277 }
2278
2279
2280 // Search for Distribute guides
2281 for (var i=0; i<DISTRIBUTE_RESOURCES.length; i++) {
2282 // current search comparison, with counters for tag and title,
2283 // used later to improve ranking
2284 var s = DISTRIBUTE_RESOURCES[i];
2285 s.matched_tag = 0;
2286 s.matched_title = 0;
2287 var matched = false;
2288
2289 // Check if query matches any tags; work backwards toward 1 to assist ranking
2290 for (var j = s.keywords.length - 1; j >= 0; j--) {
2291 // it matches a tag
2292 if (s.keywords[j].toLowerCase().match(textRegex)) {
2293 matched = true;
2294 s.matched_tag = j + 1; // add 1 to index position
2295 }
2296 }
2297 // Check if query matches the doc title, but only for current language
2298 if (s.lang == currentLang) {
2299 // if query matches the doc title
2300 if (s.title.toLowerCase().match(textRegex)) {
2301 matched = true;
2302 s.matched_title = 1;
2303 }
2304 }
2305 if (matched) {
2306 gDocsMatches[matchedCountDocs] = s;
2307 matchedCountDocs++;
2308 }
2309 }
2310
2311
2312 // Search for Google guides
2313 for (var i=0; i<GOOGLE_RESOURCES.length; i++) {
2314 // current search comparison, with counters for tag and title,
2315 // used later to improve ranking
2316 var s = GOOGLE_RESOURCES[i];
2317 s.matched_tag = 0;
2318 s.matched_title = 0;
2319 var matched = false;
2320
2321 // Check if query matches any tags; work backwards toward 1 to assist ranking
2322 for (var j = s.keywords.length - 1; j >= 0; j--) {
2323 // it matches a tag
2324 if (s.keywords[j].toLowerCase().match(textRegex)) {
2325 matched = true;
2326 s.matched_tag = j + 1; // add 1 to index position
2327 }
2328 }
2329 // Check if query matches the doc title, but only for current language
2330 if (s.lang == currentLang) {
2331 // if query matches the doc title
2332 if (s.title.toLowerCase().match(textRegex)) {
2333 matched = true;
2334 s.matched_title = 1;
2335 }
2336 }
2337 if (matched) {
2338 gDocsMatches[matchedCountDocs] = s;
2339 matchedCountDocs++;
2340 }
2341 }
2342
2343
2344 // Search for Samples
2345 for (var i=0; i<SAMPLES_RESOURCES.length; i++) {
2346 // current search comparison, with counters for tag and title,
2347 // used later to improve ranking
2348 var s = SAMPLES_RESOURCES[i];
2349 s.matched_tag = 0;
2350 s.matched_title = 0;
2351 var matched = false;
2352 // Check if query matches any tags; work backwards toward 1 to assist ranking
2353 for (var j = s.keywords.length - 1; j >= 0; j--) {
2354 // it matches a tag
2355 if (s.keywords[j].toLowerCase().match(textRegex)) {
2356 matched = true;
2357 s.matched_tag = j + 1; // add 1 to index position
2358 }
2359 }
2360 // Check if query matches the doc title, but only for current language
2361 if (s.lang == currentLang) {
2362 // if query matches the doc title.t
2363 if (s.title.toLowerCase().match(textRegex)) {
2364 matched = true;
2365 s.matched_title = 1;
2366 }
2367 }
2368 if (matched) {
2369 gDocsMatches[matchedCountDocs] = s;
2370 matchedCountDocs++;
2371 }
2372 }
2373
2374 // Rank/sort all the matched pages
Scott Main0e76e7e2013-03-12 10:24:07 -07002375 rank_autocomplete_doc_results(text, gDocsMatches);
2376 }
2377
2378 // draw the suggestions
Scott Mainf5089842012-08-14 16:31:07 -07002379 sync_selection_table(toroot);
2380 return true; // allow the event to bubble up to the search api
2381 }
2382}
2383
Scott Main0e76e7e2013-03-12 10:24:07 -07002384/* Order the jd doc result list based on match quality */
2385function rank_autocomplete_doc_results(query, matches) {
2386 query = query || '';
2387 if (!matches || !matches.length)
2388 return;
2389
2390 var _resultScoreFn = function(match) {
2391 var score = 1.0;
2392
2393 // if the query matched a tag
2394 if (match.matched_tag > 0) {
2395 // multiply score by factor relative to position in tags list (max of 3)
2396 score *= 3 / match.matched_tag;
2397
2398 // if it also matched the title
2399 if (match.matched_title > 0) {
2400 score *= 2;
2401 }
2402 } else if (match.matched_title > 0) {
2403 score *= 3;
2404 }
2405
2406 return score;
2407 };
2408
2409 for (var i=0; i<matches.length; i++) {
2410 matches[i].__resultScore = _resultScoreFn(matches[i]);
2411 }
2412
2413 matches.sort(function(a,b){
2414 var n = b.__resultScore - a.__resultScore;
2415 if (n == 0) // lexicographical sort if scores are the same
2416 n = (a.label < b.label) ? -1 : 1;
2417 return n;
2418 });
2419}
2420
Scott Main7e447ed2013-02-19 17:22:37 -08002421/* Order the result list based on match quality */
Scott Main0e76e7e2013-03-12 10:24:07 -07002422function rank_autocomplete_api_results(query, matches) {
Scott Mainf5089842012-08-14 16:31:07 -07002423 query = query || '';
Scott Main7e447ed2013-02-19 17:22:37 -08002424 if (!matches || !matches.length)
Scott Mainf5089842012-08-14 16:31:07 -07002425 return;
2426
2427 // helper function that gets the last occurence index of the given regex
2428 // in the given string, or -1 if not found
2429 var _lastSearch = function(s, re) {
2430 if (s == '')
2431 return -1;
2432 var l = -1;
2433 var tmp;
2434 while ((tmp = s.search(re)) >= 0) {
2435 if (l < 0) l = 0;
2436 l += tmp;
2437 s = s.substr(tmp + 1);
2438 }
2439 return l;
2440 };
2441
2442 // helper function that counts the occurrences of a given character in
2443 // a given string
2444 var _countChar = function(s, c) {
2445 var n = 0;
2446 for (var i=0; i<s.length; i++)
2447 if (s.charAt(i) == c) ++n;
2448 return n;
2449 };
2450
2451 var queryLower = query.toLowerCase();
2452 var queryAlnum = (queryLower.match(/\w+/) || [''])[0];
2453 var partPrefixAlnumRE = new RegExp('\\b' + queryAlnum);
2454 var partExactAlnumRE = new RegExp('\\b' + queryAlnum + '\\b');
2455
2456 var _resultScoreFn = function(result) {
2457 // scores are calculated based on exact and prefix matches,
2458 // and then number of path separators (dots) from the last
2459 // match (i.e. favoring classes and deep package names)
2460 var score = 1.0;
2461 var labelLower = result.label.toLowerCase();
2462 var t;
2463 t = _lastSearch(labelLower, partExactAlnumRE);
2464 if (t >= 0) {
2465 // exact part match
2466 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2467 score *= 200 / (partsAfter + 1);
2468 } else {
2469 t = _lastSearch(labelLower, partPrefixAlnumRE);
2470 if (t >= 0) {
2471 // part prefix match
2472 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2473 score *= 20 / (partsAfter + 1);
2474 }
2475 }
2476
2477 return score;
2478 };
2479
Scott Main7e447ed2013-02-19 17:22:37 -08002480 for (var i=0; i<matches.length; i++) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002481 // if the API is deprecated, default score is 0; otherwise, perform scoring
2482 if (matches[i].deprecated == "true") {
2483 matches[i].__resultScore = 0;
2484 } else {
2485 matches[i].__resultScore = _resultScoreFn(matches[i]);
2486 }
Scott Mainf5089842012-08-14 16:31:07 -07002487 }
2488
Scott Main7e447ed2013-02-19 17:22:37 -08002489 matches.sort(function(a,b){
Scott Mainf5089842012-08-14 16:31:07 -07002490 var n = b.__resultScore - a.__resultScore;
2491 if (n == 0) // lexicographical sort if scores are the same
2492 n = (a.label < b.label) ? -1 : 1;
2493 return n;
2494 });
2495}
2496
Scott Main7e447ed2013-02-19 17:22:37 -08002497/* Add emphasis to part of string that matches query */
Scott Mainf5089842012-08-14 16:31:07 -07002498function highlight_autocomplete_result_labels(query) {
2499 query = query || '';
Scott Main7e447ed2013-02-19 17:22:37 -08002500 if ((!gMatches || !gMatches.length) && (!gGoogleMatches || !gGoogleMatches.length))
Scott Mainf5089842012-08-14 16:31:07 -07002501 return;
2502
2503 var queryLower = query.toLowerCase();
2504 var queryAlnumDot = (queryLower.match(/[\w\.]+/) || [''])[0];
2505 var queryRE = new RegExp(
2506 '(' + queryAlnumDot.replace(/\./g, '\\.') + ')', 'ig');
2507 for (var i=0; i<gMatches.length; i++) {
2508 gMatches[i].__hilabel = gMatches[i].label.replace(
2509 queryRE, '<b>$1</b>');
2510 }
Scott Main7e447ed2013-02-19 17:22:37 -08002511 for (var i=0; i<gGoogleMatches.length; i++) {
2512 gGoogleMatches[i].__hilabel = gGoogleMatches[i].label.replace(
2513 queryRE, '<b>$1</b>');
2514 }
Scott Mainf5089842012-08-14 16:31:07 -07002515}
2516
2517function search_focus_changed(obj, focused)
2518{
Scott Main3b90aff2013-08-01 18:09:35 -07002519 if (!focused) {
Scott Mainf5089842012-08-14 16:31:07 -07002520 if(obj.value == ""){
2521 $(".search .close").addClass("hide");
2522 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002523 $(".suggest-card").hide();
Scott Mainf5089842012-08-14 16:31:07 -07002524 }
2525}
2526
2527function submit_search() {
2528 var query = document.getElementById('search_autocomplete').value;
2529 location.hash = 'q=' + query;
2530 loadSearchResults();
Dirk Doughertyc3921652014-05-13 16:55:26 -07002531 $("#searchResults").slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002532 return false;
2533}
2534
2535
2536function hideResults() {
Dirk Doughertyc3921652014-05-13 16:55:26 -07002537 $("#searchResults").slideUp('fast', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002538 $(".search .close").addClass("hide");
2539 location.hash = '';
Scott Main3b90aff2013-08-01 18:09:35 -07002540
Scott Mainf5089842012-08-14 16:31:07 -07002541 $("#search_autocomplete").val("").blur();
Scott Main3b90aff2013-08-01 18:09:35 -07002542
Scott Mainf5089842012-08-14 16:31:07 -07002543 // reset the ajax search callback to nothing, so results don't appear unless ENTER
2544 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {});
Scott Main0e76e7e2013-03-12 10:24:07 -07002545
2546 // forcefully regain key-up event control (previously jacked by search api)
2547 $("#search_autocomplete").keyup(function(event) {
2548 return search_changed(event, false, toRoot);
2549 });
2550
Scott Mainf5089842012-08-14 16:31:07 -07002551 return false;
2552}
2553
2554
2555
2556/* ########################################################## */
2557/* ################ CUSTOM SEARCH ENGINE ################## */
2558/* ########################################################## */
2559
Scott Mainf5089842012-08-14 16:31:07 -07002560var searchControl;
Scott Main0e76e7e2013-03-12 10:24:07 -07002561google.load('search', '1', {"callback" : function() {
2562 searchControl = new google.search.SearchControl();
2563 } });
Scott Mainf5089842012-08-14 16:31:07 -07002564
2565function loadSearchResults() {
2566 document.getElementById("search_autocomplete").style.color = "#000";
2567
Scott Mainf5089842012-08-14 16:31:07 -07002568 searchControl = new google.search.SearchControl();
2569
2570 // use our existing search form and use tabs when multiple searchers are used
2571 drawOptions = new google.search.DrawOptions();
2572 drawOptions.setDrawMode(google.search.SearchControl.DRAW_MODE_TABBED);
2573 drawOptions.setInput(document.getElementById("search_autocomplete"));
2574
2575 // configure search result options
2576 searchOptions = new google.search.SearcherOptions();
2577 searchOptions.setExpandMode(GSearchControl.EXPAND_MODE_OPEN);
2578
2579 // configure each of the searchers, for each tab
2580 devSiteSearcher = new google.search.WebSearch();
2581 devSiteSearcher.setUserDefinedLabel("All");
2582 devSiteSearcher.setSiteRestriction("001482626316274216503:zu90b7s047u");
2583
2584 designSearcher = new google.search.WebSearch();
2585 designSearcher.setUserDefinedLabel("Design");
2586 designSearcher.setSiteRestriction("http://developer.android.com/design/");
2587
2588 trainingSearcher = new google.search.WebSearch();
2589 trainingSearcher.setUserDefinedLabel("Training");
2590 trainingSearcher.setSiteRestriction("http://developer.android.com/training/");
2591
2592 guidesSearcher = new google.search.WebSearch();
2593 guidesSearcher.setUserDefinedLabel("Guides");
2594 guidesSearcher.setSiteRestriction("http://developer.android.com/guide/");
2595
2596 referenceSearcher = new google.search.WebSearch();
2597 referenceSearcher.setUserDefinedLabel("Reference");
2598 referenceSearcher.setSiteRestriction("http://developer.android.com/reference/");
2599
Scott Maindf08ada2012-12-03 08:54:37 -08002600 googleSearcher = new google.search.WebSearch();
2601 googleSearcher.setUserDefinedLabel("Google Services");
2602 googleSearcher.setSiteRestriction("http://developer.android.com/google/");
2603
Scott Mainf5089842012-08-14 16:31:07 -07002604 blogSearcher = new google.search.WebSearch();
2605 blogSearcher.setUserDefinedLabel("Blog");
2606 blogSearcher.setSiteRestriction("http://android-developers.blogspot.com");
2607
2608 // add each searcher to the search control
2609 searchControl.addSearcher(devSiteSearcher, searchOptions);
2610 searchControl.addSearcher(designSearcher, searchOptions);
2611 searchControl.addSearcher(trainingSearcher, searchOptions);
2612 searchControl.addSearcher(guidesSearcher, searchOptions);
2613 searchControl.addSearcher(referenceSearcher, searchOptions);
Scott Maindf08ada2012-12-03 08:54:37 -08002614 searchControl.addSearcher(googleSearcher, searchOptions);
Scott Mainf5089842012-08-14 16:31:07 -07002615 searchControl.addSearcher(blogSearcher, searchOptions);
2616
2617 // configure result options
2618 searchControl.setResultSetSize(google.search.Search.LARGE_RESULTSET);
2619 searchControl.setLinkTarget(google.search.Search.LINK_TARGET_SELF);
2620 searchControl.setTimeoutInterval(google.search.SearchControl.TIMEOUT_SHORT);
2621 searchControl.setNoResultsString(google.search.SearchControl.NO_RESULTS_DEFAULT_STRING);
2622
2623 // upon ajax search, refresh the url and search title
2624 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {
2625 updateResultTitle(query);
2626 var query = document.getElementById('search_autocomplete').value;
2627 location.hash = 'q=' + query;
2628 });
2629
Scott Mainde295272013-03-25 15:48:35 -07002630 // once search results load, set up click listeners
2631 searchControl.setSearchCompleteCallback(this, function(control, searcher, query) {
2632 addResultClickListeners();
2633 });
2634
Scott Mainf5089842012-08-14 16:31:07 -07002635 // draw the search results box
2636 searchControl.draw(document.getElementById("leftSearchControl"), drawOptions);
2637
2638 // get query and execute the search
2639 searchControl.execute(decodeURI(getQuery(location.hash)));
2640
2641 document.getElementById("search_autocomplete").focus();
2642 addTabListeners();
2643}
2644// End of loadSearchResults
2645
2646
2647google.setOnLoadCallback(function(){
2648 if (location.hash.indexOf("q=") == -1) {
2649 // if there's no query in the url, don't search and make sure results are hidden
2650 $('#searchResults').hide();
2651 return;
2652 } else {
2653 // first time loading search results for this page
Dirk Doughertyc3921652014-05-13 16:55:26 -07002654 $('#searchResults').slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002655 $(".search .close").removeClass("hide");
2656 loadSearchResults();
2657 }
2658}, true);
2659
smain@google.com9a818f52014-10-03 09:25:59 -07002660/* Adjust the scroll position to account for sticky header, only if the hash matches an id.
2661 This does not handle <a name=""> tags. Some CSS fixes those, but only for reference docs. */
Scott Mainb16376f2014-05-21 20:35:47 -07002662function offsetScrollForSticky() {
smain@google.com11fc0092014-10-16 22:10:00 -07002663 // Ignore if there's no search bar (some special pages have no header)
2664 if ($("#search-container").length < 1) return;
2665
smain@google.com3b77ab52014-06-17 11:57:27 -07002666 var hash = escape(location.hash.substr(1));
2667 var $matchingElement = $("#"+hash);
smain@google.com08f336ea2014-10-03 17:40:00 -07002668 // Sanity check that there's an element with that ID on the page
2669 if ($matchingElement.length) {
Scott Mainb16376f2014-05-21 20:35:47 -07002670 // If the position of the target element is near the top of the page (<20px, where we expect it
2671 // to be because we need to move it down 60px to become in view), then move it down 60px
2672 if (Math.abs($matchingElement.offset().top - $(window).scrollTop()) < 20) {
2673 $(window).scrollTop($(window).scrollTop() - 60);
Scott Mainb16376f2014-05-21 20:35:47 -07002674 }
2675 }
2676}
2677
Scott Mainf5089842012-08-14 16:31:07 -07002678// when an event on the browser history occurs (back, forward, load) requery hash and do search
2679$(window).hashchange( function(){
smain@google.com2f077192014-10-09 18:04:10 -07002680 // Ignore if there's no search bar (some special pages have no header)
2681 if ($("#search-container").length < 1) return;
2682
Dirk Doughertyc3921652014-05-13 16:55:26 -07002683 // If the hash isn't a search query or there's an error in the query,
2684 // then adjust the scroll position to account for sticky header, then exit.
Scott Mainf5089842012-08-14 16:31:07 -07002685 if ((location.hash.indexOf("q=") == -1) || (query == "undefined")) {
2686 // If the results pane is open, close it.
2687 if (!$("#searchResults").is(":hidden")) {
2688 hideResults();
2689 }
Scott Mainb16376f2014-05-21 20:35:47 -07002690 offsetScrollForSticky();
Scott Mainf5089842012-08-14 16:31:07 -07002691 return;
2692 }
2693
2694 // Otherwise, we have a search to do
2695 var query = decodeURI(getQuery(location.hash));
2696 searchControl.execute(query);
Dirk Doughertyc3921652014-05-13 16:55:26 -07002697 $('#searchResults').slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002698 $("#search_autocomplete").focus();
2699 $(".search .close").removeClass("hide");
2700
2701 updateResultTitle(query);
2702});
2703
2704function updateResultTitle(query) {
2705 $("#searchTitle").html("Results for <em>" + escapeHTML(query) + "</em>");
2706}
2707
2708// forcefully regain key-up event control (previously jacked by search api)
2709$("#search_autocomplete").keyup(function(event) {
2710 return search_changed(event, false, toRoot);
2711});
2712
2713// add event listeners to each tab so we can track the browser history
2714function addTabListeners() {
2715 var tabHeaders = $(".gsc-tabHeader");
2716 for (var i = 0; i < tabHeaders.length; i++) {
2717 $(tabHeaders[i]).attr("id",i).click(function() {
2718 /*
2719 // make a copy of the page numbers for the search left pane
2720 setTimeout(function() {
2721 // remove any residual page numbers
2722 $('#searchResults .gsc-tabsArea .gsc-cursor-box.gs-bidi-start-align').remove();
Scott Main3b90aff2013-08-01 18:09:35 -07002723 // move the page numbers to the left position; make a clone,
Scott Mainf5089842012-08-14 16:31:07 -07002724 // because the element is drawn to the DOM only once
Scott Main3b90aff2013-08-01 18:09:35 -07002725 // and because we're going to remove it (previous line),
2726 // we need it to be available to move again as the user navigates
Scott Mainf5089842012-08-14 16:31:07 -07002727 $('#searchResults .gsc-webResult .gsc-cursor-box.gs-bidi-start-align:visible')
2728 .clone().appendTo('#searchResults .gsc-tabsArea');
2729 }, 200);
2730 */
2731 });
2732 }
2733 setTimeout(function(){$(tabHeaders[0]).click()},200);
2734}
2735
Scott Mainde295272013-03-25 15:48:35 -07002736// add analytics tracking events to each result link
2737function addResultClickListeners() {
2738 $("#searchResults a.gs-title").each(function(index, link) {
2739 // When user clicks enter for Google search results, track it
2740 $(link).click(function() {
smain@google.comed677d72014-12-12 10:19:17 -08002741 ga('send', 'event', 'Google Click', 'clicked: ' + $(this).attr('href'),
2742 'query: ' + $("#search_autocomplete").val().toLowerCase());
Scott Mainde295272013-03-25 15:48:35 -07002743 });
2744 });
2745}
2746
Scott Mainf5089842012-08-14 16:31:07 -07002747
2748function getQuery(hash) {
2749 var queryParts = hash.split('=');
2750 return queryParts[1];
2751}
2752
2753/* returns the given string with all HTML brackets converted to entities
2754 TODO: move this to the site's JS library */
2755function escapeHTML(string) {
2756 return string.replace(/</g,"&lt;")
2757 .replace(/>/g,"&gt;");
2758}
2759
2760
2761
2762
2763
2764
2765
2766/* ######################################################## */
2767/* ################# JAVADOC REFERENCE ################### */
2768/* ######################################################## */
2769
Scott Main65511c02012-09-07 15:51:32 -07002770/* Initialize some droiddoc stuff, but only if we're in the reference */
Scott Main52dd2062013-08-15 12:22:28 -07002771if (location.pathname.indexOf("/reference") == 0) {
2772 if(!(location.pathname.indexOf("/reference-gms/packages.html") == 0)
2773 && !(location.pathname.indexOf("/reference-gcm/packages.html") == 0)
2774 && !(location.pathname.indexOf("/reference/com/google") == 0)) {
Robert Ly67d75f12012-12-03 12:53:42 -08002775 $(document).ready(function() {
2776 // init available apis based on user pref
2777 changeApiLevel();
2778 initSidenavHeightResize()
2779 });
2780 }
Scott Main65511c02012-09-07 15:51:32 -07002781}
Scott Mainf5089842012-08-14 16:31:07 -07002782
2783var API_LEVEL_COOKIE = "api_level";
2784var minLevel = 1;
2785var maxLevel = 1;
2786
2787/******* SIDENAV DIMENSIONS ************/
Scott Main3b90aff2013-08-01 18:09:35 -07002788
Scott Mainf5089842012-08-14 16:31:07 -07002789 function initSidenavHeightResize() {
2790 // Change the drag bar size to nicely fit the scrollbar positions
2791 var $dragBar = $(".ui-resizable-s");
2792 $dragBar.css({'width': $dragBar.parent().width() - 5 + "px"});
Scott Main3b90aff2013-08-01 18:09:35 -07002793
2794 $( "#resize-packages-nav" ).resizable({
Scott Mainf5089842012-08-14 16:31:07 -07002795 containment: "#nav-panels",
2796 handles: "s",
2797 alsoResize: "#packages-nav",
2798 resize: function(event, ui) { resizeNav(); }, /* resize the nav while dragging */
2799 stop: function(event, ui) { saveNavPanels(); } /* once stopped, save the sizes to cookie */
2800 });
Scott Main3b90aff2013-08-01 18:09:35 -07002801
Scott Mainf5089842012-08-14 16:31:07 -07002802 }
Scott Main3b90aff2013-08-01 18:09:35 -07002803
Scott Mainf5089842012-08-14 16:31:07 -07002804function updateSidenavFixedWidth() {
Scott Mainf5257812014-05-22 17:26:38 -07002805 if (!sticky) return;
Scott Mainf5089842012-08-14 16:31:07 -07002806 $('#devdoc-nav').css({
2807 'width' : $('#side-nav').css('width'),
2808 'margin' : $('#side-nav').css('margin')
2809 });
2810 $('#devdoc-nav a.totop').css({'display':'block','width':$("#nav").innerWidth()+'px'});
Scott Main3b90aff2013-08-01 18:09:35 -07002811
Scott Mainf5089842012-08-14 16:31:07 -07002812 initSidenavHeightResize();
2813}
2814
2815function updateSidenavFullscreenWidth() {
Scott Mainf5257812014-05-22 17:26:38 -07002816 if (!sticky) return;
Scott Mainf5089842012-08-14 16:31:07 -07002817 $('#devdoc-nav').css({
2818 'width' : $('#side-nav').css('width'),
2819 'margin' : $('#side-nav').css('margin')
2820 });
2821 $('#devdoc-nav .totop').css({'left': 'inherit'});
Scott Main3b90aff2013-08-01 18:09:35 -07002822
Scott Mainf5089842012-08-14 16:31:07 -07002823 initSidenavHeightResize();
2824}
2825
2826function buildApiLevelSelector() {
2827 maxLevel = SINCE_DATA.length;
2828 var userApiLevel = parseInt(readCookie(API_LEVEL_COOKIE));
2829 userApiLevel = userApiLevel == 0 ? maxLevel : userApiLevel; // If there's no cookie (zero), use the max by default
2830
2831 minLevel = parseInt($("#doc-api-level").attr("class"));
2832 // Handle provisional api levels; the provisional level will always be the highest possible level
2833 // Provisional api levels will also have a length; other stuff that's just missing a level won't,
2834 // so leave those kinds of entities at the default level of 1 (for example, the R.styleable class)
2835 if (isNaN(minLevel) && minLevel.length) {
2836 minLevel = maxLevel;
2837 }
2838 var select = $("#apiLevelSelector").html("").change(changeApiLevel);
2839 for (var i = maxLevel-1; i >= 0; i--) {
2840 var option = $("<option />").attr("value",""+SINCE_DATA[i]).append(""+SINCE_DATA[i]);
2841 // if (SINCE_DATA[i] < minLevel) option.addClass("absent"); // always false for strings (codenames)
2842 select.append(option);
2843 }
2844
2845 // get the DOM element and use setAttribute cuz IE6 fails when using jquery .attr('selected',true)
2846 var selectedLevelItem = $("#apiLevelSelector option[value='"+userApiLevel+"']").get(0);
2847 selectedLevelItem.setAttribute('selected',true);
2848}
2849
2850function changeApiLevel() {
2851 maxLevel = SINCE_DATA.length;
2852 var selectedLevel = maxLevel;
2853
2854 selectedLevel = parseInt($("#apiLevelSelector option:selected").val());
2855 toggleVisisbleApis(selectedLevel, "body");
2856
smain@google.com6bdcb982014-11-14 11:53:07 -08002857 writeCookie(API_LEVEL_COOKIE, selectedLevel, null);
Scott Mainf5089842012-08-14 16:31:07 -07002858
2859 if (selectedLevel < minLevel) {
2860 var thing = ($("#jd-header").html().indexOf("package") != -1) ? "package" : "class";
Scott Main8f24ca82012-11-16 10:34:22 -08002861 $("#naMessage").show().html("<div><p><strong>This " + thing
2862 + " requires API level " + minLevel + " or higher.</strong></p>"
2863 + "<p>This document is hidden because your selected API level for the documentation is "
2864 + selectedLevel + ". You can change the documentation API level with the selector "
2865 + "above the left navigation.</p>"
2866 + "<p>For more information about specifying the API level your app requires, "
2867 + "read <a href='" + toRoot + "training/basics/supporting-devices/platforms.html'"
2868 + ">Supporting Different Platform Versions</a>.</p>"
2869 + "<input type='button' value='OK, make this page visible' "
2870 + "title='Change the API level to " + minLevel + "' "
2871 + "onclick='$(\"#apiLevelSelector\").val(\"" + minLevel + "\");changeApiLevel();' />"
2872 + "</div>");
Scott Mainf5089842012-08-14 16:31:07 -07002873 } else {
2874 $("#naMessage").hide();
2875 }
2876}
2877
2878function toggleVisisbleApis(selectedLevel, context) {
2879 var apis = $(".api",context);
2880 apis.each(function(i) {
2881 var obj = $(this);
2882 var className = obj.attr("class");
2883 var apiLevelIndex = className.lastIndexOf("-")+1;
2884 var apiLevelEndIndex = className.indexOf(" ", apiLevelIndex);
2885 apiLevelEndIndex = apiLevelEndIndex != -1 ? apiLevelEndIndex : className.length;
2886 var apiLevel = className.substring(apiLevelIndex, apiLevelEndIndex);
2887 if (apiLevel.length == 0) { // for odd cases when the since data is actually missing, just bail
2888 return;
2889 }
2890 apiLevel = parseInt(apiLevel);
2891
2892 // Handle provisional api levels; if this item's level is the provisional one, set it to the max
2893 var selectedLevelNum = parseInt(selectedLevel)
2894 var apiLevelNum = parseInt(apiLevel);
2895 if (isNaN(apiLevelNum)) {
2896 apiLevelNum = maxLevel;
2897 }
2898
2899 // Grey things out that aren't available and give a tooltip title
2900 if (apiLevelNum > selectedLevelNum) {
2901 obj.addClass("absent").attr("title","Requires API Level \""
Scott Main641c2c22013-10-31 14:48:45 -07002902 + apiLevel + "\" or higher. To reveal, change the target API level "
2903 + "above the left navigation.");
Scott Main3b90aff2013-08-01 18:09:35 -07002904 }
Scott Mainf5089842012-08-14 16:31:07 -07002905 else obj.removeClass("absent").removeAttr("title");
2906 });
2907}
2908
2909
2910
2911
2912/* ################# SIDENAV TREE VIEW ################### */
2913
2914function new_node(me, mom, text, link, children_data, api_level)
2915{
2916 var node = new Object();
2917 node.children = Array();
2918 node.children_data = children_data;
2919 node.depth = mom.depth + 1;
2920
2921 node.li = document.createElement("li");
2922 mom.get_children_ul().appendChild(node.li);
2923
2924 node.label_div = document.createElement("div");
2925 node.label_div.className = "label";
2926 if (api_level != null) {
2927 $(node.label_div).addClass("api");
2928 $(node.label_div).addClass("api-level-"+api_level);
2929 }
2930 node.li.appendChild(node.label_div);
2931
2932 if (children_data != null) {
2933 node.expand_toggle = document.createElement("a");
2934 node.expand_toggle.href = "javascript:void(0)";
2935 node.expand_toggle.onclick = function() {
2936 if (node.expanded) {
2937 $(node.get_children_ul()).slideUp("fast");
2938 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2939 node.expanded = false;
2940 } else {
2941 expand_node(me, node);
2942 }
2943 };
2944 node.label_div.appendChild(node.expand_toggle);
2945
2946 node.plus_img = document.createElement("img");
2947 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2948 node.plus_img.className = "plus";
2949 node.plus_img.width = "8";
2950 node.plus_img.border = "0";
2951 node.expand_toggle.appendChild(node.plus_img);
2952
2953 node.expanded = false;
2954 }
2955
2956 var a = document.createElement("a");
2957 node.label_div.appendChild(a);
2958 node.label = document.createTextNode(text);
2959 a.appendChild(node.label);
2960 if (link) {
2961 a.href = me.toroot + link;
2962 } else {
2963 if (children_data != null) {
2964 a.className = "nolink";
2965 a.href = "javascript:void(0)";
2966 a.onclick = node.expand_toggle.onclick;
2967 // This next line shouldn't be necessary. I'll buy a beer for the first
2968 // person who figures out how to remove this line and have the link
2969 // toggle shut on the first try. --joeo@android.com
2970 node.expanded = false;
2971 }
2972 }
Scott Main3b90aff2013-08-01 18:09:35 -07002973
Scott Mainf5089842012-08-14 16:31:07 -07002974
2975 node.children_ul = null;
2976 node.get_children_ul = function() {
2977 if (!node.children_ul) {
2978 node.children_ul = document.createElement("ul");
2979 node.children_ul.className = "children_ul";
2980 node.children_ul.style.display = "none";
2981 node.li.appendChild(node.children_ul);
2982 }
2983 return node.children_ul;
2984 };
2985
2986 return node;
2987}
2988
Robert Lyd2dd6e52012-11-29 21:28:48 -08002989
2990
2991
Scott Mainf5089842012-08-14 16:31:07 -07002992function expand_node(me, node)
2993{
2994 if (node.children_data && !node.expanded) {
2995 if (node.children_visited) {
2996 $(node.get_children_ul()).slideDown("fast");
2997 } else {
2998 get_node(me, node);
2999 if ($(node.label_div).hasClass("absent")) {
3000 $(node.get_children_ul()).addClass("absent");
Scott Main3b90aff2013-08-01 18:09:35 -07003001 }
Scott Mainf5089842012-08-14 16:31:07 -07003002 $(node.get_children_ul()).slideDown("fast");
3003 }
3004 node.plus_img.src = me.toroot + "assets/images/triangle-opened-small.png";
3005 node.expanded = true;
3006
3007 // perform api level toggling because new nodes are new to the DOM
3008 var selectedLevel = $("#apiLevelSelector option:selected").val();
3009 toggleVisisbleApis(selectedLevel, "#side-nav");
3010 }
3011}
3012
3013function get_node(me, mom)
3014{
3015 mom.children_visited = true;
3016 for (var i in mom.children_data) {
3017 var node_data = mom.children_data[i];
3018 mom.children[i] = new_node(me, mom, node_data[0], node_data[1],
3019 node_data[2], node_data[3]);
3020 }
3021}
3022
3023function this_page_relative(toroot)
3024{
3025 var full = document.location.pathname;
3026 var file = "";
3027 if (toroot.substr(0, 1) == "/") {
3028 if (full.substr(0, toroot.length) == toroot) {
3029 return full.substr(toroot.length);
3030 } else {
3031 // the file isn't under toroot. Fail.
3032 return null;
3033 }
3034 } else {
3035 if (toroot != "./") {
3036 toroot = "./" + toroot;
3037 }
3038 do {
3039 if (toroot.substr(toroot.length-3, 3) == "../" || toroot == "./") {
3040 var pos = full.lastIndexOf("/");
3041 file = full.substr(pos) + file;
3042 full = full.substr(0, pos);
3043 toroot = toroot.substr(0, toroot.length-3);
3044 }
3045 } while (toroot != "" && toroot != "/");
3046 return file.substr(1);
3047 }
3048}
3049
3050function find_page(url, data)
3051{
3052 var nodes = data;
3053 var result = null;
3054 for (var i in nodes) {
3055 var d = nodes[i];
3056 if (d[1] == url) {
3057 return new Array(i);
3058 }
3059 else if (d[2] != null) {
3060 result = find_page(url, d[2]);
3061 if (result != null) {
3062 return (new Array(i).concat(result));
3063 }
3064 }
3065 }
3066 return null;
3067}
3068
Scott Mainf5089842012-08-14 16:31:07 -07003069function init_default_navtree(toroot) {
Scott Main25e73002013-03-27 15:24:06 -07003070 // load json file for navtree data
3071 $.getScript(toRoot + 'navtree_data.js', function(data, textStatus, jqxhr) {
3072 // when the file is loaded, initialize the tree
3073 if(jqxhr.status === 200) {
3074 init_navtree("tree-list", toroot, NAVTREE_DATA);
3075 }
3076 });
Scott Main3b90aff2013-08-01 18:09:35 -07003077
Scott Mainf5089842012-08-14 16:31:07 -07003078 // perform api level toggling because because the whole tree is new to the DOM
3079 var selectedLevel = $("#apiLevelSelector option:selected").val();
3080 toggleVisisbleApis(selectedLevel, "#side-nav");
3081}
3082
3083function init_navtree(navtree_id, toroot, root_nodes)
3084{
3085 var me = new Object();
3086 me.toroot = toroot;
3087 me.node = new Object();
3088
3089 me.node.li = document.getElementById(navtree_id);
3090 me.node.children_data = root_nodes;
3091 me.node.children = new Array();
3092 me.node.children_ul = document.createElement("ul");
3093 me.node.get_children_ul = function() { return me.node.children_ul; };
3094 //me.node.children_ul.className = "children_ul";
3095 me.node.li.appendChild(me.node.children_ul);
3096 me.node.depth = 0;
3097
3098 get_node(me, me.node);
3099
3100 me.this_page = this_page_relative(toroot);
3101 me.breadcrumbs = find_page(me.this_page, root_nodes);
3102 if (me.breadcrumbs != null && me.breadcrumbs.length != 0) {
3103 var mom = me.node;
3104 for (var i in me.breadcrumbs) {
3105 var j = me.breadcrumbs[i];
3106 mom = mom.children[j];
3107 expand_node(me, mom);
3108 }
3109 mom.label_div.className = mom.label_div.className + " selected";
3110 addLoadEvent(function() {
3111 scrollIntoView("nav-tree");
3112 });
3113 }
3114}
3115
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003116
3117
3118
3119
3120
3121
3122
Robert Lyd2dd6e52012-11-29 21:28:48 -08003123/* TODO: eliminate redundancy with non-google functions */
3124function init_google_navtree(navtree_id, toroot, root_nodes)
3125{
3126 var me = new Object();
3127 me.toroot = toroot;
3128 me.node = new Object();
3129
3130 me.node.li = document.getElementById(navtree_id);
3131 me.node.children_data = root_nodes;
3132 me.node.children = new Array();
3133 me.node.children_ul = document.createElement("ul");
3134 me.node.get_children_ul = function() { return me.node.children_ul; };
3135 //me.node.children_ul.className = "children_ul";
3136 me.node.li.appendChild(me.node.children_ul);
3137 me.node.depth = 0;
3138
3139 get_google_node(me, me.node);
Robert Lyd2dd6e52012-11-29 21:28:48 -08003140}
3141
3142function new_google_node(me, mom, text, link, children_data, api_level)
3143{
3144 var node = new Object();
3145 var child;
3146 node.children = Array();
3147 node.children_data = children_data;
3148 node.depth = mom.depth + 1;
3149 node.get_children_ul = function() {
3150 if (!node.children_ul) {
Scott Main3b90aff2013-08-01 18:09:35 -07003151 node.children_ul = document.createElement("ul");
3152 node.children_ul.className = "tree-list-children";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003153 node.li.appendChild(node.children_ul);
3154 }
3155 return node.children_ul;
3156 };
3157 node.li = document.createElement("li");
3158
3159 mom.get_children_ul().appendChild(node.li);
Scott Main3b90aff2013-08-01 18:09:35 -07003160
3161
Robert Lyd2dd6e52012-11-29 21:28:48 -08003162 if(link) {
3163 child = document.createElement("a");
3164
3165 }
3166 else {
3167 child = document.createElement("span");
Scott Mainac71b2b2012-11-30 14:40:58 -08003168 child.className = "tree-list-subtitle";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003169
3170 }
3171 if (children_data != null) {
3172 node.li.className="nav-section";
3173 node.label_div = document.createElement("div");
Scott Main3b90aff2013-08-01 18:09:35 -07003174 node.label_div.className = "nav-section-header-ref";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003175 node.li.appendChild(node.label_div);
3176 get_google_node(me, node);
3177 node.label_div.appendChild(child);
3178 }
3179 else {
3180 node.li.appendChild(child);
3181 }
3182 if(link) {
3183 child.href = me.toroot + link;
3184 }
3185 node.label = document.createTextNode(text);
3186 child.appendChild(node.label);
3187
3188 node.children_ul = null;
3189
3190 return node;
3191}
3192
3193function get_google_node(me, mom)
3194{
3195 mom.children_visited = true;
3196 var linkText;
3197 for (var i in mom.children_data) {
3198 var node_data = mom.children_data[i];
3199 linkText = node_data[0];
3200
3201 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3202 linkText = linkText.substr(19, linkText.length);
3203 }
3204 mom.children[i] = new_google_node(me, mom, linkText, node_data[1],
3205 node_data[2], node_data[3]);
3206 }
3207}
Scott Mainad08f072013-08-20 16:49:57 -07003208
3209
3210
3211
3212
3213
3214/****** NEW version of script to build google and sample navs dynamically ******/
3215// TODO: update Google reference docs to tolerate this new implementation
3216
Scott Maine624b3f2013-09-12 12:56:41 -07003217var NODE_NAME = 0;
3218var NODE_HREF = 1;
3219var NODE_GROUP = 2;
3220var NODE_TAGS = 3;
3221var NODE_CHILDREN = 4;
3222
Scott Mainad08f072013-08-20 16:49:57 -07003223function init_google_navtree2(navtree_id, data)
3224{
3225 var $containerUl = $("#"+navtree_id);
Scott Mainad08f072013-08-20 16:49:57 -07003226 for (var i in data) {
3227 var node_data = data[i];
3228 $containerUl.append(new_google_node2(node_data));
3229 }
3230
Scott Main70557ee2013-10-30 14:47:40 -07003231 // Make all third-generation list items 'sticky' to prevent them from collapsing
3232 $containerUl.find('li li li.nav-section').addClass('sticky');
3233
Scott Mainad08f072013-08-20 16:49:57 -07003234 initExpandableNavItems("#"+navtree_id);
3235}
3236
3237function new_google_node2(node_data)
3238{
Scott Maine624b3f2013-09-12 12:56:41 -07003239 var linkText = node_data[NODE_NAME];
Scott Mainad08f072013-08-20 16:49:57 -07003240 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3241 linkText = linkText.substr(19, linkText.length);
3242 }
3243 var $li = $('<li>');
3244 var $a;
Scott Maine624b3f2013-09-12 12:56:41 -07003245 if (node_data[NODE_HREF] != null) {
Scott Main70557ee2013-10-30 14:47:40 -07003246 $a = $('<a href="' + toRoot + node_data[NODE_HREF] + '" title="' + linkText + '" >'
3247 + linkText + '</a>');
Scott Mainad08f072013-08-20 16:49:57 -07003248 } else {
Scott Main70557ee2013-10-30 14:47:40 -07003249 $a = $('<a href="#" onclick="return false;" title="' + linkText + '" >'
3250 + linkText + '/</a>');
Scott Mainad08f072013-08-20 16:49:57 -07003251 }
3252 var $childUl = $('<ul>');
Scott Maine624b3f2013-09-12 12:56:41 -07003253 if (node_data[NODE_CHILDREN] != null) {
Scott Mainad08f072013-08-20 16:49:57 -07003254 $li.addClass("nav-section");
3255 $a = $('<div class="nav-section-header">').append($a);
Scott Maine624b3f2013-09-12 12:56:41 -07003256 if (node_data[NODE_HREF] == null) $a.addClass('empty');
Scott Mainad08f072013-08-20 16:49:57 -07003257
Scott Maine624b3f2013-09-12 12:56:41 -07003258 for (var i in node_data[NODE_CHILDREN]) {
3259 var child_node_data = node_data[NODE_CHILDREN][i];
Scott Mainad08f072013-08-20 16:49:57 -07003260 $childUl.append(new_google_node2(child_node_data));
3261 }
3262 $li.append($childUl);
3263 }
3264 $li.prepend($a);
3265
3266 return $li;
3267}
3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278
Robert Lyd2dd6e52012-11-29 21:28:48 -08003279function showGoogleRefTree() {
3280 init_default_google_navtree(toRoot);
3281 init_default_gcm_navtree(toRoot);
Robert Lyd2dd6e52012-11-29 21:28:48 -08003282}
3283
3284function init_default_google_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07003285 // load json file for navtree data
3286 $.getScript(toRoot + 'gms_navtree_data.js', function(data, textStatus, jqxhr) {
3287 // when the file is loaded, initialize the tree
3288 if(jqxhr.status === 200) {
3289 init_google_navtree("gms-tree-list", toroot, GMS_NAVTREE_DATA);
3290 highlightSidenav();
3291 resizeNav();
3292 }
3293 });
Robert Lyd2dd6e52012-11-29 21:28:48 -08003294}
3295
3296function init_default_gcm_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07003297 // load json file for navtree data
3298 $.getScript(toRoot + 'gcm_navtree_data.js', function(data, textStatus, jqxhr) {
3299 // when the file is loaded, initialize the tree
3300 if(jqxhr.status === 200) {
3301 init_google_navtree("gcm-tree-list", toroot, GCM_NAVTREE_DATA);
3302 highlightSidenav();
3303 resizeNav();
3304 }
3305 });
Robert Lyd2dd6e52012-11-29 21:28:48 -08003306}
3307
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003308function showSamplesRefTree() {
3309 init_default_samples_navtree(toRoot);
3310}
3311
3312function init_default_samples_navtree(toroot) {
3313 // load json file for navtree data
3314 $.getScript(toRoot + 'samples_navtree_data.js', function(data, textStatus, jqxhr) {
3315 // when the file is loaded, initialize the tree
3316 if(jqxhr.status === 200) {
Scott Mainf1435b72013-10-30 16:27:38 -07003317 // hack to remove the "about the samples" link then put it back in
3318 // after we nuke the list to remove the dummy static list of samples
3319 var $firstLi = $("#nav.samples-nav > li:first-child").clone();
3320 $("#nav.samples-nav").empty();
3321 $("#nav.samples-nav").append($firstLi);
3322
Scott Mainad08f072013-08-20 16:49:57 -07003323 init_google_navtree2("nav.samples-nav", SAMPLES_NAVTREE_DATA);
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003324 highlightSidenav();
3325 resizeNav();
Scott Main03aca9a2013-10-31 07:20:55 -07003326 if ($("#jd-content #samples").length) {
3327 showSamples();
3328 }
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003329 }
3330 });
3331}
3332
Scott Mainf5089842012-08-14 16:31:07 -07003333/* TOGGLE INHERITED MEMBERS */
3334
3335/* Toggle an inherited class (arrow toggle)
3336 * @param linkObj The link that was clicked.
3337 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3338 * 'null' to simply toggle.
3339 */
3340function toggleInherited(linkObj, expand) {
3341 var base = linkObj.getAttribute("id");
3342 var list = document.getElementById(base + "-list");
3343 var summary = document.getElementById(base + "-summary");
3344 var trigger = document.getElementById(base + "-trigger");
3345 var a = $(linkObj);
3346 if ( (expand == null && a.hasClass("closed")) || expand ) {
3347 list.style.display = "none";
3348 summary.style.display = "block";
3349 trigger.src = toRoot + "assets/images/triangle-opened.png";
3350 a.removeClass("closed");
3351 a.addClass("opened");
3352 } else if ( (expand == null && a.hasClass("opened")) || (expand == false) ) {
3353 list.style.display = "block";
3354 summary.style.display = "none";
3355 trigger.src = toRoot + "assets/images/triangle-closed.png";
3356 a.removeClass("opened");
3357 a.addClass("closed");
3358 }
3359 return false;
3360}
3361
3362/* Toggle all inherited classes in a single table (e.g. all inherited methods)
3363 * @param linkObj The link that was clicked.
3364 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3365 * 'null' to simply toggle.
3366 */
3367function toggleAllInherited(linkObj, expand) {
3368 var a = $(linkObj);
3369 var table = $(a.parent().parent().parent()); // ugly way to get table/tbody
3370 var expandos = $(".jd-expando-trigger", table);
3371 if ( (expand == null && a.text() == "[Expand]") || expand ) {
3372 expandos.each(function(i) {
3373 toggleInherited(this, true);
3374 });
3375 a.text("[Collapse]");
3376 } else if ( (expand == null && a.text() == "[Collapse]") || (expand == false) ) {
3377 expandos.each(function(i) {
3378 toggleInherited(this, false);
3379 });
3380 a.text("[Expand]");
3381 }
3382 return false;
3383}
3384
3385/* Toggle all inherited members in the class (link in the class title)
3386 */
3387function toggleAllClassInherited() {
3388 var a = $("#toggleAllClassInherited"); // get toggle link from class title
3389 var toggles = $(".toggle-all", $("#body-content"));
3390 if (a.text() == "[Expand All]") {
3391 toggles.each(function(i) {
3392 toggleAllInherited(this, true);
3393 });
3394 a.text("[Collapse All]");
3395 } else {
3396 toggles.each(function(i) {
3397 toggleAllInherited(this, false);
3398 });
3399 a.text("[Expand All]");
3400 }
3401 return false;
3402}
3403
3404/* Expand all inherited members in the class. Used when initiating page search */
3405function ensureAllInheritedExpanded() {
3406 var toggles = $(".toggle-all", $("#body-content"));
3407 toggles.each(function(i) {
3408 toggleAllInherited(this, true);
3409 });
3410 $("#toggleAllClassInherited").text("[Collapse All]");
3411}
3412
3413
3414/* HANDLE KEY EVENTS
3415 * - Listen for Ctrl+F (Cmd on Mac) and expand all inherited members (to aid page search)
3416 */
3417var agent = navigator['userAgent'].toLowerCase();
3418var mac = agent.indexOf("macintosh") != -1;
3419
3420$(document).keydown( function(e) {
3421var control = mac ? e.metaKey && !e.ctrlKey : e.ctrlKey; // get ctrl key
3422 if (control && e.which == 70) { // 70 is "F"
3423 ensureAllInheritedExpanded();
3424 }
3425});
Scott Main498d7102013-08-21 15:47:38 -07003426
3427
3428
3429
3430
3431
3432/* On-demand functions */
3433
3434/** Move sample code line numbers out of PRE block and into non-copyable column */
3435function initCodeLineNumbers() {
3436 var numbers = $("#codesample-block a.number");
3437 if (numbers.length) {
3438 $("#codesample-line-numbers").removeClass("hidden").append(numbers);
3439 }
3440
3441 $(document).ready(function() {
3442 // select entire line when clicked
3443 $("span.code-line").click(function() {
3444 if (!shifted) {
3445 selectText(this);
3446 }
3447 });
3448 // invoke line link on double click
3449 $(".code-line").dblclick(function() {
3450 document.location.hash = $(this).attr('id');
3451 });
3452 // highlight the line when hovering on the number
3453 $("#codesample-line-numbers a.number").mouseover(function() {
3454 var id = $(this).attr('href');
3455 $(id).css('background','#e7e7e7');
3456 });
3457 $("#codesample-line-numbers a.number").mouseout(function() {
3458 var id = $(this).attr('href');
3459 $(id).css('background','none');
3460 });
3461 });
3462}
3463
3464// create SHIFT key binder to avoid the selectText method when selecting multiple lines
3465var shifted = false;
3466$(document).bind('keyup keydown', function(e){shifted = e.shiftKey; return true;} );
3467
3468// courtesy of jasonedelman.com
3469function selectText(element) {
3470 var doc = document
3471 , range, selection
3472 ;
3473 if (doc.body.createTextRange) { //ms
3474 range = doc.body.createTextRange();
3475 range.moveToElementText(element);
3476 range.select();
3477 } else if (window.getSelection) { //all others
Scott Main70557ee2013-10-30 14:47:40 -07003478 selection = window.getSelection();
Scott Main498d7102013-08-21 15:47:38 -07003479 range = doc.createRange();
3480 range.selectNodeContents(element);
3481 selection.removeAllRanges();
3482 selection.addRange(range);
3483 }
Scott Main285f0772013-08-22 23:22:09 +00003484}
Scott Main03aca9a2013-10-31 07:20:55 -07003485
3486
3487
3488
3489/** Display links and other information about samples that match the
3490 group specified by the URL */
3491function showSamples() {
3492 var group = $("#samples").attr('class');
3493 $("#samples").html("<p>Here are some samples for <b>" + group + "</b> apps:</p>");
3494
3495 var $ul = $("<ul>");
3496 $selectedLi = $("#nav li.selected");
3497
3498 $selectedLi.children("ul").children("li").each(function() {
3499 var $li = $("<li>").append($(this).find("a").first().clone());
3500 $ul.append($li);
3501 });
3502
3503 $("#samples").append($ul);
3504
3505}
Dirk Doughertyc3921652014-05-13 16:55:26 -07003506
3507
3508
3509/* ########################################################## */
3510/* ################### RESOURCE CARDS ##################### */
3511/* ########################################################## */
3512
3513/** Handle resource queries, collections, and grids (sections). Requires
3514 jd_tag_helpers.js and the *_unified_data.js to be loaded. */
3515
3516(function() {
3517 // Prevent the same resource from being loaded more than once per page.
3518 var addedPageResources = {};
3519
3520 $(document).ready(function() {
3521 $('.resource-widget').each(function() {
3522 initResourceWidget(this);
3523 });
3524
3525 /* Pass the line height to ellipsisfade() to adjust the height of the
3526 text container to show the max number of lines possible, without
3527 showing lines that are cut off. This works with the css ellipsis
3528 classes to fade last text line and apply an ellipsis char. */
3529
Scott Mainb16376f2014-05-21 20:35:47 -07003530 //card text currently uses 15px line height.
Dirk Doughertyc3921652014-05-13 16:55:26 -07003531 var lineHeight = 15;
3532 $('.card-info .text').ellipsisfade(lineHeight);
3533 });
3534
3535 /*
3536 Three types of resource layouts:
3537 Flow - Uses a fixed row-height flow using float left style.
3538 Carousel - Single card slideshow all same dimension absolute.
3539 Stack - Uses fixed columns and flexible element height.
3540 */
3541 function initResourceWidget(widget) {
3542 var $widget = $(widget);
3543 var isFlow = $widget.hasClass('resource-flow-layout'),
3544 isCarousel = $widget.hasClass('resource-carousel-layout'),
3545 isStack = $widget.hasClass('resource-stack-layout');
3546
3547 // find size of widget by pulling out its class name
3548 var sizeCols = 1;
3549 var m = $widget.get(0).className.match(/\bcol-(\d+)\b/);
3550 if (m) {
3551 sizeCols = parseInt(m[1], 10);
3552 }
3553
3554 var opts = {
3555 cardSizes: ($widget.data('cardsizes') || '').split(','),
3556 maxResults: parseInt($widget.data('maxresults') || '100', 10),
3557 itemsPerPage: $widget.data('itemsperpage'),
3558 sortOrder: $widget.data('sortorder'),
3559 query: $widget.data('query'),
3560 section: $widget.data('section'),
Robert Lye7eeb402014-06-03 19:35:24 -07003561 sizeCols: sizeCols,
3562 /* Added by LFL 6/6/14 */
3563 resourceStyle: $widget.data('resourcestyle') || 'card',
3564 stackSort: $widget.data('stacksort') || 'true'
Dirk Doughertyc3921652014-05-13 16:55:26 -07003565 };
3566
3567 // run the search for the set of resources to show
3568
3569 var resources = buildResourceList(opts);
3570
3571 if (isFlow) {
3572 drawResourcesFlowWidget($widget, opts, resources);
3573 } else if (isCarousel) {
3574 drawResourcesCarouselWidget($widget, opts, resources);
3575 } else if (isStack) {
smain@google.com95948b82014-06-16 19:24:25 -07003576 /* Looks like this got removed and is not used, so repurposing for the
3577 homepage style layout.
Robert Lye7eeb402014-06-03 19:35:24 -07003578 Modified by LFL 6/6/14
3579 */
3580 //var sections = buildSectionList(opts);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003581 opts['numStacks'] = $widget.data('numstacks');
Robert Lye7eeb402014-06-03 19:35:24 -07003582 drawResourcesStackWidget($widget, opts, resources/*, sections*/);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003583 }
3584 }
3585
3586 /* Initializes a Resource Carousel Widget */
3587 function drawResourcesCarouselWidget($widget, opts, resources) {
3588 $widget.empty();
3589 var plusone = true; //always show plusone on carousel
3590
3591 $widget.addClass('resource-card slideshow-container')
3592 .append($('<a>').addClass('slideshow-prev').text('Prev'))
3593 .append($('<a>').addClass('slideshow-next').text('Next'));
3594
3595 var css = { 'width': $widget.width() + 'px',
3596 'height': $widget.height() + 'px' };
3597
3598 var $ul = $('<ul>');
3599
3600 for (var i = 0; i < resources.length; ++i) {
Dirk Doughertyc3921652014-05-13 16:55:26 -07003601 var $card = $('<a>')
Robert Lye7eeb402014-06-03 19:35:24 -07003602 .attr('href', cleanUrl(resources[i].url))
Dirk Doughertyc3921652014-05-13 16:55:26 -07003603 .decorateResourceCard(resources[i],plusone);
3604
3605 $('<li>').css(css)
3606 .append($card)
3607 .appendTo($ul);
3608 }
3609
3610 $('<div>').addClass('frame')
3611 .append($ul)
3612 .appendTo($widget);
3613
3614 $widget.dacSlideshow({
3615 auto: true,
3616 btnPrev: '.slideshow-prev',
3617 btnNext: '.slideshow-next'
3618 });
3619 };
3620
Robert Lye7eeb402014-06-03 19:35:24 -07003621 /* Initializes a Resource Card Stack Widget (column-based layout)
3622 Modified by LFL 6/6/14
3623 */
Dirk Doughertyc3921652014-05-13 16:55:26 -07003624 function drawResourcesStackWidget($widget, opts, resources, sections) {
3625 // Don't empty widget, grab all items inside since they will be the first
3626 // items stacked, followed by the resource query
3627 var plusone = true; //by default show plusone on section cards
3628 var cards = $widget.find('.resource-card').detach().toArray();
3629 var numStacks = opts.numStacks || 1;
3630 var $stacks = [];
3631 var urlString;
3632
3633 for (var i = 0; i < numStacks; ++i) {
3634 $stacks[i] = $('<div>').addClass('resource-card-stack')
3635 .appendTo($widget);
3636 }
3637
3638 var sectionResources = [];
3639
3640 // Extract any subsections that are actually resource cards
Robert Lye7eeb402014-06-03 19:35:24 -07003641 if (sections) {
3642 for (var i = 0; i < sections.length; ++i) {
3643 if (!sections[i].sections || !sections[i].sections.length) {
3644 // Render it as a resource card
3645 sectionResources.push(
3646 $('<a>')
3647 .addClass('resource-card section-card')
3648 .attr('href', cleanUrl(sections[i].resource.url))
3649 .decorateResourceCard(sections[i].resource,plusone)[0]
3650 );
Dirk Doughertyc3921652014-05-13 16:55:26 -07003651
Robert Lye7eeb402014-06-03 19:35:24 -07003652 } else {
3653 cards.push(
3654 $('<div>')
3655 .addClass('resource-card section-card-menu')
3656 .decorateResourceSection(sections[i],plusone)[0]
3657 );
3658 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003659 }
3660 }
3661
3662 cards = cards.concat(sectionResources);
3663
3664 for (var i = 0; i < resources.length; ++i) {
Robert Lye7eeb402014-06-03 19:35:24 -07003665 var $card = createResourceElement(resources[i], opts);
smain@google.com95948b82014-06-16 19:24:25 -07003666
Robert Lye7eeb402014-06-03 19:35:24 -07003667 if (opts.resourceStyle.indexOf('related') > -1) {
3668 $card.addClass('related-card');
3669 }
smain@google.com95948b82014-06-16 19:24:25 -07003670
Dirk Doughertyc3921652014-05-13 16:55:26 -07003671 cards.push($card[0]);
3672 }
3673
Robert Lye7eeb402014-06-03 19:35:24 -07003674 if (opts.stackSort != 'false') {
3675 for (var i = 0; i < cards.length; ++i) {
3676 // Find the stack with the shortest height, but give preference to
3677 // left to right order.
3678 var minHeight = $stacks[0].height();
3679 var minIndex = 0;
Dirk Doughertyc3921652014-05-13 16:55:26 -07003680
Robert Lye7eeb402014-06-03 19:35:24 -07003681 for (var j = 1; j < numStacks; ++j) {
3682 var height = $stacks[j].height();
3683 if (height < minHeight - 45) {
3684 minHeight = height;
3685 minIndex = j;
3686 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003687 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003688
Robert Lye7eeb402014-06-03 19:35:24 -07003689 $stacks[minIndex].append($(cards[i]));
3690 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003691 }
3692
3693 };
smain@google.com95948b82014-06-16 19:24:25 -07003694
3695 /*
Robert Lye7eeb402014-06-03 19:35:24 -07003696 Create a resource card using the given resource object and a list of html
3697 configured options. Returns a jquery object containing the element.
3698 */
smain@google.com95948b82014-06-16 19:24:25 -07003699 function createResourceElement(resource, opts, plusone) {
Robert Lye7eeb402014-06-03 19:35:24 -07003700 var $el;
smain@google.com95948b82014-06-16 19:24:25 -07003701
Robert Lye7eeb402014-06-03 19:35:24 -07003702 // The difference here is that generic cards are not entirely clickable
3703 // so its a div instead of an a tag, also the generic one is not given
3704 // the resource-card class so it appears with a transparent background
3705 // and can be styled in whatever way the css setup.
3706 if (opts.resourceStyle == 'generic') {
3707 $el = $('<div>')
3708 .addClass('resource')
3709 .attr('href', cleanUrl(resource.url))
3710 .decorateResource(resource, opts);
3711 } else {
3712 var cls = 'resource resource-card';
smain@google.com95948b82014-06-16 19:24:25 -07003713
Robert Lye7eeb402014-06-03 19:35:24 -07003714 $el = $('<a>')
3715 .addClass(cls)
3716 .attr('href', cleanUrl(resource.url))
3717 .decorateResourceCard(resource, plusone);
3718 }
smain@google.com95948b82014-06-16 19:24:25 -07003719
Robert Lye7eeb402014-06-03 19:35:24 -07003720 return $el;
3721 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003722
3723 /* Initializes a flow widget, see distribute.scss for generating accompanying css */
3724 function drawResourcesFlowWidget($widget, opts, resources) {
3725 $widget.empty();
3726 var cardSizes = opts.cardSizes || ['6x6'];
3727 var i = 0, j = 0;
3728 var plusone = true; // by default show plusone on resource cards
3729
3730 while (i < resources.length) {
3731 var cardSize = cardSizes[j++ % cardSizes.length];
3732 cardSize = cardSize.replace(/^\s+|\s+$/,'');
Dirk Doughertyc3921652014-05-13 16:55:26 -07003733 // Some card sizes do not get a plusone button, such as where space is constrained
3734 // or for cards commonly embedded in docs (to improve overall page speed).
3735 plusone = !((cardSize == "6x2") || (cardSize == "6x3") ||
3736 (cardSize == "9x2") || (cardSize == "9x3") ||
3737 (cardSize == "12x2") || (cardSize == "12x3"));
3738
3739 // A stack has a third dimension which is the number of stacked items
3740 var isStack = cardSize.match(/(\d+)x(\d+)x(\d+)/);
3741 var stackCount = 0;
3742 var $stackDiv = null;
3743
3744 if (isStack) {
3745 // Create a stack container which should have the dimensions defined
3746 // by the product of the items inside.
3747 $stackDiv = $('<div>').addClass('resource-card-stack resource-card-' + isStack[1]
3748 + 'x' + isStack[2] * isStack[3]) .appendTo($widget);
3749 }
3750
3751 // Build each stack item or just a single item
3752 do {
3753 var resource = resources[i];
Dirk Doughertyc3921652014-05-13 16:55:26 -07003754
Robert Lye7eeb402014-06-03 19:35:24 -07003755 var $card = createResourceElement(resources[i], opts, plusone);
smain@google.com95948b82014-06-16 19:24:25 -07003756
3757 $card.addClass('resource-card-' + cardSize +
Robert Lye7eeb402014-06-03 19:35:24 -07003758 ' resource-card-' + resource.type);
smain@google.com95948b82014-06-16 19:24:25 -07003759
Dirk Doughertyc3921652014-05-13 16:55:26 -07003760 if (isStack) {
3761 $card.addClass('resource-card-' + isStack[1] + 'x' + isStack[2]);
3762 if (++stackCount == parseInt(isStack[3])) {
3763 $card.addClass('resource-card-row-stack-last');
3764 stackCount = 0;
3765 }
3766 } else {
3767 stackCount = 0;
3768 }
3769
Robert Lye7eeb402014-06-03 19:35:24 -07003770 $card.appendTo($stackDiv || $widget);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003771
3772 } while (++i < resources.length && stackCount > 0);
3773 }
3774 }
3775
3776 /* Build a site map of resources using a section as a root. */
3777 function buildSectionList(opts) {
3778 if (opts.section && SECTION_BY_ID[opts.section]) {
3779 return SECTION_BY_ID[opts.section].sections || [];
3780 }
3781 return [];
3782 }
3783
3784 function buildResourceList(opts) {
3785 var maxResults = opts.maxResults || 100;
3786
3787 var query = opts.query || '';
3788 var expressions = parseResourceQuery(query);
3789 var addedResourceIndices = {};
3790 var results = [];
3791
3792 for (var i = 0; i < expressions.length; i++) {
3793 var clauses = expressions[i];
3794
3795 // build initial set of resources from first clause
3796 var firstClause = clauses[0];
3797 var resources = [];
3798 switch (firstClause.attr) {
3799 case 'type':
3800 resources = ALL_RESOURCES_BY_TYPE[firstClause.value];
3801 break;
3802 case 'lang':
3803 resources = ALL_RESOURCES_BY_LANG[firstClause.value];
3804 break;
3805 case 'tag':
3806 resources = ALL_RESOURCES_BY_TAG[firstClause.value];
3807 break;
3808 case 'collection':
3809 var urls = RESOURCE_COLLECTIONS[firstClause.value].resources || [];
3810 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3811 break;
3812 case 'section':
3813 var urls = SITE_MAP[firstClause.value].sections || [];
3814 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3815 break;
3816 }
3817 // console.log(firstClause.attr + ':' + firstClause.value);
3818 resources = resources || [];
3819
3820 // use additional clauses to filter corpus
3821 if (clauses.length > 1) {
3822 var otherClauses = clauses.slice(1);
3823 resources = resources.filter(getResourceMatchesClausesFilter(otherClauses));
3824 }
3825
3826 // filter out resources already added
3827 if (i > 1) {
3828 resources = resources.filter(getResourceNotAlreadyAddedFilter(addedResourceIndices));
3829 }
3830
3831 // add to list of already added indices
3832 for (var j = 0; j < resources.length; j++) {
3833 // console.log(resources[j].title);
3834 addedResourceIndices[resources[j].index] = 1;
3835 }
3836
3837 // concat to final results list
3838 results = results.concat(resources);
3839 }
3840
3841 if (opts.sortOrder && results.length) {
3842 var attr = opts.sortOrder;
3843
3844 if (opts.sortOrder == 'random') {
3845 var i = results.length, j, temp;
3846 while (--i) {
3847 j = Math.floor(Math.random() * (i + 1));
3848 temp = results[i];
3849 results[i] = results[j];
3850 results[j] = temp;
3851 }
3852 } else {
3853 var desc = attr.charAt(0) == '-';
3854 if (desc) {
3855 attr = attr.substring(1);
3856 }
3857 results = results.sort(function(x,y) {
3858 return (desc ? -1 : 1) * (parseInt(x[attr], 10) - parseInt(y[attr], 10));
3859 });
3860 }
3861 }
3862
3863 results = results.filter(getResourceNotAlreadyAddedFilter(addedPageResources));
3864 results = results.slice(0, maxResults);
3865
3866 for (var j = 0; j < results.length; ++j) {
3867 addedPageResources[results[j].index] = 1;
3868 }
3869
3870 return results;
3871 }
3872
3873
3874 function getResourceNotAlreadyAddedFilter(addedResourceIndices) {
3875 return function(resource) {
3876 return !addedResourceIndices[resource.index];
3877 };
3878 }
3879
3880
3881 function getResourceMatchesClausesFilter(clauses) {
3882 return function(resource) {
3883 return doesResourceMatchClauses(resource, clauses);
3884 };
3885 }
3886
3887
3888 function doesResourceMatchClauses(resource, clauses) {
3889 for (var i = 0; i < clauses.length; i++) {
3890 var map;
3891 switch (clauses[i].attr) {
3892 case 'type':
3893 map = IS_RESOURCE_OF_TYPE[clauses[i].value];
3894 break;
3895 case 'lang':
3896 map = IS_RESOURCE_IN_LANG[clauses[i].value];
3897 break;
3898 case 'tag':
3899 map = IS_RESOURCE_TAGGED[clauses[i].value];
3900 break;
3901 }
3902
3903 if (!map || (!!clauses[i].negative ? map[resource.index] : !map[resource.index])) {
3904 return clauses[i].negative;
3905 }
3906 }
3907 return true;
3908 }
smain@google.com95948b82014-06-16 19:24:25 -07003909
Robert Lye7eeb402014-06-03 19:35:24 -07003910 function cleanUrl(url)
3911 {
3912 if (url && url.indexOf('//') === -1) {
3913 url = toRoot + url;
3914 }
smain@google.com95948b82014-06-16 19:24:25 -07003915
Robert Lye7eeb402014-06-03 19:35:24 -07003916 return url;
3917 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003918
3919
3920 function parseResourceQuery(query) {
3921 // Parse query into array of expressions (expression e.g. 'tag:foo + type:video')
3922 var expressions = [];
3923 var expressionStrs = query.split(',') || [];
3924 for (var i = 0; i < expressionStrs.length; i++) {
3925 var expr = expressionStrs[i] || '';
3926
3927 // Break expression into clauses (clause e.g. 'tag:foo')
3928 var clauses = [];
3929 var clauseStrs = expr.split(/(?=[\+\-])/);
3930 for (var j = 0; j < clauseStrs.length; j++) {
3931 var clauseStr = clauseStrs[j] || '';
3932
3933 // Get attribute and value from clause (e.g. attribute='tag', value='foo')
3934 var parts = clauseStr.split(':');
3935 var clause = {};
3936
3937 clause.attr = parts[0].replace(/^\s+|\s+$/g,'');
3938 if (clause.attr) {
3939 if (clause.attr.charAt(0) == '+') {
3940 clause.attr = clause.attr.substring(1);
3941 } else if (clause.attr.charAt(0) == '-') {
3942 clause.negative = true;
3943 clause.attr = clause.attr.substring(1);
3944 }
3945 }
3946
3947 if (parts.length > 1) {
3948 clause.value = parts[1].replace(/^\s+|\s+$/g,'');
3949 }
3950
3951 clauses.push(clause);
3952 }
3953
3954 if (!clauses.length) {
3955 continue;
3956 }
3957
3958 expressions.push(clauses);
3959 }
3960
3961 return expressions;
3962 }
3963})();
3964
3965(function($) {
Robert Lye7eeb402014-06-03 19:35:24 -07003966
smain@google.com95948b82014-06-16 19:24:25 -07003967 /*
Robert Lye7eeb402014-06-03 19:35:24 -07003968 Utility method for creating dom for the description area of a card.
3969 Used in decorateResourceCard and decorateResource.
3970 */
3971 function buildResourceCardDescription(resource, plusone) {
3972 var $description = $('<div>').addClass('description ellipsis');
smain@google.com95948b82014-06-16 19:24:25 -07003973
Robert Lye7eeb402014-06-03 19:35:24 -07003974 $description.append($('<div>').addClass('text').html(resource.summary));
smain@google.com95948b82014-06-16 19:24:25 -07003975
Robert Lye7eeb402014-06-03 19:35:24 -07003976 if (resource.cta) {
3977 $description.append($('<a>').addClass('cta').html(resource.cta));
3978 }
smain@google.com95948b82014-06-16 19:24:25 -07003979
Robert Lye7eeb402014-06-03 19:35:24 -07003980 if (plusone) {
smain@google.com95948b82014-06-16 19:24:25 -07003981 var plusurl = resource.url.indexOf("//") > -1 ? resource.url :
Robert Lye7eeb402014-06-03 19:35:24 -07003982 "//developer.android.com/" + resource.url;
smain@google.com95948b82014-06-16 19:24:25 -07003983
Robert Lye7eeb402014-06-03 19:35:24 -07003984 $description.append($('<div>').addClass('util')
3985 .append($('<div>').addClass('g-plusone')
3986 .attr('data-size', 'small')
3987 .attr('data-align', 'right')
3988 .attr('data-href', plusurl)));
3989 }
smain@google.com95948b82014-06-16 19:24:25 -07003990
Robert Lye7eeb402014-06-03 19:35:24 -07003991 return $description;
3992 }
smain@google.com95948b82014-06-16 19:24:25 -07003993
3994
Dirk Doughertyc3921652014-05-13 16:55:26 -07003995 /* Simple jquery function to create dom for a standard resource card */
3996 $.fn.decorateResourceCard = function(resource,plusone) {
3997 var section = resource.group || resource.type;
smain@google.com95948b82014-06-16 19:24:25 -07003998 var imgUrl = resource.image ||
Robert Lye7eeb402014-06-03 19:35:24 -07003999 'assets/images/resource-card-default-android.jpg';
smain@google.com95948b82014-06-16 19:24:25 -07004000
Robert Lye7eeb402014-06-03 19:35:24 -07004001 if (imgUrl.indexOf('//') === -1) {
4002 imgUrl = toRoot + imgUrl;
Dirk Doughertyc3921652014-05-13 16:55:26 -07004003 }
Robert Lye7eeb402014-06-03 19:35:24 -07004004
4005 $('<div>').addClass('card-bg')
smain@google.com95948b82014-06-16 19:24:25 -07004006 .css('background-image', 'url(' + (imgUrl || toRoot +
Robert Lye7eeb402014-06-03 19:35:24 -07004007 'assets/images/resource-card-default-android.jpg') + ')')
Dirk Doughertyc3921652014-05-13 16:55:26 -07004008 .appendTo(this);
smain@google.com95948b82014-06-16 19:24:25 -07004009
Robert Lye7eeb402014-06-03 19:35:24 -07004010 $('<div>').addClass('card-info' + (!resource.summary ? ' empty-desc' : ''))
4011 .append($('<div>').addClass('section').text(section))
4012 .append($('<div>').addClass('title').html(resource.title))
4013 .append(buildResourceCardDescription(resource, plusone))
4014 .appendTo(this);
Dirk Doughertyc3921652014-05-13 16:55:26 -07004015
4016 return this;
4017 };
4018
4019 /* Simple jquery function to create dom for a resource section card (menu) */
4020 $.fn.decorateResourceSection = function(section,plusone) {
4021 var resource = section.resource;
4022 //keep url clean for matching and offline mode handling
4023 var urlPrefix = resource.image.indexOf("//") > -1 ? "" : toRoot;
4024 var $base = $('<a>')
4025 .addClass('card-bg')
4026 .attr('href', resource.url)
4027 .append($('<div>').addClass('card-section-icon')
4028 .append($('<div>').addClass('icon'))
4029 .append($('<div>').addClass('section').html(resource.title)))
4030 .appendTo(this);
4031
4032 var $cardInfo = $('<div>').addClass('card-info').appendTo(this);
4033
4034 if (section.sections && section.sections.length) {
4035 // Recurse the section sub-tree to find a resource image.
4036 var stack = [section];
4037
4038 while (stack.length) {
4039 if (stack[0].resource.image) {
4040 $base.css('background-image', 'url(' + urlPrefix + stack[0].resource.image + ')');
4041 break;
4042 }
4043
4044 if (stack[0].sections) {
4045 stack = stack.concat(stack[0].sections);
4046 }
4047
4048 stack.shift();
4049 }
4050
4051 var $ul = $('<ul>')
4052 .appendTo($cardInfo);
4053
4054 var max = section.sections.length > 3 ? 3 : section.sections.length;
4055
4056 for (var i = 0; i < max; ++i) {
4057
4058 var subResource = section.sections[i];
4059 if (!plusone) {
4060 $('<li>')
4061 .append($('<a>').attr('href', subResource.url)
4062 .append($('<div>').addClass('title').html(subResource.title))
4063 .append($('<div>').addClass('description ellipsis')
4064 .append($('<div>').addClass('text').html(subResource.summary))
4065 .append($('<div>').addClass('util'))))
4066 .appendTo($ul);
4067 } else {
4068 $('<li>')
4069 .append($('<a>').attr('href', subResource.url)
4070 .append($('<div>').addClass('title').html(subResource.title))
4071 .append($('<div>').addClass('description ellipsis')
4072 .append($('<div>').addClass('text').html(subResource.summary))
4073 .append($('<div>').addClass('util')
4074 .append($('<div>').addClass('g-plusone')
4075 .attr('data-size', 'small')
4076 .attr('data-align', 'right')
4077 .attr('data-href', resource.url)))))
4078 .appendTo($ul);
4079 }
4080 }
4081
4082 // Add a more row
4083 if (max < section.sections.length) {
4084 $('<li>')
4085 .append($('<a>').attr('href', resource.url)
4086 .append($('<div>')
4087 .addClass('title')
4088 .text('More')))
4089 .appendTo($ul);
4090 }
4091 } else {
4092 // No sub-resources, just render description?
4093 }
4094
4095 return this;
4096 };
smain@google.com95948b82014-06-16 19:24:25 -07004097
4098
4099
4100
Robert Lye7eeb402014-06-03 19:35:24 -07004101 /* Render other types of resource styles that are not cards. */
4102 $.fn.decorateResource = function(resource, opts) {
smain@google.com95948b82014-06-16 19:24:25 -07004103 var imgUrl = resource.image ||
Robert Lye7eeb402014-06-03 19:35:24 -07004104 'assets/images/resource-card-default-android.jpg';
4105 var linkUrl = resource.url;
smain@google.com95948b82014-06-16 19:24:25 -07004106
Robert Lye7eeb402014-06-03 19:35:24 -07004107 if (imgUrl.indexOf('//') === -1) {
4108 imgUrl = toRoot + imgUrl;
4109 }
smain@google.com95948b82014-06-16 19:24:25 -07004110
Robert Lye7eeb402014-06-03 19:35:24 -07004111 if (linkUrl && linkUrl.indexOf('//') === -1) {
4112 linkUrl = toRoot + linkUrl;
4113 }
4114
4115 $(this).append(
4116 $('<div>').addClass('image')
4117 .css('background-image', 'url(' + imgUrl + ')'),
4118 $('<div>').addClass('info').append(
4119 $('<h4>').addClass('title').html(resource.title),
4120 $('<p>').addClass('summary').html(resource.summary),
4121 $('<a>').attr('href', linkUrl).addClass('cta').html('Learn More')
4122 )
4123 );
4124
4125 return this;
4126 };
Dirk Doughertyc3921652014-05-13 16:55:26 -07004127})(jQuery);
Robert Lye7eeb402014-06-03 19:35:24 -07004128
4129
Dirk Doughertyc3921652014-05-13 16:55:26 -07004130/* Calculate the vertical area remaining */
4131(function($) {
4132 $.fn.ellipsisfade= function(lineHeight) {
4133 this.each(function() {
4134 // get element text
4135 var $this = $(this);
4136 var remainingHeight = $this.parent().parent().height();
4137 $this.parent().siblings().each(function ()
smain@google.comc91ecb72014-06-23 10:22:23 -07004138 {
smain@google.comcda1a9a2014-06-19 17:07:46 -07004139 if ($(this).is(":visible")) {
4140 var h = $(this).height();
4141 remainingHeight = remainingHeight - h;
4142 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07004143 });
4144
4145 adjustedRemainingHeight = ((remainingHeight)/lineHeight>>0)*lineHeight
4146 $this.parent().css({'height': adjustedRemainingHeight});
4147 $this.css({'height': "auto"});
4148 });
4149
4150 return this;
4151 };
4152}) (jQuery);
Robert Lye7eeb402014-06-03 19:35:24 -07004153
4154/*
4155 Fullscreen Carousel
smain@google.com95948b82014-06-16 19:24:25 -07004156
Robert Lye7eeb402014-06-03 19:35:24 -07004157 The following allows for an area at the top of the page that takes over the
smain@google.com95948b82014-06-16 19:24:25 -07004158 entire browser height except for its top offset and an optional bottom
Robert Lye7eeb402014-06-03 19:35:24 -07004159 padding specified as a data attribute.
smain@google.com95948b82014-06-16 19:24:25 -07004160
Robert Lye7eeb402014-06-03 19:35:24 -07004161 HTML:
smain@google.com95948b82014-06-16 19:24:25 -07004162
Robert Lye7eeb402014-06-03 19:35:24 -07004163 <div class="fullscreen-carousel">
4164 <div class="fullscreen-carousel-content">
4165 <!-- content here -->
4166 </div>
4167 <div class="fullscreen-carousel-content">
4168 <!-- content here -->
4169 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004170
Robert Lye7eeb402014-06-03 19:35:24 -07004171 etc ...
smain@google.com95948b82014-06-16 19:24:25 -07004172
Robert Lye7eeb402014-06-03 19:35:24 -07004173 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004174
Robert Lye7eeb402014-06-03 19:35:24 -07004175 Control over how the carousel takes over the screen can mostly be defined in
4176 a css file. Setting min-height on the .fullscreen-carousel-content elements
smain@google.com95948b82014-06-16 19:24:25 -07004177 will prevent them from shrinking to far vertically when the browser is very
Robert Lye7eeb402014-06-03 19:35:24 -07004178 short, and setting max-height on the .fullscreen-carousel itself will prevent
smain@google.com95948b82014-06-16 19:24:25 -07004179 the area from becoming to long in the case that the browser is stretched very
Robert Lye7eeb402014-06-03 19:35:24 -07004180 tall.
smain@google.com95948b82014-06-16 19:24:25 -07004181
Robert Lye7eeb402014-06-03 19:35:24 -07004182 There is limited functionality for having multiple sections since that request
4183 was removed, but it is possible to add .next-arrow and .prev-arrow elements to
4184 scroll between multiple content areas.
4185*/
4186
4187(function() {
4188 $(document).ready(function() {
4189 $('.fullscreen-carousel').each(function() {
4190 initWidget(this);
4191 });
4192 });
4193
4194 function initWidget(widget) {
4195 var $widget = $(widget);
smain@google.com95948b82014-06-16 19:24:25 -07004196
Robert Lye7eeb402014-06-03 19:35:24 -07004197 var topOffset = $widget.offset().top;
4198 var padBottom = parseInt($widget.data('paddingbottom')) || 0;
4199 var maxHeight = 0;
4200 var minHeight = 0;
4201 var $content = $widget.find('.fullscreen-carousel-content');
4202 var $nextArrow = $widget.find('.next-arrow');
4203 var $prevArrow = $widget.find('.prev-arrow');
4204 var $curSection = $($content[0]);
smain@google.com95948b82014-06-16 19:24:25 -07004205
Robert Lye7eeb402014-06-03 19:35:24 -07004206 if ($content.length <= 1) {
4207 $nextArrow.hide();
4208 $prevArrow.hide();
4209 } else {
4210 $nextArrow.click(function() {
4211 var index = ($content.index($curSection) + 1);
4212 $curSection.hide();
4213 $curSection = $($content[index >= $content.length ? 0 : index]);
4214 $curSection.show();
4215 });
smain@google.com95948b82014-06-16 19:24:25 -07004216
Robert Lye7eeb402014-06-03 19:35:24 -07004217 $prevArrow.click(function() {
4218 var index = ($content.index($curSection) - 1);
4219 $curSection.hide();
4220 $curSection = $($content[index < 0 ? $content.length - 1 : 0]);
4221 $curSection.show();
4222 });
4223 }
4224
4225 // Just hide all content sections except first.
4226 $content.each(function(index) {
4227 if ($(this).height() > minHeight) minHeight = $(this).height();
4228 $(this).css({position: 'absolute', display: index > 0 ? 'none' : ''});
4229 });
4230
4231 // Register for changes to window size, and trigger.
4232 $(window).resize(resizeWidget);
4233 resizeWidget();
4234
4235 function resizeWidget() {
4236 var height = $(window).height() - topOffset - padBottom;
4237 $widget.width($(window).width());
smain@google.com95948b82014-06-16 19:24:25 -07004238 $widget.height(height < minHeight ? minHeight :
Robert Lye7eeb402014-06-03 19:35:24 -07004239 (maxHeight && height > maxHeight ? maxHeight : height));
4240 }
smain@google.com95948b82014-06-16 19:24:25 -07004241 }
Robert Lye7eeb402014-06-03 19:35:24 -07004242})();
4243
4244
4245
4246
4247
4248/*
4249 Tab Carousel
smain@google.com95948b82014-06-16 19:24:25 -07004250
Robert Lye7eeb402014-06-03 19:35:24 -07004251 The following allows tab widgets to be installed via the html below. Each
4252 tab content section should have a data-tab attribute matching one of the
4253 nav items'. Also each tab content section should have a width matching the
4254 tab carousel.
smain@google.com95948b82014-06-16 19:24:25 -07004255
Robert Lye7eeb402014-06-03 19:35:24 -07004256 HTML:
smain@google.com95948b82014-06-16 19:24:25 -07004257
Robert Lye7eeb402014-06-03 19:35:24 -07004258 <div class="tab-carousel">
4259 <ul class="tab-nav">
4260 <li><a href="#" data-tab="handsets">Handsets</a>
4261 <li><a href="#" data-tab="wearable">Wearable</a>
4262 <li><a href="#" data-tab="tv">TV</a>
4263 </ul>
smain@google.com95948b82014-06-16 19:24:25 -07004264
Robert Lye7eeb402014-06-03 19:35:24 -07004265 <div class="tab-carousel-content">
4266 <div data-tab="handsets">
4267 <!--Full width content here-->
4268 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004269
Robert Lye7eeb402014-06-03 19:35:24 -07004270 <div data-tab="wearable">
4271 <!--Full width content here-->
4272 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004273
Robert Lye7eeb402014-06-03 19:35:24 -07004274 <div data-tab="tv">
4275 <!--Full width content here-->
4276 </div>
4277 </div>
4278 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004279
Robert Lye7eeb402014-06-03 19:35:24 -07004280*/
4281(function() {
4282 $(document).ready(function() {
4283 $('.tab-carousel').each(function() {
4284 initWidget(this);
4285 });
4286 });
4287
4288 function initWidget(widget) {
4289 var $widget = $(widget);
4290 var $nav = $widget.find('.tab-nav');
4291 var $anchors = $nav.find('[data-tab]');
4292 var $li = $nav.find('li');
4293 var $contentContainer = $widget.find('.tab-carousel-content');
4294 var $tabs = $contentContainer.find('[data-tab]');
4295 var $curTab = $($tabs[0]); // Current tab is first tab.
4296 var width = $widget.width();
4297
4298 // Setup nav interactivity.
4299 $anchors.click(function(evt) {
4300 evt.preventDefault();
4301 var query = '[data-tab=' + $(this).data('tab') + ']';
smain@google.com95948b82014-06-16 19:24:25 -07004302 transitionWidget($tabs.filter(query));
Robert Lye7eeb402014-06-03 19:35:24 -07004303 });
smain@google.com95948b82014-06-16 19:24:25 -07004304
Robert Lye7eeb402014-06-03 19:35:24 -07004305 // Add highlight for navigation on first item.
4306 var $highlight = $('<div>').addClass('highlight')
4307 .css({left:$li.position().left + 'px', width:$li.outerWidth() + 'px'})
4308 .appendTo($nav);
smain@google.com95948b82014-06-16 19:24:25 -07004309
Robert Lye7eeb402014-06-03 19:35:24 -07004310 // Store height since we will change contents to absolute.
4311 $contentContainer.height($contentContainer.height());
smain@google.com95948b82014-06-16 19:24:25 -07004312
Robert Lye7eeb402014-06-03 19:35:24 -07004313 // Absolutely position tabs so they're ready for transition.
4314 $tabs.each(function(index) {
4315 $(this).css({position: 'absolute', left: index > 0 ? width + 'px' : '0'});
4316 });
smain@google.com95948b82014-06-16 19:24:25 -07004317
Robert Lye7eeb402014-06-03 19:35:24 -07004318 function transitionWidget($toTab) {
4319 if (!$curTab.is($toTab)) {
4320 var curIndex = $tabs.index($curTab[0]);
4321 var toIndex = $tabs.index($toTab[0]);
4322 var dir = toIndex > curIndex ? 1 : -1;
smain@google.com95948b82014-06-16 19:24:25 -07004323
Robert Lye7eeb402014-06-03 19:35:24 -07004324 // Animate content sections.
4325 $toTab.css({left:(width * dir) + 'px'});
4326 $curTab.animate({left:(width * -dir) + 'px'});
4327 $toTab.animate({left:'0'});
smain@google.com95948b82014-06-16 19:24:25 -07004328
Robert Lye7eeb402014-06-03 19:35:24 -07004329 // Animate navigation highlight.
smain@google.com95948b82014-06-16 19:24:25 -07004330 $highlight.animate({left:$($li[toIndex]).position().left + 'px',
Robert Lye7eeb402014-06-03 19:35:24 -07004331 width:$($li[toIndex]).outerWidth() + 'px'})
smain@google.com95948b82014-06-16 19:24:25 -07004332
Robert Lye7eeb402014-06-03 19:35:24 -07004333 // Store new current section.
4334 $curTab = $toTab;
4335 }
4336 }
smain@google.com95948b82014-06-16 19:24:25 -07004337 }
Dirk Doughertyb87e3002014-11-18 19:34:34 -08004338})();