blob: 7f4be4e16e0234a204653e54f15b4c87ddf74f76 [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");
Dirk Dougherty529a1072014-12-15 15:10:06 -0800237 } else if (secondFrag == "analyze") {
238 $("#nav-x li.analyze a").addClass("selected");
Dirk Doughertyc3921652014-05-13 16:55:26 -0700239 } else if (secondFrag == "tools") {
240 $("#nav-x li.disttools a").addClass("selected");
241 } else if (secondFrag == "stories") {
242 $("#nav-x li.stories a").addClass("selected");
243 } else if (secondFrag == "essentials") {
244 $("#nav-x li.essentials a").addClass("selected");
245 } else if (secondFrag == "googleplay") {
246 $("#nav-x li.googleplay a").addClass("selected");
247 }
248 } else if ($("body").hasClass("about")) {
249 $("#sticky-header").addClass("about");
Scott Mainb16376f2014-05-21 20:35:47 -0700250 }
Scott Mainac2aef52013-02-12 14:15:23 -0800251
Scott Mainf6145542013-04-01 16:38:11 -0700252 // set global variable so we can highlight the sidenav a bit later (such as for google reference)
253 // and highlight the sidenav
254 mPagePath = pagePath;
255 highlightSidenav();
Dirk Doughertyc3921652014-05-13 16:55:26 -0700256 buildBreadcrumbs();
Scott Mainac2aef52013-02-12 14:15:23 -0800257
Scott Mainf6145542013-04-01 16:38:11 -0700258 // set up prev/next links if they exist
Scott Maine4d8f1b2012-06-21 18:03:05 -0700259 var $selNavLink = $('#nav').find('a[href="' + pagePath + '"]');
Scott Main5a1123e2012-09-26 12:51:28 -0700260 var $selListItem;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700261 if ($selNavLink.length) {
Scott Mainac2aef52013-02-12 14:15:23 -0800262 $selListItem = $selNavLink.closest('li');
Scott Maine4d8f1b2012-06-21 18:03:05 -0700263
264 // set up prev links
265 var $prevLink = [];
266 var $prevListItem = $selListItem.prev('li');
Scott Main3b90aff2013-08-01 18:09:35 -0700267
Scott Maine4d8f1b2012-06-21 18:03:05 -0700268 var crossBoundaries = ($("body.design").length > 0) || ($("body.guide").length > 0) ? true :
269false; // navigate across topic boundaries only in design docs
270 if ($prevListItem.length) {
smain@google.comc91ecb72014-06-23 10:22:23 -0700271 if ($prevListItem.hasClass('nav-section') || crossBoundaries) {
Scott Main5a1123e2012-09-26 12:51:28 -0700272 // jump to last topic of previous section
273 $prevLink = $prevListItem.find('a:last');
274 } else if (!$selListItem.hasClass('nav-section')) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700275 // jump to previous topic in this section
276 $prevLink = $prevListItem.find('a:eq(0)');
277 }
278 } else {
279 // jump to this section's index page (if it exists)
280 var $parentListItem = $selListItem.parents('li');
281 $prevLink = $selListItem.parents('li').find('a');
Scott Main3b90aff2013-08-01 18:09:35 -0700282
Scott Maine4d8f1b2012-06-21 18:03:05 -0700283 // except if cross boundaries aren't allowed, and we're at the top of a section already
284 // (and there's another parent)
Scott Main3b90aff2013-08-01 18:09:35 -0700285 if (!crossBoundaries && $parentListItem.hasClass('nav-section')
Scott Maine4d8f1b2012-06-21 18:03:05 -0700286 && $selListItem.hasClass('nav-section')) {
287 $prevLink = [];
288 }
289 }
290
Scott Maine4d8f1b2012-06-21 18:03:05 -0700291 // set up next links
292 var $nextLink = [];
Scott Maine4d8f1b2012-06-21 18:03:05 -0700293 var startClass = false;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700294 var isCrossingBoundary = false;
Scott Main3b90aff2013-08-01 18:09:35 -0700295
Scott Main1a00f7f2013-10-29 11:11:19 -0700296 if ($selListItem.hasClass('nav-section') && $selListItem.children('div.empty').length == 0) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700297 // we're on an index page, jump to the first topic
Scott Mainb505ca62012-07-26 18:00:14 -0700298 $nextLink = $selListItem.find('ul:eq(0)').find('a:eq(0)');
Scott Maine4d8f1b2012-06-21 18:03:05 -0700299
300 // if there aren't any children, go to the next section (required for About pages)
301 if($nextLink.length == 0) {
302 $nextLink = $selListItem.next('li').find('a');
Scott Mainb505ca62012-07-26 18:00:14 -0700303 } else if ($('.topic-start-link').length) {
304 // as long as there's a child link and there is a "topic start link" (we're on a landing)
305 // then set the landing page "start link" text to be the first doc title
306 $('.topic-start-link').text($nextLink.text().toUpperCase());
Scott Maine4d8f1b2012-06-21 18:03:05 -0700307 }
Scott Main3b90aff2013-08-01 18:09:35 -0700308
Scott Main5a1123e2012-09-26 12:51:28 -0700309 // If the selected page has a description, then it's a class or article homepage
310 if ($selListItem.find('a[description]').length) {
311 // this means we're on a class landing page
Scott Maine4d8f1b2012-06-21 18:03:05 -0700312 startClass = true;
313 }
314 } else {
315 // jump to the next topic in this section (if it exists)
316 $nextLink = $selListItem.next('li').find('a:eq(0)');
Scott Main1a00f7f2013-10-29 11:11:19 -0700317 if ($nextLink.length == 0) {
Scott Main5a1123e2012-09-26 12:51:28 -0700318 isCrossingBoundary = true;
319 // no more topics in this section, jump to the first topic in the next section
smain@google.comabf34112014-06-23 11:39:02 -0700320 $nextLink = $selListItem.parents('li:eq(0)').next('li').find('a:eq(0)');
Scott Main5a1123e2012-09-26 12:51:28 -0700321 if (!$nextLink.length) { // Go up another layer to look for next page (lesson > class > course)
322 $nextLink = $selListItem.parents('li:eq(1)').next('li.nav-section').find('a:eq(0)');
Scott Main1a00f7f2013-10-29 11:11:19 -0700323 if ($nextLink.length == 0) {
324 // if that doesn't work, we're at the end of the list, so disable NEXT link
325 $('.next-page-link').attr('href','').addClass("disabled")
326 .click(function() { return false; });
smain@google.comc91ecb72014-06-23 10:22:23 -0700327 // and completely hide the one in the footer
328 $('.content-footer .next-page-link').hide();
Scott Main1a00f7f2013-10-29 11:11:19 -0700329 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700330 }
331 }
332 }
Scott Main5a1123e2012-09-26 12:51:28 -0700333
334 if (startClass) {
335 $('.start-class-link').attr('href', $nextLink.attr('href')).removeClass("hide");
336
Scott Main3b90aff2013-08-01 18:09:35 -0700337 // if there's no training bar (below the start button),
Scott Main5a1123e2012-09-26 12:51:28 -0700338 // then we need to add a bottom border to button
339 if (!$("#tb").length) {
340 $('.start-class-link').css({'border-bottom':'1px solid #DADADA'});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700341 }
Scott Main5a1123e2012-09-26 12:51:28 -0700342 } else if (isCrossingBoundary && !$('body.design').length) { // Design always crosses boundaries
343 $('.content-footer.next-class').show();
344 $('.next-page-link').attr('href','')
345 .removeClass("hide").addClass("disabled")
346 .click(function() { return false; });
smain@google.comc91ecb72014-06-23 10:22:23 -0700347 // and completely hide the one in the footer
348 $('.content-footer .next-page-link').hide();
Scott Main1a00f7f2013-10-29 11:11:19 -0700349 if ($nextLink.length) {
350 $('.next-class-link').attr('href',$nextLink.attr('href'))
smain@google.com5bc3a1a2014-06-17 20:02:53 -0700351 .removeClass("hide")
352 .append(": " + $nextLink.html());
Scott Main1a00f7f2013-10-29 11:11:19 -0700353 $('.next-class-link').find('.new').empty();
354 }
Scott Main5a1123e2012-09-26 12:51:28 -0700355 } else {
smain@google.com5bc3a1a2014-06-17 20:02:53 -0700356 $('.next-page-link').attr('href', $nextLink.attr('href'))
357 .removeClass("hide");
358 // for the footer link, also add the next page title
359 $('.content-footer .next-page-link').append(": " + $nextLink.html());
Scott Main5a1123e2012-09-26 12:51:28 -0700360 }
361
362 if (!startClass && $prevLink.length) {
363 var prevHref = $prevLink.attr('href');
364 if (prevHref == SITE_ROOT + 'index.html') {
365 // Don't show Previous when it leads to the homepage
366 } else {
367 $('.prev-page-link').attr('href', $prevLink.attr('href')).removeClass("hide");
368 }
Scott Main3b90aff2013-08-01 18:09:35 -0700369 }
Scott Main5a1123e2012-09-26 12:51:28 -0700370
Scott Maine4d8f1b2012-06-21 18:03:05 -0700371 }
Scott Main3b90aff2013-08-01 18:09:35 -0700372
373
374
Scott Main5a1123e2012-09-26 12:51:28 -0700375 // Set up the course landing pages for Training with class names and descriptions
376 if ($('body.trainingcourse').length) {
377 var $classLinks = $selListItem.find('ul li a').not('#nav .nav-section .nav-section ul a');
Scott Maine7d75352014-05-22 15:50:56 -0700378
379 // create an array for all the class descriptions
380 var $classDescriptions = new Array($classLinks.length);
381 var lang = getLangPref();
382 $classLinks.each(function(index) {
383 var langDescr = $(this).attr(lang + "-description");
384 if (typeof langDescr !== 'undefined' && langDescr !== false) {
385 // if there's a class description in the selected language, use that
386 $classDescriptions[index] = langDescr;
387 } else {
388 // otherwise, use the default english description
389 $classDescriptions[index] = $(this).attr("description");
390 }
391 });
Scott Main3b90aff2013-08-01 18:09:35 -0700392
Scott Main5a1123e2012-09-26 12:51:28 -0700393 var $olClasses = $('<ol class="class-list"></ol>');
394 var $liClass;
395 var $imgIcon;
396 var $h2Title;
397 var $pSummary;
398 var $olLessons;
399 var $liLesson;
400 $classLinks.each(function(index) {
401 $liClass = $('<li></li>');
402 $h2Title = $('<a class="title" href="'+$(this).attr('href')+'"><h2>' + $(this).html()+'</h2><span></span></a>');
Scott Maine7d75352014-05-22 15:50:56 -0700403 $pSummary = $('<p class="description">' + $classDescriptions[index] + '</p>');
Scott Main3b90aff2013-08-01 18:09:35 -0700404
Scott Main5a1123e2012-09-26 12:51:28 -0700405 $olLessons = $('<ol class="lesson-list"></ol>');
Scott Main3b90aff2013-08-01 18:09:35 -0700406
Scott Main5a1123e2012-09-26 12:51:28 -0700407 $lessons = $(this).closest('li').find('ul li a');
Scott Main3b90aff2013-08-01 18:09:35 -0700408
Scott Main5a1123e2012-09-26 12:51:28 -0700409 if ($lessons.length) {
Scott Main3b90aff2013-08-01 18:09:35 -0700410 $imgIcon = $('<img src="'+toRoot+'assets/images/resource-tutorial.png" '
411 + ' width="64" height="64" alt=""/>');
Scott Main5a1123e2012-09-26 12:51:28 -0700412 $lessons.each(function(index) {
413 $olLessons.append('<li><a href="'+$(this).attr('href')+'">' + $(this).html()+'</a></li>');
414 });
415 } else {
Scott Main3b90aff2013-08-01 18:09:35 -0700416 $imgIcon = $('<img src="'+toRoot+'assets/images/resource-article.png" '
417 + ' width="64" height="64" alt=""/>');
Scott Main5a1123e2012-09-26 12:51:28 -0700418 $pSummary.addClass('article');
419 }
420
421 $liClass.append($h2Title).append($imgIcon).append($pSummary).append($olLessons);
422 $olClasses.append($liClass);
423 });
424 $('.jd-descr').append($olClasses);
425 }
426
Scott Maine4d8f1b2012-06-21 18:03:05 -0700427 // Set up expand/collapse behavior
Scott Mainad08f072013-08-20 16:49:57 -0700428 initExpandableNavItems("#nav");
Scott Main3b90aff2013-08-01 18:09:35 -0700429
Scott Main3b90aff2013-08-01 18:09:35 -0700430
Scott Maine4d8f1b2012-06-21 18:03:05 -0700431 $(".scroll-pane").scroll(function(event) {
432 event.preventDefault();
433 return false;
434 });
435
436 /* Resize nav height when window height changes */
437 $(window).resize(function() {
438 if ($('#side-nav').length == 0) return;
439 var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
440 setNavBarLeftPos(); // do this even if sidenav isn't fixed because it could become fixed
441 // make sidenav behave when resizing the window and side-scolling is a concern
Scott Mainf5257812014-05-22 17:26:38 -0700442 if (sticky) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700443 if ((stylesheet.attr("disabled") == "disabled") || stylesheet.length == 0) {
444 updateSideNavPosition();
445 } else {
446 updateSidenavFullscreenWidth();
447 }
448 }
449 resizeNav();
450 });
451
452
Scott Maine4d8f1b2012-06-21 18:03:05 -0700453 var navBarLeftPos;
454 if ($('#devdoc-nav').length) {
455 setNavBarLeftPos();
456 }
457
458
Scott Maine4d8f1b2012-06-21 18:03:05 -0700459 // Set up play-on-hover <video> tags.
460 $('video.play-on-hover').bind('click', function(){
461 $(this).get(0).load(); // in case the video isn't seekable
462 $(this).get(0).play();
463 });
464
465 // Set up tooltips
466 var TOOLTIP_MARGIN = 10;
Scott Maindb3678b2012-10-23 14:13:41 -0700467 $('acronym,.tooltip-link').each(function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700468 var $target = $(this);
469 var $tooltip = $('<div>')
470 .addClass('tooltip-box')
Scott Maindb3678b2012-10-23 14:13:41 -0700471 .append($target.attr('title'))
Scott Maine4d8f1b2012-06-21 18:03:05 -0700472 .hide()
473 .appendTo('body');
474 $target.removeAttr('title');
475
476 $target.hover(function() {
477 // in
478 var targetRect = $target.offset();
479 targetRect.width = $target.width();
480 targetRect.height = $target.height();
481
482 $tooltip.css({
483 left: targetRect.left,
484 top: targetRect.top + targetRect.height + TOOLTIP_MARGIN
485 });
486 $tooltip.addClass('below');
487 $tooltip.show();
488 }, function() {
489 // out
490 $tooltip.hide();
491 });
492 });
493
494 // Set up <h2> deeplinks
495 $('h2').click(function() {
496 var id = $(this).attr('id');
497 if (id) {
498 document.location.hash = id;
499 }
500 });
501
502 //Loads the +1 button
503 var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
504 po.src = 'https://apis.google.com/js/plusone.js';
505 var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
506
507
Scott Main3b90aff2013-08-01 18:09:35 -0700508 // Revise the sidenav widths to make room for the scrollbar
Scott Maine4d8f1b2012-06-21 18:03:05 -0700509 // which avoids the visible width from changing each time the bar appears
510 var $sidenav = $("#side-nav");
511 var sidenav_width = parseInt($sidenav.innerWidth());
Scott Main3b90aff2013-08-01 18:09:35 -0700512
Scott Maine4d8f1b2012-06-21 18:03:05 -0700513 $("#devdoc-nav #nav").css("width", sidenav_width - 4 + "px"); // 4px is scrollbar width
514
515
516 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
Scott Main3b90aff2013-08-01 18:09:35 -0700517
Scott Maine4d8f1b2012-06-21 18:03:05 -0700518 if ($(".scroll-pane").length > 1) {
519 // Check if there's a user preference for the panel heights
520 var cookieHeight = readCookie("reference_height");
521 if (cookieHeight) {
522 restoreHeight(cookieHeight);
523 }
524 }
Scott Main3b90aff2013-08-01 18:09:35 -0700525
Scott Main06f3f2c2014-05-30 11:23:00 -0700526 // Resize once loading is finished
Scott Maine4d8f1b2012-06-21 18:03:05 -0700527 resizeNav();
Scott Main06f3f2c2014-05-30 11:23:00 -0700528 // Check if there's an anchor that we need to scroll into view.
529 // A delay is needed, because some browsers do not immediately scroll down to the anchor
530 window.setTimeout(offsetScrollForSticky, 100);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700531
Scott Main015d6162013-01-29 09:01:52 -0800532 /* init the language selector based on user cookie for lang */
533 loadLangPref();
534 changeNavLang(getLangPref());
535
536 /* setup event handlers to ensure the overflow menu is visible while picking lang */
537 $("#language select")
538 .mousedown(function() {
539 $("div.morehover").addClass("hover"); })
540 .blur(function() {
541 $("div.morehover").removeClass("hover"); });
542
543 /* some global variable setup */
544 resizePackagesNav = $("#resize-packages-nav");
545 classesNav = $("#classes-nav");
546 devdocNav = $("#devdoc-nav");
547
548 var cookiePath = "";
549 if (location.href.indexOf("/reference/") != -1) {
550 cookiePath = "reference_";
551 } else if (location.href.indexOf("/guide/") != -1) {
552 cookiePath = "guide_";
553 } else if (location.href.indexOf("/tools/") != -1) {
554 cookiePath = "tools_";
555 } else if (location.href.indexOf("/training/") != -1) {
556 cookiePath = "training_";
557 } else if (location.href.indexOf("/design/") != -1) {
558 cookiePath = "design_";
559 } else if (location.href.indexOf("/distribute/") != -1) {
560 cookiePath = "distribute_";
561 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700562
smain@google.com698fff02014-11-20 20:39:33 -0800563
564 /* setup shadowbox for any videos that want it */
smain@google.comf75ee212014-11-24 09:42:59 -0800565 var $videoLinks = $("a.video-shadowbox-button, a.notice-developers-video");
smain@google.com698fff02014-11-20 20:39:33 -0800566 if ($videoLinks.length) {
567 // if there's at least one, add the shadowbox HTML to the body
568 $('body').prepend(
569'<div id="video-container">'+
570 '<div id="video-frame">'+
571 '<div class="video-close">'+
572 '<span id="icon-video-close" onclick="closeVideo()">&nbsp;</span>'+
573 '</div>'+
574 '<div id="youTubePlayer"></div>'+
575 '</div>'+
576'</div>');
577
578 // loads the IFrame Player API code asynchronously.
579 $.getScript("https://www.youtube.com/iframe_api");
580
581 $videoLinks.each(function() {
582 var videoId = $(this).attr('href').split('?v=')[1];
583 $(this).click(function(event) {
584 event.preventDefault();
585 startYouTubePlayer(videoId);
586 });
587 });
smain@google.com698fff02014-11-20 20:39:33 -0800588 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700589});
Scott Main7e447ed2013-02-19 17:22:37 -0800590// END of the onload event
Scott Maine4d8f1b2012-06-21 18:03:05 -0700591
592
smain@google.com698fff02014-11-20 20:39:33 -0800593var youTubePlayer;
594function onYouTubeIframeAPIReady() {
595}
596
smain@google.com3de83c12014-12-12 19:06:52 -0800597/* Returns the height the shadowbox video should be. It's based on the current
598 height of the "video-frame" element, which is 100% height for the window.
599 Then minus the margin so the video isn't actually the full window height. */
600function getVideoHeight() {
601 var frameHeight = $("#video-frame").height();
602 var marginTop = $("#video-frame").css('margin-top').split('px')[0];
603 return frameHeight - (marginTop * 2);
604}
605
smain@google.comd162be52015-02-05 13:27:16 -0800606var mPlayerPaused = false;
607
smain@google.com698fff02014-11-20 20:39:33 -0800608function startYouTubePlayer(videoId) {
smain@google.com3de83c12014-12-12 19:06:52 -0800609 $("#video-container").show();
610 $("#video-frame").show();
smain@google.comd162be52015-02-05 13:27:16 -0800611 mPlayerPaused = false;
smain@google.com3de83c12014-12-12 19:06:52 -0800612
613 // compute the size of the player so it's centered in window
614 var maxWidth = 940; // the width of the web site content
615 var videoAspect = .5625; // based on 1280x720 resolution
616 var maxHeight = maxWidth * videoAspect;
617 var videoHeight = getVideoHeight();
618 var videoWidth = videoHeight / videoAspect;
619 if (videoWidth > maxWidth) {
620 videoWidth = maxWidth;
621 videoHeight = maxHeight;
smain@google.com570c2212014-11-25 19:01:40 -0800622 }
smain@google.com3de83c12014-12-12 19:06:52 -0800623 $("#video-frame").css('width', videoWidth);
624
625 // check if we've already created this player
smain@google.com698fff02014-11-20 20:39:33 -0800626 if (youTubePlayer == null) {
smain@google.com3de83c12014-12-12 19:06:52 -0800627 // check if there's a start time specified
628 var idAndHash = videoId.split("#");
629 var startTime = 0;
630 if (idAndHash.length > 1) {
631 startTime = idAndHash[1].split("t=")[1] != undefined ? idAndHash[1].split("t=")[1] : 0;
632 }
633 // enable localized player
634 var lang = getLangPref();
635 var captionsOn = lang == 'en' ? 0 : 1;
636
smain@google.com698fff02014-11-20 20:39:33 -0800637 youTubePlayer = new YT.Player('youTubePlayer', {
smain@google.com3de83c12014-12-12 19:06:52 -0800638 height: videoHeight,
639 width: videoWidth,
smain@google.com570c2212014-11-25 19:01:40 -0800640 videoId: idAndHash[0],
smain@google.com481f15c2014-12-12 11:57:27 -0800641 playerVars: {start: startTime, hl: lang, cc_load_policy: captionsOn},
smain@google.com698fff02014-11-20 20:39:33 -0800642 events: {
smain@google.comf75ee212014-11-24 09:42:59 -0800643 'onReady': onPlayerReady,
644 'onStateChange': onPlayerStateChange
smain@google.com698fff02014-11-20 20:39:33 -0800645 }
646 });
647 } else {
smain@google.com3de83c12014-12-12 19:06:52 -0800648 // reset the size in case the user adjusted the window since last play
649 youTubePlayer.setSize(videoWidth, videoHeight);
smain@google.com94e0b532014-12-16 19:07:08 -0800650 // if a video different from the one already playing was requested, cue it up
smain@google.comd162be52015-02-05 13:27:16 -0800651 if (videoId != youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0]) {
smain@google.com94e0b532014-12-16 19:07:08 -0800652 youTubePlayer.cueVideoById(videoId);
653 }
smain@google.com698fff02014-11-20 20:39:33 -0800654 youTubePlayer.playVideo();
655 }
smain@google.com698fff02014-11-20 20:39:33 -0800656}
657
658function onPlayerReady(event) {
659 event.target.playVideo();
smain@google.comd162be52015-02-05 13:27:16 -0800660 mPlayerPaused = false;
smain@google.com698fff02014-11-20 20:39:33 -0800661}
662
663function closeVideo() {
664 try {
smain@google.comf75ee212014-11-24 09:42:59 -0800665 youTubePlayer.pauseVideo();
smain@google.com698fff02014-11-20 20:39:33 -0800666 } catch(e) {
smain@google.com698fff02014-11-20 20:39:33 -0800667 }
smain@google.com3de83c12014-12-12 19:06:52 -0800668 $("#video-container").fadeOut(200);
smain@google.com698fff02014-11-20 20:39:33 -0800669}
670
smain@google.comf75ee212014-11-24 09:42:59 -0800671/* Track youtube playback for analytics */
672function onPlayerStateChange(event) {
673 // Video starts, send the video ID
674 if (event.data == YT.PlayerState.PLAYING) {
smain@google.comd162be52015-02-05 13:27:16 -0800675 if (mPlayerPaused) {
676 ga('send', 'event', 'Videos', 'Resume',
677 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0]);
678 } else {
679 // track the start playing event so we know from which page the video was selected
680 ga('send', 'event', 'Videos', 'Start: ' +
681 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0],
682 'on: ' + document.location.href);
683 }
684 mPlayerPaused = false;
smain@google.comf75ee212014-11-24 09:42:59 -0800685 }
686 // Video paused, send video ID and video elapsed time
687 if (event.data == YT.PlayerState.PAUSED) {
smain@google.comd24088c2014-12-12 11:31:13 -0800688 ga('send', 'event', 'Videos', 'Paused',
smain@google.comd162be52015-02-05 13:27:16 -0800689 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0],
690 youTubePlayer.getCurrentTime());
691 mPlayerPaused = true;
smain@google.comf75ee212014-11-24 09:42:59 -0800692 }
693 // Video finished, send video ID and video elapsed time
694 if (event.data == YT.PlayerState.ENDED) {
smain@google.comd24088c2014-12-12 11:31:13 -0800695 ga('send', 'event', 'Videos', 'Finished',
smain@google.comd162be52015-02-05 13:27:16 -0800696 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0],
697 youTubePlayer.getCurrentTime());
698 mPlayerPaused = true;
smain@google.comf75ee212014-11-24 09:42:59 -0800699 }
700}
701
smain@google.com698fff02014-11-20 20:39:33 -0800702
703
Scott Mainad08f072013-08-20 16:49:57 -0700704function initExpandableNavItems(rootTag) {
705 $(rootTag + ' li.nav-section .nav-section-header').click(function() {
706 var section = $(this).closest('li.nav-section');
707 if (section.hasClass('expanded')) {
Scott Mainf0093852013-08-22 11:37:11 -0700708 /* hide me and descendants */
709 section.find('ul').slideUp(250, function() {
710 // remove 'expanded' class from my section and any children
Scott Mainad08f072013-08-20 16:49:57 -0700711 section.closest('li').removeClass('expanded');
Scott Mainf0093852013-08-22 11:37:11 -0700712 $('li.nav-section', section).removeClass('expanded');
Scott Mainad08f072013-08-20 16:49:57 -0700713 resizeNav();
714 });
715 } else {
716 /* show me */
717 // first hide all other siblings
Scott Main70557ee2013-10-30 14:47:40 -0700718 var $others = $('li.nav-section.expanded', $(this).closest('ul')).not('.sticky');
Scott Mainad08f072013-08-20 16:49:57 -0700719 $others.removeClass('expanded').children('ul').slideUp(250);
720
721 // now expand me
722 section.closest('li').addClass('expanded');
723 section.children('ul').slideDown(250, function() {
724 resizeNav();
725 });
726 }
727 });
Scott Mainf0093852013-08-22 11:37:11 -0700728
729 // Stop expand/collapse behavior when clicking on nav section links
730 // (since we're navigating away from the page)
731 // This selector captures the first instance of <a>, but not those with "#" as the href.
732 $('.nav-section-header').find('a:eq(0)').not('a[href="#"]').click(function(evt) {
733 window.location.href = $(this).attr('href');
734 return false;
735 });
Scott Mainad08f072013-08-20 16:49:57 -0700736}
737
Dirk Doughertyc3921652014-05-13 16:55:26 -0700738
739/** Create the list of breadcrumb links in the sticky header */
740function buildBreadcrumbs() {
741 var $breadcrumbUl = $("#sticky-header ul.breadcrumb");
742 // Add the secondary horizontal nav item, if provided
743 var $selectedSecondNav = $("div#nav-x ul.nav-x a.selected").clone().removeClass("selected");
744 if ($selectedSecondNav.length) {
745 $breadcrumbUl.prepend($("<li>").append($selectedSecondNav))
746 }
747 // Add the primary horizontal nav
748 var $selectedFirstNav = $("div#header-wrap ul.nav-x a.selected").clone().removeClass("selected");
749 // If there's no header nav item, use the logo link and title from alt text
750 if ($selectedFirstNav.length < 1) {
751 $selectedFirstNav = $("<a>")
752 .attr('href', $("div#header .logo a").attr('href'))
753 .text($("div#header .logo img").attr('alt'));
754 }
755 $breadcrumbUl.prepend($("<li>").append($selectedFirstNav));
756}
757
758
759
Scott Maine624b3f2013-09-12 12:56:41 -0700760/** Highlight the current page in sidenav, expanding children as appropriate */
Scott Mainf6145542013-04-01 16:38:11 -0700761function highlightSidenav() {
Scott Maine624b3f2013-09-12 12:56:41 -0700762 // if something is already highlighted, undo it. This is for dynamic navigation (Samples index)
763 if ($("ul#nav li.selected").length) {
764 unHighlightSidenav();
765 }
766 // look for URL in sidenav, including the hash
767 var $selNavLink = $('#nav').find('a[href="' + mPagePath + location.hash + '"]');
768
769 // If the selNavLink is still empty, look for it without the hash
770 if ($selNavLink.length == 0) {
771 $selNavLink = $('#nav').find('a[href="' + mPagePath + '"]');
772 }
773
Scott Mainf6145542013-04-01 16:38:11 -0700774 var $selListItem;
775 if ($selNavLink.length) {
Scott Mainf6145542013-04-01 16:38:11 -0700776 // Find this page's <li> in sidenav and set selected
777 $selListItem = $selNavLink.closest('li');
778 $selListItem.addClass('selected');
Scott Main3b90aff2013-08-01 18:09:35 -0700779
Scott Mainf6145542013-04-01 16:38:11 -0700780 // Traverse up the tree and expand all parent nav-sections
781 $selNavLink.parents('li.nav-section').each(function() {
782 $(this).addClass('expanded');
783 $(this).children('ul').show();
784 });
785 }
786}
787
Scott Maine624b3f2013-09-12 12:56:41 -0700788function unHighlightSidenav() {
789 $("ul#nav li.selected").removeClass("selected");
790 $('ul#nav li.nav-section.expanded').removeClass('expanded').children('ul').hide();
791}
Scott Maine4d8f1b2012-06-21 18:03:05 -0700792
793function toggleFullscreen(enable) {
794 var delay = 20;
795 var enabled = true;
796 var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
797 if (enable) {
798 // Currently NOT USING fullscreen; enable fullscreen
799 stylesheet.removeAttr('disabled');
800 $('#nav-swap .fullscreen').removeClass('disabled');
801 $('#devdoc-nav').css({left:''});
802 setTimeout(updateSidenavFullscreenWidth,delay); // need to wait a moment for css to switch
803 enabled = true;
804 } else {
805 // Currently USING fullscreen; disable fullscreen
806 stylesheet.attr('disabled', 'disabled');
807 $('#nav-swap .fullscreen').addClass('disabled');
808 setTimeout(updateSidenavFixedWidth,delay); // need to wait a moment for css to switch
809 enabled = false;
810 }
smain@google.com6bdcb982014-11-14 11:53:07 -0800811 writeCookie("fullscreen", enabled, null);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700812 setNavBarLeftPos();
813 resizeNav(delay);
814 updateSideNavPosition();
815 setTimeout(initSidenavHeightResize,delay);
816}
817
818
819function setNavBarLeftPos() {
820 navBarLeftPos = $('#body-content').offset().left;
821}
822
823
824function updateSideNavPosition() {
825 var newLeft = $(window).scrollLeft() - navBarLeftPos;
826 $('#devdoc-nav').css({left: -newLeft});
827 $('#devdoc-nav .totop').css({left: -(newLeft - parseInt($('#side-nav').css('margin-left')))});
828}
Scott Main3b90aff2013-08-01 18:09:35 -0700829
Scott Maine4d8f1b2012-06-21 18:03:05 -0700830// TODO: use $(document).ready instead
831function addLoadEvent(newfun) {
832 var current = window.onload;
833 if (typeof window.onload != 'function') {
834 window.onload = newfun;
835 } else {
836 window.onload = function() {
837 current();
838 newfun();
839 }
840 }
841}
842
843var agent = navigator['userAgent'].toLowerCase();
844// If a mobile phone, set flag and do mobile setup
845if ((agent.indexOf("mobile") != -1) || // android, iphone, ipod
846 (agent.indexOf("blackberry") != -1) ||
847 (agent.indexOf("webos") != -1) ||
848 (agent.indexOf("mini") != -1)) { // opera mini browsers
849 isMobile = true;
850}
851
852
Scott Main498d7102013-08-21 15:47:38 -0700853$(document).ready(function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700854 $("pre:not(.no-pretty-print)").addClass("prettyprint");
855 prettyPrint();
Scott Main498d7102013-08-21 15:47:38 -0700856});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700857
Scott Maine4d8f1b2012-06-21 18:03:05 -0700858
859
860
861/* ######### RESIZE THE SIDENAV HEIGHT ########## */
862
863function resizeNav(delay) {
864 var $nav = $("#devdoc-nav");
865 var $window = $(window);
866 var navHeight;
Scott Main3b90aff2013-08-01 18:09:35 -0700867
Scott Maine4d8f1b2012-06-21 18:03:05 -0700868 // Get the height of entire window and the total header height.
869 // Then figure out based on scroll position whether the header is visible
870 var windowHeight = $window.height();
871 var scrollTop = $window.scrollTop();
Dirk Doughertyc3921652014-05-13 16:55:26 -0700872 var headerHeight = $('#header-wrapper').outerHeight();
873 var headerVisible = scrollTop < stickyTop;
Scott Main3b90aff2013-08-01 18:09:35 -0700874
875 // get the height of space between nav and top of window.
Scott Maine4d8f1b2012-06-21 18:03:05 -0700876 // Could be either margin or top position, depending on whether the nav is fixed.
Scott Main3b90aff2013-08-01 18:09:35 -0700877 var topMargin = (parseInt($nav.css('margin-top')) || parseInt($nav.css('top'))) + 1;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700878 // add 1 for the #side-nav bottom margin
Scott Main3b90aff2013-08-01 18:09:35 -0700879
Scott Maine4d8f1b2012-06-21 18:03:05 -0700880 // Depending on whether the header is visible, set the side nav's height.
881 if (headerVisible) {
882 // The sidenav height grows as the header goes off screen
Dirk Doughertyc3921652014-05-13 16:55:26 -0700883 navHeight = windowHeight - (headerHeight - scrollTop) - topMargin;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700884 } else {
885 // Once header is off screen, the nav height is almost full window height
886 navHeight = windowHeight - topMargin;
887 }
Scott Main3b90aff2013-08-01 18:09:35 -0700888
889
890
Scott Maine4d8f1b2012-06-21 18:03:05 -0700891 $scrollPanes = $(".scroll-pane");
892 if ($scrollPanes.length > 1) {
893 // subtract the height of the api level widget and nav swapper from the available nav height
894 navHeight -= ($('#api-nav-header').outerHeight(true) + $('#nav-swap').outerHeight(true));
Scott Main3b90aff2013-08-01 18:09:35 -0700895
Scott Maine4d8f1b2012-06-21 18:03:05 -0700896 $("#swapper").css({height:navHeight + "px"});
897 if ($("#nav-tree").is(":visible")) {
898 $("#nav-tree").css({height:navHeight});
899 }
Scott Main3b90aff2013-08-01 18:09:35 -0700900
901 var classesHeight = navHeight - parseInt($("#resize-packages-nav").css("height")) - 10 + "px";
Scott Maine4d8f1b2012-06-21 18:03:05 -0700902 //subtract 10px to account for drag bar
Scott Main3b90aff2013-08-01 18:09:35 -0700903
904 // if the window becomes small enough to make the class panel height 0,
Scott Maine4d8f1b2012-06-21 18:03:05 -0700905 // then the package panel should begin to shrink
906 if (parseInt(classesHeight) <= 0) {
907 $("#resize-packages-nav").css({height:navHeight - 10}); //subtract 10px for drag bar
908 $("#packages-nav").css({height:navHeight - 10});
909 }
Scott Main3b90aff2013-08-01 18:09:35 -0700910
Scott Maine4d8f1b2012-06-21 18:03:05 -0700911 $("#classes-nav").css({'height':classesHeight, 'margin-top':'10px'});
912 $("#classes-nav .jspContainer").css({height:classesHeight});
Scott Main3b90aff2013-08-01 18:09:35 -0700913
914
Scott Maine4d8f1b2012-06-21 18:03:05 -0700915 } else {
916 $nav.height(navHeight);
917 }
Scott Main3b90aff2013-08-01 18:09:35 -0700918
Scott Maine4d8f1b2012-06-21 18:03:05 -0700919 if (delay) {
920 updateFromResize = true;
921 delayedReInitScrollbars(delay);
922 } else {
923 reInitScrollbars();
924 }
Scott Main3b90aff2013-08-01 18:09:35 -0700925
Scott Maine4d8f1b2012-06-21 18:03:05 -0700926}
927
928var updateScrollbars = false;
929var updateFromResize = false;
930
931/* Re-initialize the scrollbars to account for changed nav size.
932 * This method postpones the actual update by a 1/4 second in order to optimize the
933 * scroll performance while the header is still visible, because re-initializing the
934 * scroll panes is an intensive process.
935 */
936function delayedReInitScrollbars(delay) {
937 // If we're scheduled for an update, but have received another resize request
938 // before the scheduled resize has occured, just ignore the new request
939 // (and wait for the scheduled one).
940 if (updateScrollbars && updateFromResize) {
941 updateFromResize = false;
942 return;
943 }
Scott Main3b90aff2013-08-01 18:09:35 -0700944
Scott Maine4d8f1b2012-06-21 18:03:05 -0700945 // We're scheduled for an update and the update request came from this method's setTimeout
946 if (updateScrollbars && !updateFromResize) {
947 reInitScrollbars();
948 updateScrollbars = false;
949 } else {
950 updateScrollbars = true;
951 updateFromResize = false;
952 setTimeout('delayedReInitScrollbars()',delay);
953 }
954}
955
956/* Re-initialize the scrollbars to account for changed nav size. */
957function reInitScrollbars() {
958 var pane = $(".scroll-pane").each(function(){
959 var api = $(this).data('jsp');
960 if (!api) { setTimeout(reInitScrollbars,300); return;}
961 api.reinitialise( {verticalGutter:0} );
Scott Main3b90aff2013-08-01 18:09:35 -0700962 });
Scott Maine4d8f1b2012-06-21 18:03:05 -0700963 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
964}
965
966
967/* Resize the height of the nav panels in the reference,
968 * and save the new size to a cookie */
969function saveNavPanels() {
970 var basePath = getBaseUri(location.pathname);
971 var section = basePath.substring(1,basePath.indexOf("/",1));
smain@google.com6bdcb982014-11-14 11:53:07 -0800972 writeCookie("height", resizePackagesNav.css("height"), section);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700973}
974
975
976
977function restoreHeight(packageHeight) {
978 $("#resize-packages-nav").height(packageHeight);
979 $("#packages-nav").height(packageHeight);
980 // var classesHeight = navHeight - packageHeight;
981 // $("#classes-nav").css({height:classesHeight});
982 // $("#classes-nav .jspContainer").css({height:classesHeight});
983}
984
985
986
987/* ######### END RESIZE THE SIDENAV HEIGHT ########## */
988
989
990
991
992
Scott Main3b90aff2013-08-01 18:09:35 -0700993/** Scroll the jScrollPane to make the currently selected item visible
Scott Maine4d8f1b2012-06-21 18:03:05 -0700994 This is called when the page finished loading. */
995function scrollIntoView(nav) {
996 var $nav = $("#"+nav);
997 var element = $nav.jScrollPane({/* ...settings... */});
998 var api = element.data('jsp');
999
1000 if ($nav.is(':visible')) {
1001 var $selected = $(".selected", $nav);
Scott Mainbc729572013-07-30 18:00:51 -07001002 if ($selected.length == 0) {
1003 // If no selected item found, exit
1004 return;
1005 }
Scott Main52dd2062013-08-15 12:22:28 -07001006 // get the selected item's offset from its container nav by measuring the item's offset
1007 // relative to the document then subtract the container nav's offset relative to the document
1008 var selectedOffset = $selected.offset().top - $nav.offset().top;
1009 if (selectedOffset > $nav.height() * .8) { // multiply nav height by .8 so we move up the item
1010 // if it's more than 80% down the nav
1011 // scroll the item up by an amount equal to 80% the container nav's height
1012 api.scrollTo(0, selectedOffset - ($nav.height() * .8), false);
Scott Maine4d8f1b2012-06-21 18:03:05 -07001013 }
1014 }
1015}
1016
1017
1018
1019
1020
1021
1022/* Show popup dialogs */
1023function showDialog(id) {
1024 $dialog = $("#"+id);
1025 $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>');
1026 $dialog.wrapInner('<div/>');
1027 $dialog.removeClass("hide");
1028}
1029
1030
1031
1032
1033
1034/* ######### COOKIES! ########## */
1035
1036function readCookie(cookie) {
1037 var myCookie = cookie_namespace+"_"+cookie+"=";
1038 if (document.cookie) {
1039 var index = document.cookie.indexOf(myCookie);
1040 if (index != -1) {
1041 var valStart = index + myCookie.length;
1042 var valEnd = document.cookie.indexOf(";", valStart);
1043 if (valEnd == -1) {
1044 valEnd = document.cookie.length;
1045 }
1046 var val = document.cookie.substring(valStart, valEnd);
1047 return val;
1048 }
1049 }
1050 return 0;
1051}
1052
smain@google.com6bdcb982014-11-14 11:53:07 -08001053function writeCookie(cookie, val, section) {
Scott Maine4d8f1b2012-06-21 18:03:05 -07001054 if (val==undefined) return;
1055 section = section == null ? "_" : "_"+section+"_";
smain@google.com6bdcb982014-11-14 11:53:07 -08001056 var age = 2*365*24*60*60; // set max-age to 2 years
Scott Main3b90aff2013-08-01 18:09:35 -07001057 var cookieValue = cookie_namespace + section + cookie + "=" + val
smain@google.com80e38f42014-11-03 10:47:12 -08001058 + "; max-age=" + age +"; path=/";
Scott Maine4d8f1b2012-06-21 18:03:05 -07001059 document.cookie = cookieValue;
1060}
1061
1062/* ######### END COOKIES! ########## */
1063
1064
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001065var sticky = false;
Dirk Doughertyc3921652014-05-13 16:55:26 -07001066var stickyTop;
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001067var prevScrollLeft = 0; // used to compare current position to previous position of horiz scroll
Dirk Doughertyc3921652014-05-13 16:55:26 -07001068/* Sets the vertical scoll position at which the sticky bar should appear.
1069 This method is called to reset the position when search results appear or hide */
1070function setStickyTop() {
1071 stickyTop = $('#header-wrapper').outerHeight() - $('#sticky-header').outerHeight();
1072}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001073
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001074/*
Scott Mainb16376f2014-05-21 20:35:47 -07001075 * Displays sticky nav bar on pages when dac header scrolls out of view
Dirk Doughertyc3921652014-05-13 16:55:26 -07001076 */
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001077$(window).scroll(function(event) {
1078
1079 setStickyTop();
1080 var hiding = false;
1081 var $stickyEl = $('#sticky-header');
1082 var $menuEl = $('.menu-container');
1083 // Exit if there's no sidenav
1084 if ($('#side-nav').length == 0) return;
1085 // Exit if the mouse target is a DIV, because that means the event is coming
1086 // from a scrollable div and so there's no need to make adjustments to our layout
1087 if ($(event.target).nodeName == "DIV") {
1088 return;
1089 }
1090
1091 var top = $(window).scrollTop();
1092 // we set the navbar fixed when the scroll position is beyond the height of the site header...
1093 var shouldBeSticky = top >= stickyTop;
1094 // ... except if the document content is shorter than the sidenav height.
1095 // (this is necessary to avoid crazy behavior on OSX Lion due to overscroll bouncing)
1096 if ($("#doc-col").height() < $("#side-nav").height()) {
1097 shouldBeSticky = false;
1098 }
Scott Mainf5257812014-05-22 17:26:38 -07001099 // Account for horizontal scroll
1100 var scrollLeft = $(window).scrollLeft();
1101 // When the sidenav is fixed and user scrolls horizontally, reposition the sidenav to match
1102 if (sticky && (scrollLeft != prevScrollLeft)) {
1103 updateSideNavPosition();
1104 prevScrollLeft = scrollLeft;
1105 }
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001106
1107 // Don't continue if the header is sufficently far away
1108 // (to avoid intensive resizing that slows scrolling)
1109 if (sticky == shouldBeSticky) {
1110 return;
1111 }
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001112
1113 // If sticky header visible and position is now near top, hide sticky
1114 if (sticky && !shouldBeSticky) {
1115 sticky = false;
1116 hiding = true;
1117 // make the sidenav static again
1118 $('#devdoc-nav')
1119 .removeClass('fixed')
1120 .css({'width':'auto','margin':''})
1121 .prependTo('#side-nav');
1122 // delay hide the sticky
1123 $menuEl.removeClass('sticky-menu');
1124 $stickyEl.fadeOut(250);
1125 hiding = false;
1126
1127 // update the sidenaav position for side scrolling
1128 updateSideNavPosition();
1129 } else if (!sticky && shouldBeSticky) {
1130 sticky = true;
1131 $stickyEl.fadeIn(10);
1132 $menuEl.addClass('sticky-menu');
1133
1134 // make the sidenav fixed
1135 var width = $('#devdoc-nav').width();
1136 $('#devdoc-nav')
1137 .addClass('fixed')
1138 .css({'width':width+'px'})
1139 .prependTo('#body-content');
1140
1141 // update the sidenaav position for side scrolling
1142 updateSideNavPosition();
1143
1144 } else if (hiding && top < 15) {
1145 $menuEl.removeClass('sticky-menu');
1146 $stickyEl.hide();
1147 hiding = false;
1148 }
1149 resizeNav(250); // pass true in order to delay the scrollbar re-initialization for performance
1150});
1151
1152/*
1153 * Manages secion card states and nav resize to conclude loading
1154 */
Dirk Doughertyc3921652014-05-13 16:55:26 -07001155(function() {
1156 $(document).ready(function() {
1157
Dirk Doughertyc3921652014-05-13 16:55:26 -07001158 // Stack hover states
1159 $('.section-card-menu').each(function(index, el) {
1160 var height = $(el).height();
1161 $(el).css({height:height+'px', position:'relative'});
1162 var $cardInfo = $(el).find('.card-info');
1163
1164 $cardInfo.css({position: 'absolute', bottom:'0px', left:'0px', right:'0px', overflow:'visible'});
1165 });
1166
Dirk Doughertyc3921652014-05-13 16:55:26 -07001167 });
1168
1169})();
1170
Scott Maine4d8f1b2012-06-21 18:03:05 -07001171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
Scott Maind7026f72013-06-17 15:08:49 -07001184/* MISC LIBRARY FUNCTIONS */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001185
1186
1187
1188
1189
1190function toggle(obj, slide) {
1191 var ul = $("ul:first", obj);
1192 var li = ul.parent();
1193 if (li.hasClass("closed")) {
1194 if (slide) {
1195 ul.slideDown("fast");
1196 } else {
1197 ul.show();
1198 }
1199 li.removeClass("closed");
1200 li.addClass("open");
1201 $(".toggle-img", li).attr("title", "hide pages");
1202 } else {
1203 ul.slideUp("fast");
1204 li.removeClass("open");
1205 li.addClass("closed");
1206 $(".toggle-img", li).attr("title", "show pages");
1207 }
1208}
1209
1210
Scott Maine4d8f1b2012-06-21 18:03:05 -07001211function buildToggleLists() {
1212 $(".toggle-list").each(
1213 function(i) {
1214 $("div:first", this).append("<a class='toggle-img' href='#' title='show pages' onClick='toggle(this.parentNode.parentNode, true); return false;'></a>");
1215 $(this).addClass("closed");
1216 });
1217}
1218
1219
1220
Scott Maind7026f72013-06-17 15:08:49 -07001221function hideNestedItems(list, toggle) {
1222 $list = $(list);
1223 // hide nested lists
1224 if($list.hasClass('showing')) {
1225 $("li ol", $list).hide('fast');
1226 $list.removeClass('showing');
1227 // show nested lists
1228 } else {
1229 $("li ol", $list).show('fast');
1230 $list.addClass('showing');
1231 }
1232 $(".more,.less",$(toggle)).toggle();
1233}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001234
1235
smain@google.com95948b82014-06-16 19:24:25 -07001236/* Call this to add listeners to a <select> element for Studio/Eclipse/Other docs */
1237function setupIdeDocToggle() {
1238 $( "select.ide" ).change(function() {
1239 var selected = $(this).find("option:selected").attr("value");
1240 $(".select-ide").hide();
1241 $(".select-ide."+selected).show();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001242
smain@google.com95948b82014-06-16 19:24:25 -07001243 $("select.ide").val(selected);
1244 });
1245}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270/* REFERENCE NAV SWAP */
1271
1272
1273function getNavPref() {
1274 var v = readCookie('reference_nav');
1275 if (v != NAV_PREF_TREE) {
1276 v = NAV_PREF_PANELS;
1277 }
1278 return v;
1279}
1280
1281function chooseDefaultNav() {
1282 nav_pref = getNavPref();
1283 if (nav_pref == NAV_PREF_TREE) {
1284 $("#nav-panels").toggle();
1285 $("#panel-link").toggle();
1286 $("#nav-tree").toggle();
1287 $("#tree-link").toggle();
1288 }
1289}
1290
1291function swapNav() {
1292 if (nav_pref == NAV_PREF_TREE) {
1293 nav_pref = NAV_PREF_PANELS;
1294 } else {
1295 nav_pref = NAV_PREF_TREE;
1296 init_default_navtree(toRoot);
1297 }
smain@google.com6bdcb982014-11-14 11:53:07 -08001298 writeCookie("nav", nav_pref, "reference");
Scott Maine4d8f1b2012-06-21 18:03:05 -07001299
1300 $("#nav-panels").toggle();
1301 $("#panel-link").toggle();
1302 $("#nav-tree").toggle();
1303 $("#tree-link").toggle();
Scott Main3b90aff2013-08-01 18:09:35 -07001304
Scott Maine4d8f1b2012-06-21 18:03:05 -07001305 resizeNav();
1306
1307 // Gross nasty hack to make tree view show up upon first swap by setting height manually
1308 $("#nav-tree .jspContainer:visible")
1309 .css({'height':$("#nav-tree .jspContainer .jspPane").height() +'px'});
1310 // Another nasty hack to make the scrollbar appear now that we have height
1311 resizeNav();
Scott Main3b90aff2013-08-01 18:09:35 -07001312
Scott Maine4d8f1b2012-06-21 18:03:05 -07001313 if ($("#nav-tree").is(':visible')) {
1314 scrollIntoView("nav-tree");
1315 } else {
1316 scrollIntoView("packages-nav");
1317 scrollIntoView("classes-nav");
1318 }
1319}
1320
1321
1322
Scott Mainf5089842012-08-14 16:31:07 -07001323/* ############################################ */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001324/* ########## LOCALIZATION ############ */
Scott Mainf5089842012-08-14 16:31:07 -07001325/* ############################################ */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001326
1327function getBaseUri(uri) {
1328 var intlUrl = (uri.substring(0,6) == "/intl/");
1329 if (intlUrl) {
1330 base = uri.substring(uri.indexOf('intl/')+5,uri.length);
1331 base = base.substring(base.indexOf('/')+1, base.length);
1332 //alert("intl, returning base url: /" + base);
1333 return ("/" + base);
1334 } else {
1335 //alert("not intl, returning uri as found.");
1336 return uri;
1337 }
1338}
1339
1340function requestAppendHL(uri) {
1341//append "?hl=<lang> to an outgoing request (such as to blog)
1342 var lang = getLangPref();
1343 if (lang) {
1344 var q = 'hl=' + lang;
1345 uri += '?' + q;
1346 window.location = uri;
1347 return false;
1348 } else {
1349 return true;
1350 }
1351}
1352
1353
Scott Maine4d8f1b2012-06-21 18:03:05 -07001354function changeNavLang(lang) {
Scott Main6eb95f12012-10-02 17:12:23 -07001355 var $links = $("#devdoc-nav,#header,#nav-x,.training-nav-top,.content-footer").find("a["+lang+"-lang]");
1356 $links.each(function(i){ // for each link with a translation
1357 var $link = $(this);
1358 if (lang != "en") { // No need to worry about English, because a language change invokes new request
1359 // put the desired language from the attribute as the text
1360 $link.text($link.attr(lang+"-lang"))
Scott Maine4d8f1b2012-06-21 18:03:05 -07001361 }
Scott Main6eb95f12012-10-02 17:12:23 -07001362 });
Scott Maine4d8f1b2012-06-21 18:03:05 -07001363}
1364
Scott Main015d6162013-01-29 09:01:52 -08001365function changeLangPref(lang, submit) {
smain@google.com6bdcb982014-11-14 11:53:07 -08001366 writeCookie("pref_lang", lang, null);
Scott Main015d6162013-01-29 09:01:52 -08001367
1368 // ####### TODO: Remove this condition once we're stable on devsite #######
1369 // This condition is only needed if we still need to support legacy GAE server
1370 if (devsite) {
1371 // Switch language when on Devsite server
1372 if (submit) {
1373 $("#setlang").submit();
1374 }
1375 } else {
1376 // Switch language when on legacy GAE server
Scott Main015d6162013-01-29 09:01:52 -08001377 if (submit) {
1378 window.location = getBaseUri(location.pathname);
1379 }
Scott Maine4d8f1b2012-06-21 18:03:05 -07001380 }
1381}
1382
1383function loadLangPref() {
1384 var lang = readCookie("pref_lang");
1385 if (lang != 0) {
1386 $("#language").find("option[value='"+lang+"']").attr("selected",true);
1387 }
1388}
1389
1390function getLangPref() {
1391 var lang = $("#language").find(":selected").attr("value");
1392 if (!lang) {
1393 lang = readCookie("pref_lang");
1394 }
1395 return (lang != 0) ? lang : 'en';
1396}
1397
1398/* ########## END LOCALIZATION ############ */
1399
1400
1401
1402
1403
1404
1405/* Used to hide and reveal supplemental content, such as long code samples.
1406 See the companion CSS in android-developer-docs.css */
1407function toggleContent(obj) {
Scott Maindc63dda2013-08-22 16:03:21 -07001408 var div = $(obj).closest(".toggle-content");
1409 var toggleMe = $(".toggle-content-toggleme:eq(0)",div);
Scott Maine4d8f1b2012-06-21 18:03:05 -07001410 if (div.hasClass("closed")) { // if it's closed, open it
1411 toggleMe.slideDown();
Scott Maindc63dda2013-08-22 16:03:21 -07001412 $(".toggle-content-text:eq(0)", obj).toggle();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001413 div.removeClass("closed").addClass("open");
Scott Maindc63dda2013-08-22 16:03:21 -07001414 $(".toggle-content-img:eq(0)", div).attr("title", "hide").attr("src", toRoot
Scott Maine4d8f1b2012-06-21 18:03:05 -07001415 + "assets/images/triangle-opened.png");
1416 } else { // if it's open, close it
1417 toggleMe.slideUp('fast', function() { // Wait until the animation is done before closing arrow
Scott Maindc63dda2013-08-22 16:03:21 -07001418 $(".toggle-content-text:eq(0)", obj).toggle();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001419 div.removeClass("open").addClass("closed");
Scott Maindc63dda2013-08-22 16:03:21 -07001420 div.find(".toggle-content").removeClass("open").addClass("closed")
1421 .find(".toggle-content-toggleme").hide();
Scott Main3b90aff2013-08-01 18:09:35 -07001422 $(".toggle-content-img", div).attr("title", "show").attr("src", toRoot
Scott Maine4d8f1b2012-06-21 18:03:05 -07001423 + "assets/images/triangle-closed.png");
1424 });
1425 }
1426 return false;
1427}
Scott Mainf5089842012-08-14 16:31:07 -07001428
1429
Scott Maindb3678b2012-10-23 14:13:41 -07001430/* New version of expandable content */
1431function toggleExpandable(link,id) {
1432 if($(id).is(':visible')) {
1433 $(id).slideUp();
1434 $(link).removeClass('expanded');
1435 } else {
1436 $(id).slideDown();
1437 $(link).addClass('expanded');
1438 }
1439}
1440
1441function hideExpandable(ids) {
1442 $(ids).slideUp();
Scott Main55d99832012-11-12 23:03:59 -08001443 $(ids).prev('h4').find('a.expandable').removeClass('expanded');
Scott Maindb3678b2012-10-23 14:13:41 -07001444}
1445
Scott Mainf5089842012-08-14 16:31:07 -07001446
1447
1448
1449
Scott Main3b90aff2013-08-01 18:09:35 -07001450/*
Scott Mainf5089842012-08-14 16:31:07 -07001451 * Slideshow 1.0
1452 * Used on /index.html and /develop/index.html for carousel
1453 *
1454 * Sample usage:
1455 * HTML -
1456 * <div class="slideshow-container">
1457 * <a href="" class="slideshow-prev">Prev</a>
1458 * <a href="" class="slideshow-next">Next</a>
1459 * <ul>
1460 * <li class="item"><img src="images/marquee1.jpg"></li>
1461 * <li class="item"><img src="images/marquee2.jpg"></li>
1462 * <li class="item"><img src="images/marquee3.jpg"></li>
1463 * <li class="item"><img src="images/marquee4.jpg"></li>
1464 * </ul>
1465 * </div>
1466 *
1467 * <script type="text/javascript">
1468 * $('.slideshow-container').dacSlideshow({
1469 * auto: true,
1470 * btnPrev: '.slideshow-prev',
1471 * btnNext: '.slideshow-next'
1472 * });
1473 * </script>
1474 *
1475 * Options:
1476 * btnPrev: optional identifier for previous button
1477 * btnNext: optional identifier for next button
Scott Maineb410352013-01-14 19:03:40 -08001478 * btnPause: optional identifier for pause button
Scott Mainf5089842012-08-14 16:31:07 -07001479 * auto: whether or not to auto-proceed
1480 * speed: animation speed
1481 * autoTime: time between auto-rotation
1482 * easing: easing function for transition
1483 * start: item to select by default
1484 * scroll: direction to scroll in
1485 * pagination: whether or not to include dotted pagination
1486 *
1487 */
1488
1489 (function($) {
1490 $.fn.dacSlideshow = function(o) {
Scott Main3b90aff2013-08-01 18:09:35 -07001491
Scott Mainf5089842012-08-14 16:31:07 -07001492 //Options - see above
1493 o = $.extend({
1494 btnPrev: null,
1495 btnNext: null,
Scott Maineb410352013-01-14 19:03:40 -08001496 btnPause: null,
Scott Mainf5089842012-08-14 16:31:07 -07001497 auto: true,
1498 speed: 500,
1499 autoTime: 12000,
1500 easing: null,
1501 start: 0,
1502 scroll: 1,
1503 pagination: true
1504
1505 }, o || {});
Scott Main3b90aff2013-08-01 18:09:35 -07001506
1507 //Set up a carousel for each
Scott Mainf5089842012-08-14 16:31:07 -07001508 return this.each(function() {
1509
1510 var running = false;
1511 var animCss = o.vertical ? "top" : "left";
1512 var sizeCss = o.vertical ? "height" : "width";
1513 var div = $(this);
1514 var ul = $("ul", div);
1515 var tLi = $("li", ul);
Scott Main3b90aff2013-08-01 18:09:35 -07001516 var tl = tLi.size();
Scott Mainf5089842012-08-14 16:31:07 -07001517 var timer = null;
1518
1519 var li = $("li", ul);
1520 var itemLength = li.size();
1521 var curr = o.start;
1522
1523 li.css({float: o.vertical ? "none" : "left"});
1524 ul.css({margin: "0", padding: "0", position: "relative", "list-style-type": "none", "z-index": "1"});
1525 div.css({position: "relative", "z-index": "2", left: "0px"});
1526
1527 var liSize = o.vertical ? height(li) : width(li);
1528 var ulSize = liSize * itemLength;
1529 var divSize = liSize;
1530
1531 li.css({width: li.width(), height: li.height()});
1532 ul.css(sizeCss, ulSize+"px").css(animCss, -(curr*liSize));
1533
1534 div.css(sizeCss, divSize+"px");
Scott Main3b90aff2013-08-01 18:09:35 -07001535
Scott Mainf5089842012-08-14 16:31:07 -07001536 //Pagination
1537 if (o.pagination) {
1538 var pagination = $("<div class='pagination'></div>");
1539 var pag_ul = $("<ul></ul>");
1540 if (tl > 1) {
1541 for (var i=0;i<tl;i++) {
1542 var li = $("<li>"+i+"</li>");
1543 pag_ul.append(li);
1544 if (i==o.start) li.addClass('active');
1545 li.click(function() {
1546 go(parseInt($(this).text()));
1547 })
1548 }
1549 pagination.append(pag_ul);
1550 div.append(pagination);
1551 }
1552 }
Scott Main3b90aff2013-08-01 18:09:35 -07001553
Scott Mainf5089842012-08-14 16:31:07 -07001554 //Previous button
1555 if(o.btnPrev)
1556 $(o.btnPrev).click(function(e) {
1557 e.preventDefault();
1558 return go(curr-o.scroll);
1559 });
1560
1561 //Next button
1562 if(o.btnNext)
1563 $(o.btnNext).click(function(e) {
1564 e.preventDefault();
1565 return go(curr+o.scroll);
1566 });
Scott Maineb410352013-01-14 19:03:40 -08001567
1568 //Pause button
1569 if(o.btnPause)
1570 $(o.btnPause).click(function(e) {
1571 e.preventDefault();
1572 if ($(this).hasClass('paused')) {
1573 startRotateTimer();
1574 } else {
1575 pauseRotateTimer();
1576 }
1577 });
Scott Main3b90aff2013-08-01 18:09:35 -07001578
Scott Mainf5089842012-08-14 16:31:07 -07001579 //Auto rotation
1580 if(o.auto) startRotateTimer();
Scott Main3b90aff2013-08-01 18:09:35 -07001581
Scott Mainf5089842012-08-14 16:31:07 -07001582 function startRotateTimer() {
1583 clearInterval(timer);
1584 timer = setInterval(function() {
1585 if (curr == tl-1) {
1586 go(0);
1587 } else {
Scott Main3b90aff2013-08-01 18:09:35 -07001588 go(curr+o.scroll);
1589 }
Scott Mainf5089842012-08-14 16:31:07 -07001590 }, o.autoTime);
Scott Maineb410352013-01-14 19:03:40 -08001591 $(o.btnPause).removeClass('paused');
1592 }
1593
1594 function pauseRotateTimer() {
1595 clearInterval(timer);
1596 $(o.btnPause).addClass('paused');
Scott Mainf5089842012-08-14 16:31:07 -07001597 }
1598
1599 //Go to an item
1600 function go(to) {
1601 if(!running) {
1602
1603 if(to<0) {
1604 to = itemLength-1;
1605 } else if (to>itemLength-1) {
1606 to = 0;
1607 }
1608 curr = to;
1609
1610 running = true;
1611
1612 ul.animate(
1613 animCss == "left" ? { left: -(curr*liSize) } : { top: -(curr*liSize) } , o.speed, o.easing,
1614 function() {
1615 running = false;
1616 }
1617 );
1618
1619 $(o.btnPrev + "," + o.btnNext).removeClass("disabled");
1620 $( (curr-o.scroll<0 && o.btnPrev)
1621 ||
1622 (curr+o.scroll > itemLength && o.btnNext)
1623 ||
1624 []
1625 ).addClass("disabled");
1626
Scott Main3b90aff2013-08-01 18:09:35 -07001627
Scott Mainf5089842012-08-14 16:31:07 -07001628 var nav_items = $('li', pagination);
1629 nav_items.removeClass('active');
1630 nav_items.eq(to).addClass('active');
Scott Main3b90aff2013-08-01 18:09:35 -07001631
Scott Mainf5089842012-08-14 16:31:07 -07001632
1633 }
1634 if(o.auto) startRotateTimer();
1635 return false;
1636 };
1637 });
1638 };
1639
1640 function css(el, prop) {
1641 return parseInt($.css(el[0], prop)) || 0;
1642 };
1643 function width(el) {
1644 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1645 };
1646 function height(el) {
1647 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1648 };
1649
1650 })(jQuery);
1651
1652
Scott Main3b90aff2013-08-01 18:09:35 -07001653/*
Scott Mainf5089842012-08-14 16:31:07 -07001654 * dacSlideshow 1.0
1655 * Used on develop/index.html for side-sliding tabs
1656 *
1657 * Sample usage:
1658 * HTML -
1659 * <div class="slideshow-container">
1660 * <a href="" class="slideshow-prev">Prev</a>
1661 * <a href="" class="slideshow-next">Next</a>
1662 * <ul>
1663 * <li class="item"><img src="images/marquee1.jpg"></li>
1664 * <li class="item"><img src="images/marquee2.jpg"></li>
1665 * <li class="item"><img src="images/marquee3.jpg"></li>
1666 * <li class="item"><img src="images/marquee4.jpg"></li>
1667 * </ul>
1668 * </div>
1669 *
1670 * <script type="text/javascript">
1671 * $('.slideshow-container').dacSlideshow({
1672 * auto: true,
1673 * btnPrev: '.slideshow-prev',
1674 * btnNext: '.slideshow-next'
1675 * });
1676 * </script>
1677 *
1678 * Options:
1679 * btnPrev: optional identifier for previous button
1680 * btnNext: optional identifier for next button
1681 * auto: whether or not to auto-proceed
1682 * speed: animation speed
1683 * autoTime: time between auto-rotation
1684 * easing: easing function for transition
1685 * start: item to select by default
1686 * scroll: direction to scroll in
1687 * pagination: whether or not to include dotted pagination
1688 *
1689 */
1690 (function($) {
1691 $.fn.dacTabbedList = function(o) {
Scott Main3b90aff2013-08-01 18:09:35 -07001692
Scott Mainf5089842012-08-14 16:31:07 -07001693 //Options - see above
1694 o = $.extend({
1695 speed : 250,
1696 easing: null,
1697 nav_id: null,
1698 frame_id: null
1699 }, o || {});
Scott Main3b90aff2013-08-01 18:09:35 -07001700
1701 //Set up a carousel for each
Scott Mainf5089842012-08-14 16:31:07 -07001702 return this.each(function() {
1703
1704 var curr = 0;
1705 var running = false;
1706 var animCss = "margin-left";
1707 var sizeCss = "width";
1708 var div = $(this);
Scott Main3b90aff2013-08-01 18:09:35 -07001709
Scott Mainf5089842012-08-14 16:31:07 -07001710 var nav = $(o.nav_id, div);
1711 var nav_li = $("li", nav);
Scott Main3b90aff2013-08-01 18:09:35 -07001712 var nav_size = nav_li.size();
Scott Mainf5089842012-08-14 16:31:07 -07001713 var frame = div.find(o.frame_id);
1714 var content_width = $(frame).find('ul').width();
1715 //Buttons
1716 $(nav_li).click(function(e) {
1717 go($(nav_li).index($(this)));
1718 })
Scott Main3b90aff2013-08-01 18:09:35 -07001719
Scott Mainf5089842012-08-14 16:31:07 -07001720 //Go to an item
1721 function go(to) {
1722 if(!running) {
1723 curr = to;
1724 running = true;
1725
1726 frame.animate({ 'margin-left' : -(curr*content_width) }, o.speed, o.easing,
1727 function() {
1728 running = false;
1729 }
1730 );
1731
Scott Main3b90aff2013-08-01 18:09:35 -07001732
Scott Mainf5089842012-08-14 16:31:07 -07001733 nav_li.removeClass('active');
1734 nav_li.eq(to).addClass('active');
Scott Main3b90aff2013-08-01 18:09:35 -07001735
Scott Mainf5089842012-08-14 16:31:07 -07001736
1737 }
1738 return false;
1739 };
1740 });
1741 };
1742
1743 function css(el, prop) {
1744 return parseInt($.css(el[0], prop)) || 0;
1745 };
1746 function width(el) {
1747 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1748 };
1749 function height(el) {
1750 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1751 };
1752
1753 })(jQuery);
1754
1755
1756
1757
1758
1759/* ######################################################## */
1760/* ################ SEARCH SUGGESTIONS ################## */
1761/* ######################################################## */
1762
1763
Scott Main7e447ed2013-02-19 17:22:37 -08001764
Scott Main0e76e7e2013-03-12 10:24:07 -07001765var gSelectedIndex = -1; // the index position of currently highlighted suggestion
1766var gSelectedColumn = -1; // which column of suggestion lists is currently focused
1767
Scott Mainf5089842012-08-14 16:31:07 -07001768var gMatches = new Array();
1769var gLastText = "";
Scott Mainf5089842012-08-14 16:31:07 -07001770var gInitialized = false;
Scott Main7e447ed2013-02-19 17:22:37 -08001771var ROW_COUNT_FRAMEWORK = 20; // max number of results in list
1772var gListLength = 0;
1773
1774
1775var gGoogleMatches = new Array();
1776var ROW_COUNT_GOOGLE = 15; // max number of results in list
1777var gGoogleListLength = 0;
Scott Mainf5089842012-08-14 16:31:07 -07001778
Scott Main0e76e7e2013-03-12 10:24:07 -07001779var gDocsMatches = new Array();
1780var ROW_COUNT_DOCS = 100; // max number of results in list
1781var gDocsListLength = 0;
1782
Scott Mainde295272013-03-25 15:48:35 -07001783function onSuggestionClick(link) {
1784 // When user clicks a suggested document, track it
smain@google.comed677d72014-12-12 10:19:17 -08001785 ga('send', 'event', 'Suggestion Click', 'clicked: ' + $(link).attr('href'),
1786 'query: ' + $("#search_autocomplete").val().toLowerCase());
Scott Mainde295272013-03-25 15:48:35 -07001787}
1788
Scott Mainf5089842012-08-14 16:31:07 -07001789function set_item_selected($li, selected)
1790{
1791 if (selected) {
1792 $li.attr('class','jd-autocomplete jd-selected');
1793 } else {
1794 $li.attr('class','jd-autocomplete');
1795 }
1796}
1797
1798function set_item_values(toroot, $li, match)
1799{
1800 var $link = $('a',$li);
1801 $link.html(match.__hilabel || match.label);
1802 $link.attr('href',toroot + match.link);
1803}
1804
Scott Main719acb42013-12-05 16:05:09 -08001805function set_item_values_jd(toroot, $li, match)
1806{
1807 var $link = $('a',$li);
1808 $link.html(match.title);
1809 $link.attr('href',toroot + match.url);
1810}
1811
Scott Main0e76e7e2013-03-12 10:24:07 -07001812function new_suggestion($list) {
Scott Main7e447ed2013-02-19 17:22:37 -08001813 var $li = $("<li class='jd-autocomplete'></li>");
1814 $list.append($li);
1815
1816 $li.mousedown(function() {
1817 window.location = this.firstChild.getAttribute("href");
1818 });
1819 $li.mouseover(function() {
Scott Main0e76e7e2013-03-12 10:24:07 -07001820 $('.search_filtered_wrapper li').removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001821 $(this).addClass('jd-selected');
Scott Main0e76e7e2013-03-12 10:24:07 -07001822 gSelectedColumn = $(".search_filtered:visible").index($(this).closest('.search_filtered'));
1823 gSelectedIndex = $("li", $(".search_filtered:visible")[gSelectedColumn]).index(this);
Scott Main7e447ed2013-02-19 17:22:37 -08001824 });
Scott Mainde295272013-03-25 15:48:35 -07001825 $li.append("<a onclick='onSuggestionClick(this)'></a>");
Scott Main7e447ed2013-02-19 17:22:37 -08001826 $li.attr('class','show-item');
1827 return $li;
1828}
1829
Scott Mainf5089842012-08-14 16:31:07 -07001830function sync_selection_table(toroot)
1831{
Scott Mainf5089842012-08-14 16:31:07 -07001832 var $li; //list item jquery object
1833 var i; //list item iterator
Scott Main7e447ed2013-02-19 17:22:37 -08001834
Scott Main0e76e7e2013-03-12 10:24:07 -07001835 // if there are NO results at all, hide all columns
1836 if (!(gMatches.length > 0) && !(gGoogleMatches.length > 0) && !(gDocsMatches.length > 0)) {
1837 $('.suggest-card').hide(300);
1838 return;
1839 }
1840
1841 // if there are api results
Scott Main7e447ed2013-02-19 17:22:37 -08001842 if ((gMatches.length > 0) || (gGoogleMatches.length > 0)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001843 // reveal suggestion list
1844 $('.suggest-card.dummy').show();
1845 $('.suggest-card.reference').show();
1846 var listIndex = 0; // list index position
Scott Main7e447ed2013-02-19 17:22:37 -08001847
Scott Main0e76e7e2013-03-12 10:24:07 -07001848 // reset the lists
1849 $(".search_filtered_wrapper.reference li").remove();
Scott Main7e447ed2013-02-19 17:22:37 -08001850
Scott Main0e76e7e2013-03-12 10:24:07 -07001851 // ########### ANDROID RESULTS #############
1852 if (gMatches.length > 0) {
Scott Main7e447ed2013-02-19 17:22:37 -08001853
Scott Main0e76e7e2013-03-12 10:24:07 -07001854 // determine android results to show
1855 gListLength = gMatches.length < ROW_COUNT_FRAMEWORK ?
1856 gMatches.length : ROW_COUNT_FRAMEWORK;
1857 for (i=0; i<gListLength; i++) {
1858 var $li = new_suggestion($(".suggest-card.reference ul"));
1859 set_item_values(toroot, $li, gMatches[i]);
1860 set_item_selected($li, i == gSelectedIndex);
1861 }
1862 }
Scott Main7e447ed2013-02-19 17:22:37 -08001863
Scott Main0e76e7e2013-03-12 10:24:07 -07001864 // ########### GOOGLE RESULTS #############
1865 if (gGoogleMatches.length > 0) {
1866 // show header for list
1867 $(".suggest-card.reference ul").append("<li class='header'>in Google Services:</li>");
Scott Main7e447ed2013-02-19 17:22:37 -08001868
Scott Main0e76e7e2013-03-12 10:24:07 -07001869 // determine google results to show
1870 gGoogleListLength = gGoogleMatches.length < ROW_COUNT_GOOGLE ? gGoogleMatches.length : ROW_COUNT_GOOGLE;
1871 for (i=0; i<gGoogleListLength; i++) {
1872 var $li = new_suggestion($(".suggest-card.reference ul"));
1873 set_item_values(toroot, $li, gGoogleMatches[i]);
1874 set_item_selected($li, i == gSelectedIndex);
1875 }
1876 }
Scott Mainf5089842012-08-14 16:31:07 -07001877 } else {
Scott Main0e76e7e2013-03-12 10:24:07 -07001878 $('.suggest-card.reference').hide();
1879 $('.suggest-card.dummy').hide();
1880 }
1881
1882 // ########### JD DOC RESULTS #############
1883 if (gDocsMatches.length > 0) {
1884 // reset the lists
1885 $(".search_filtered_wrapper.docs li").remove();
1886
1887 // determine google results to show
Scott Main719acb42013-12-05 16:05:09 -08001888 // NOTE: The order of the conditions below for the sugg.type MUST BE SPECIFIC:
1889 // The order must match the reverse order that each section appears as a card in
1890 // the suggestion UI... this may be only for the "develop" grouped items though.
Scott Main0e76e7e2013-03-12 10:24:07 -07001891 gDocsListLength = gDocsMatches.length < ROW_COUNT_DOCS ? gDocsMatches.length : ROW_COUNT_DOCS;
1892 for (i=0; i<gDocsListLength; i++) {
1893 var sugg = gDocsMatches[i];
1894 var $li;
1895 if (sugg.type == "design") {
1896 $li = new_suggestion($(".suggest-card.design ul"));
1897 } else
1898 if (sugg.type == "distribute") {
1899 $li = new_suggestion($(".suggest-card.distribute ul"));
1900 } else
Scott Main719acb42013-12-05 16:05:09 -08001901 if (sugg.type == "samples") {
1902 $li = new_suggestion($(".suggest-card.develop .child-card.samples"));
1903 } else
Scott Main0e76e7e2013-03-12 10:24:07 -07001904 if (sugg.type == "training") {
1905 $li = new_suggestion($(".suggest-card.develop .child-card.training"));
1906 } else
Scott Main719acb42013-12-05 16:05:09 -08001907 if (sugg.type == "about"||"guide"||"tools"||"google") {
Scott Main0e76e7e2013-03-12 10:24:07 -07001908 $li = new_suggestion($(".suggest-card.develop .child-card.guides"));
1909 } else {
1910 continue;
1911 }
1912
Scott Main719acb42013-12-05 16:05:09 -08001913 set_item_values_jd(toroot, $li, sugg);
Scott Main0e76e7e2013-03-12 10:24:07 -07001914 set_item_selected($li, i == gSelectedIndex);
1915 }
1916
1917 // add heading and show or hide card
1918 if ($(".suggest-card.design li").length > 0) {
1919 $(".suggest-card.design ul").prepend("<li class='header'>Design:</li>");
1920 $(".suggest-card.design").show(300);
1921 } else {
1922 $('.suggest-card.design').hide(300);
1923 }
1924 if ($(".suggest-card.distribute li").length > 0) {
1925 $(".suggest-card.distribute ul").prepend("<li class='header'>Distribute:</li>");
1926 $(".suggest-card.distribute").show(300);
1927 } else {
1928 $('.suggest-card.distribute').hide(300);
1929 }
1930 if ($(".child-card.guides li").length > 0) {
1931 $(".child-card.guides").prepend("<li class='header'>Guides:</li>");
1932 $(".child-card.guides li").appendTo(".suggest-card.develop ul");
1933 }
1934 if ($(".child-card.training li").length > 0) {
1935 $(".child-card.training").prepend("<li class='header'>Training:</li>");
1936 $(".child-card.training li").appendTo(".suggest-card.develop ul");
1937 }
Scott Main719acb42013-12-05 16:05:09 -08001938 if ($(".child-card.samples li").length > 0) {
1939 $(".child-card.samples").prepend("<li class='header'>Samples:</li>");
1940 $(".child-card.samples li").appendTo(".suggest-card.develop ul");
1941 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001942
1943 if ($(".suggest-card.develop li").length > 0) {
1944 $(".suggest-card.develop").show(300);
1945 } else {
1946 $('.suggest-card.develop').hide(300);
1947 }
1948
1949 } else {
1950 $('.search_filtered_wrapper.docs .suggest-card:not(.dummy)').hide(300);
Scott Mainf5089842012-08-14 16:31:07 -07001951 }
1952}
1953
Scott Main0e76e7e2013-03-12 10:24:07 -07001954/** Called by the search input's onkeydown and onkeyup events.
1955 * Handles navigation with keyboard arrows, Enter key to invoke search,
1956 * otherwise invokes search suggestions on key-up event.
1957 * @param e The JS event
1958 * @param kd True if the event is key-down
Scott Main3b90aff2013-08-01 18:09:35 -07001959 * @param toroot A string for the site's root path
Scott Main0e76e7e2013-03-12 10:24:07 -07001960 * @returns True if the event should bubble up
1961 */
Scott Mainf5089842012-08-14 16:31:07 -07001962function search_changed(e, kd, toroot)
1963{
Scott Main719acb42013-12-05 16:05:09 -08001964 var currentLang = getLangPref();
Scott Mainf5089842012-08-14 16:31:07 -07001965 var search = document.getElementById("search_autocomplete");
1966 var text = search.value.replace(/(^ +)|( +$)/g, '');
Scott Main0e76e7e2013-03-12 10:24:07 -07001967 // get the ul hosting the currently selected item
1968 gSelectedColumn = gSelectedColumn >= 0 ? gSelectedColumn : 0;
1969 var $columns = $(".search_filtered_wrapper").find(".search_filtered:visible");
1970 var $selectedUl = $columns[gSelectedColumn];
1971
Scott Mainf5089842012-08-14 16:31:07 -07001972 // show/hide the close button
1973 if (text != '') {
1974 $(".search .close").removeClass("hide");
1975 } else {
1976 $(".search .close").addClass("hide");
1977 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001978 // 27 = esc
1979 if (e.keyCode == 27) {
1980 // close all search results
1981 if (kd) $('.search .close').trigger('click');
1982 return true;
1983 }
Scott Mainf5089842012-08-14 16:31:07 -07001984 // 13 = enter
Scott Main0e76e7e2013-03-12 10:24:07 -07001985 else if (e.keyCode == 13) {
1986 if (gSelectedIndex < 0) {
1987 $('.suggest-card').hide();
Scott Main7e447ed2013-02-19 17:22:37 -08001988 if ($("#searchResults").is(":hidden") && (search.value != "")) {
1989 // if results aren't showing (and text not empty), return true to allow search to execute
Dirk Doughertyc3921652014-05-13 16:55:26 -07001990 $('body,html').animate({scrollTop:0}, '500', 'swing');
Scott Mainf5089842012-08-14 16:31:07 -07001991 return true;
1992 } else {
1993 // otherwise, results are already showing, so allow ajax to auto refresh the results
1994 // and ignore this Enter press to avoid the reload.
1995 return false;
1996 }
1997 } else if (kd && gSelectedIndex >= 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001998 // click the link corresponding to selected item
1999 $("a",$("li",$selectedUl)[gSelectedIndex]).get()[0].click();
Scott Mainf5089842012-08-14 16:31:07 -07002000 return false;
2001 }
2002 }
Scott Mainb16376f2014-05-21 20:35:47 -07002003 // If Google results are showing, return true to allow ajax search to execute
Scott Main0e76e7e2013-03-12 10:24:07 -07002004 else if ($("#searchResults").is(":visible")) {
Scott Mainb16376f2014-05-21 20:35:47 -07002005 // Also, if search_results is scrolled out of view, scroll to top to make results visible
Dirk Doughertyca1230c2014-05-14 20:00:03 -07002006 if ((sticky ) && (search.value != "")) {
2007 $('body,html').animate({scrollTop:0}, '500', 'swing');
2008 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002009 return true;
2010 }
2011 // 38 UP ARROW
Scott Mainf5089842012-08-14 16:31:07 -07002012 else if (kd && (e.keyCode == 38)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002013 // if the next item is a header, skip it
2014 if ($($("li", $selectedUl)[gSelectedIndex-1]).hasClass("header")) {
Scott Mainf5089842012-08-14 16:31:07 -07002015 gSelectedIndex--;
Scott Main7e447ed2013-02-19 17:22:37 -08002016 }
2017 if (gSelectedIndex >= 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002018 $('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');
2021 // If user reaches top, reset selected column
2022 if (gSelectedIndex < 0) {
2023 gSelectedColumn = -1;
2024 }
Scott Mainf5089842012-08-14 16:31:07 -07002025 }
2026 return false;
2027 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002028 // 40 DOWN ARROW
Scott Mainf5089842012-08-14 16:31:07 -07002029 else if (kd && (e.keyCode == 40)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002030 // if the next item is a header, skip it
2031 if ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header")) {
Scott Mainf5089842012-08-14 16:31:07 -07002032 gSelectedIndex++;
Scott Main7e447ed2013-02-19 17:22:37 -08002033 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002034 if ((gSelectedIndex < $("li", $selectedUl).length-1) ||
2035 ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header"))) {
2036 $('li', $selectedUl).removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08002037 gSelectedIndex++;
Scott Main0e76e7e2013-03-12 10:24:07 -07002038 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
Scott Mainf5089842012-08-14 16:31:07 -07002039 }
2040 return false;
2041 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002042 // Consider left/right arrow navigation
2043 // NOTE: Order of suggest columns are reverse order (index position 0 is on right)
2044 else if (kd && $columns.length > 1 && gSelectedColumn >= 0) {
2045 // 37 LEFT ARROW
2046 // go left only if current column is not left-most column (last column)
2047 if (e.keyCode == 37 && gSelectedColumn < $columns.length - 1) {
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 // 39 RIGHT ARROW
2064 // go right only if current column is not the right-most column (first column)
2065 else if (e.keyCode == 39 && gSelectedColumn > 0) {
2066 $('li', $selectedUl).removeClass('jd-selected');
2067 gSelectedColumn--;
2068 $selectedUl = $columns[gSelectedColumn];
2069 // keep or reset the selected item to last item as appropriate
2070 gSelectedIndex = gSelectedIndex >
2071 $("li", $selectedUl).length-1 ?
2072 $("li", $selectedUl).length-1 : gSelectedIndex;
2073 // if the corresponding item is a header, move down
2074 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
2075 gSelectedIndex++;
2076 }
Scott Main3b90aff2013-08-01 18:09:35 -07002077 // set item selected
Scott Main0e76e7e2013-03-12 10:24:07 -07002078 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
2079 return false;
2080 }
2081 }
2082
Scott Main719acb42013-12-05 16:05:09 -08002083 // if key-up event and not arrow down/up/left/right,
2084 // read the search query and add suggestions to gMatches
Scott Main0e76e7e2013-03-12 10:24:07 -07002085 else if (!kd && (e.keyCode != 40)
2086 && (e.keyCode != 38)
2087 && (e.keyCode != 37)
2088 && (e.keyCode != 39)) {
2089 gSelectedIndex = -1;
Scott Mainf5089842012-08-14 16:31:07 -07002090 gMatches = new Array();
2091 matchedCount = 0;
Scott Main7e447ed2013-02-19 17:22:37 -08002092 gGoogleMatches = new Array();
2093 matchedCountGoogle = 0;
Scott Main0e76e7e2013-03-12 10:24:07 -07002094 gDocsMatches = new Array();
2095 matchedCountDocs = 0;
Scott Main7e447ed2013-02-19 17:22:37 -08002096
2097 // Search for Android matches
Scott Mainf5089842012-08-14 16:31:07 -07002098 for (var i=0; i<DATA.length; i++) {
2099 var s = DATA[i];
2100 if (text.length != 0 &&
2101 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
2102 gMatches[matchedCount] = s;
2103 matchedCount++;
2104 }
2105 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002106 rank_autocomplete_api_results(text, gMatches);
Scott Mainf5089842012-08-14 16:31:07 -07002107 for (var i=0; i<gMatches.length; i++) {
2108 var s = gMatches[i];
Scott Main7e447ed2013-02-19 17:22:37 -08002109 }
2110
2111
2112 // Search for Google matches
2113 for (var i=0; i<GOOGLE_DATA.length; i++) {
2114 var s = GOOGLE_DATA[i];
2115 if (text.length != 0 &&
2116 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
2117 gGoogleMatches[matchedCountGoogle] = s;
2118 matchedCountGoogle++;
Scott Mainf5089842012-08-14 16:31:07 -07002119 }
2120 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002121 rank_autocomplete_api_results(text, gGoogleMatches);
Scott Main7e447ed2013-02-19 17:22:37 -08002122 for (var i=0; i<gGoogleMatches.length; i++) {
2123 var s = gGoogleMatches[i];
2124 }
2125
Scott Mainf5089842012-08-14 16:31:07 -07002126 highlight_autocomplete_result_labels(text);
Scott Main0e76e7e2013-03-12 10:24:07 -07002127
2128
2129
Scott Main719acb42013-12-05 16:05:09 -08002130 // Search for matching JD docs
Dirk Dougherty9b7f8f22014-11-01 17:08:56 -07002131 if (text.length >= 2) {
Scott Main719acb42013-12-05 16:05:09 -08002132 // Regex to match only the beginning of a word
2133 var textRegex = new RegExp("\\b" + text.toLowerCase(), "g");
2134
2135
2136 // Search for Training classes
2137 for (var i=0; i<TRAINING_RESOURCES.length; i++) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002138 // current search comparison, with counters for tag and title,
2139 // used later to improve ranking
Scott Main719acb42013-12-05 16:05:09 -08002140 var s = TRAINING_RESOURCES[i];
Scott Main0e76e7e2013-03-12 10:24:07 -07002141 s.matched_tag = 0;
2142 s.matched_title = 0;
2143 var matched = false;
2144
2145 // Check if query matches any tags; work backwards toward 1 to assist ranking
Scott Main719acb42013-12-05 16:05:09 -08002146 for (var j = s.keywords.length - 1; j >= 0; j--) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002147 // it matches a tag
Scott Main719acb42013-12-05 16:05:09 -08002148 if (s.keywords[j].toLowerCase().match(textRegex)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002149 matched = true;
2150 s.matched_tag = j + 1; // add 1 to index position
2151 }
2152 }
Scott Main719acb42013-12-05 16:05:09 -08002153 // Don't consider doc title for lessons (only for class landing pages),
2154 // unless the lesson has a tag that already matches
2155 if ((s.lang == currentLang) &&
2156 (!(s.type == "training" && s.url.indexOf("index.html") == -1) || matched)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002157 // it matches the doc title
Scott Main719acb42013-12-05 16:05:09 -08002158 if (s.title.toLowerCase().match(textRegex)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002159 matched = true;
2160 s.matched_title = 1;
2161 }
2162 }
2163 if (matched) {
2164 gDocsMatches[matchedCountDocs] = s;
2165 matchedCountDocs++;
2166 }
2167 }
Scott Main719acb42013-12-05 16:05:09 -08002168
2169
2170 // Search for API Guides
2171 for (var i=0; i<GUIDE_RESOURCES.length; i++) {
2172 // current search comparison, with counters for tag and title,
2173 // used later to improve ranking
2174 var s = GUIDE_RESOURCES[i];
2175 s.matched_tag = 0;
2176 s.matched_title = 0;
2177 var matched = false;
2178
2179 // Check if query matches any tags; work backwards toward 1 to assist ranking
2180 for (var j = s.keywords.length - 1; j >= 0; j--) {
2181 // it matches a tag
2182 if (s.keywords[j].toLowerCase().match(textRegex)) {
2183 matched = true;
2184 s.matched_tag = j + 1; // add 1 to index position
2185 }
2186 }
2187 // Check if query matches the doc title, but only for current language
2188 if (s.lang == currentLang) {
2189 // if query matches the doc title
2190 if (s.title.toLowerCase().match(textRegex)) {
2191 matched = true;
2192 s.matched_title = 1;
2193 }
2194 }
2195 if (matched) {
2196 gDocsMatches[matchedCountDocs] = s;
2197 matchedCountDocs++;
2198 }
2199 }
2200
2201
2202 // Search for Tools Guides
2203 for (var i=0; i<TOOLS_RESOURCES.length; i++) {
2204 // current search comparison, with counters for tag and title,
2205 // used later to improve ranking
2206 var s = TOOLS_RESOURCES[i];
2207 s.matched_tag = 0;
2208 s.matched_title = 0;
2209 var matched = false;
2210
2211 // Check if query matches any tags; work backwards toward 1 to assist ranking
2212 for (var j = s.keywords.length - 1; j >= 0; j--) {
2213 // it matches a tag
2214 if (s.keywords[j].toLowerCase().match(textRegex)) {
2215 matched = true;
2216 s.matched_tag = j + 1; // add 1 to index position
2217 }
2218 }
2219 // Check if query matches the doc title, but only for current language
2220 if (s.lang == currentLang) {
2221 // if query matches the doc title
2222 if (s.title.toLowerCase().match(textRegex)) {
2223 matched = true;
2224 s.matched_title = 1;
2225 }
2226 }
2227 if (matched) {
2228 gDocsMatches[matchedCountDocs] = s;
2229 matchedCountDocs++;
2230 }
2231 }
2232
2233
2234 // Search for About docs
2235 for (var i=0; i<ABOUT_RESOURCES.length; i++) {
2236 // current search comparison, with counters for tag and title,
2237 // used later to improve ranking
2238 var s = ABOUT_RESOURCES[i];
2239 s.matched_tag = 0;
2240 s.matched_title = 0;
2241 var matched = false;
2242
2243 // Check if query matches any tags; work backwards toward 1 to assist ranking
2244 for (var j = s.keywords.length - 1; j >= 0; j--) {
2245 // it matches a tag
2246 if (s.keywords[j].toLowerCase().match(textRegex)) {
2247 matched = true;
2248 s.matched_tag = j + 1; // add 1 to index position
2249 }
2250 }
2251 // Check if query matches the doc title, but only for current language
2252 if (s.lang == currentLang) {
2253 // if query matches the doc title
2254 if (s.title.toLowerCase().match(textRegex)) {
2255 matched = true;
2256 s.matched_title = 1;
2257 }
2258 }
2259 if (matched) {
2260 gDocsMatches[matchedCountDocs] = s;
2261 matchedCountDocs++;
2262 }
2263 }
2264
2265
2266 // Search for Design guides
2267 for (var i=0; i<DESIGN_RESOURCES.length; i++) {
2268 // current search comparison, with counters for tag and title,
2269 // used later to improve ranking
2270 var s = DESIGN_RESOURCES[i];
2271 s.matched_tag = 0;
2272 s.matched_title = 0;
2273 var matched = false;
2274
2275 // Check if query matches any tags; work backwards toward 1 to assist ranking
2276 for (var j = s.keywords.length - 1; j >= 0; j--) {
2277 // it matches a tag
2278 if (s.keywords[j].toLowerCase().match(textRegex)) {
2279 matched = true;
2280 s.matched_tag = j + 1; // add 1 to index position
2281 }
2282 }
2283 // Check if query matches the doc title, but only for current language
2284 if (s.lang == currentLang) {
2285 // if query matches the doc title
2286 if (s.title.toLowerCase().match(textRegex)) {
2287 matched = true;
2288 s.matched_title = 1;
2289 }
2290 }
2291 if (matched) {
2292 gDocsMatches[matchedCountDocs] = s;
2293 matchedCountDocs++;
2294 }
2295 }
2296
2297
2298 // Search for Distribute guides
2299 for (var i=0; i<DISTRIBUTE_RESOURCES.length; i++) {
2300 // current search comparison, with counters for tag and title,
2301 // used later to improve ranking
2302 var s = DISTRIBUTE_RESOURCES[i];
2303 s.matched_tag = 0;
2304 s.matched_title = 0;
2305 var matched = false;
2306
2307 // Check if query matches any tags; work backwards toward 1 to assist ranking
2308 for (var j = s.keywords.length - 1; j >= 0; j--) {
2309 // it matches a tag
2310 if (s.keywords[j].toLowerCase().match(textRegex)) {
2311 matched = true;
2312 s.matched_tag = j + 1; // add 1 to index position
2313 }
2314 }
2315 // Check if query matches the doc title, but only for current language
2316 if (s.lang == currentLang) {
2317 // if query matches the doc title
2318 if (s.title.toLowerCase().match(textRegex)) {
2319 matched = true;
2320 s.matched_title = 1;
2321 }
2322 }
2323 if (matched) {
2324 gDocsMatches[matchedCountDocs] = s;
2325 matchedCountDocs++;
2326 }
2327 }
2328
2329
2330 // Search for Google guides
2331 for (var i=0; i<GOOGLE_RESOURCES.length; i++) {
2332 // current search comparison, with counters for tag and title,
2333 // used later to improve ranking
2334 var s = GOOGLE_RESOURCES[i];
2335 s.matched_tag = 0;
2336 s.matched_title = 0;
2337 var matched = false;
2338
2339 // Check if query matches any tags; work backwards toward 1 to assist ranking
2340 for (var j = s.keywords.length - 1; j >= 0; j--) {
2341 // it matches a tag
2342 if (s.keywords[j].toLowerCase().match(textRegex)) {
2343 matched = true;
2344 s.matched_tag = j + 1; // add 1 to index position
2345 }
2346 }
2347 // Check if query matches the doc title, but only for current language
2348 if (s.lang == currentLang) {
2349 // if query matches the doc title
2350 if (s.title.toLowerCase().match(textRegex)) {
2351 matched = true;
2352 s.matched_title = 1;
2353 }
2354 }
2355 if (matched) {
2356 gDocsMatches[matchedCountDocs] = s;
2357 matchedCountDocs++;
2358 }
2359 }
2360
2361
2362 // Search for Samples
2363 for (var i=0; i<SAMPLES_RESOURCES.length; i++) {
2364 // current search comparison, with counters for tag and title,
2365 // used later to improve ranking
2366 var s = SAMPLES_RESOURCES[i];
2367 s.matched_tag = 0;
2368 s.matched_title = 0;
2369 var matched = false;
2370 // Check if query matches any tags; work backwards toward 1 to assist ranking
2371 for (var j = s.keywords.length - 1; j >= 0; j--) {
2372 // it matches a tag
2373 if (s.keywords[j].toLowerCase().match(textRegex)) {
2374 matched = true;
2375 s.matched_tag = j + 1; // add 1 to index position
2376 }
2377 }
2378 // Check if query matches the doc title, but only for current language
2379 if (s.lang == currentLang) {
2380 // if query matches the doc title.t
2381 if (s.title.toLowerCase().match(textRegex)) {
2382 matched = true;
2383 s.matched_title = 1;
2384 }
2385 }
2386 if (matched) {
2387 gDocsMatches[matchedCountDocs] = s;
2388 matchedCountDocs++;
2389 }
2390 }
2391
2392 // Rank/sort all the matched pages
Scott Main0e76e7e2013-03-12 10:24:07 -07002393 rank_autocomplete_doc_results(text, gDocsMatches);
2394 }
2395
2396 // draw the suggestions
Scott Mainf5089842012-08-14 16:31:07 -07002397 sync_selection_table(toroot);
2398 return true; // allow the event to bubble up to the search api
2399 }
2400}
2401
Scott Main0e76e7e2013-03-12 10:24:07 -07002402/* Order the jd doc result list based on match quality */
2403function rank_autocomplete_doc_results(query, matches) {
2404 query = query || '';
2405 if (!matches || !matches.length)
2406 return;
2407
2408 var _resultScoreFn = function(match) {
2409 var score = 1.0;
2410
2411 // if the query matched a tag
2412 if (match.matched_tag > 0) {
2413 // multiply score by factor relative to position in tags list (max of 3)
2414 score *= 3 / match.matched_tag;
2415
2416 // if it also matched the title
2417 if (match.matched_title > 0) {
2418 score *= 2;
2419 }
2420 } else if (match.matched_title > 0) {
2421 score *= 3;
2422 }
2423
2424 return score;
2425 };
2426
2427 for (var i=0; i<matches.length; i++) {
2428 matches[i].__resultScore = _resultScoreFn(matches[i]);
2429 }
2430
2431 matches.sort(function(a,b){
2432 var n = b.__resultScore - a.__resultScore;
2433 if (n == 0) // lexicographical sort if scores are the same
2434 n = (a.label < b.label) ? -1 : 1;
2435 return n;
2436 });
2437}
2438
Scott Main7e447ed2013-02-19 17:22:37 -08002439/* Order the result list based on match quality */
Scott Main0e76e7e2013-03-12 10:24:07 -07002440function rank_autocomplete_api_results(query, matches) {
Scott Mainf5089842012-08-14 16:31:07 -07002441 query = query || '';
Scott Main7e447ed2013-02-19 17:22:37 -08002442 if (!matches || !matches.length)
Scott Mainf5089842012-08-14 16:31:07 -07002443 return;
2444
2445 // helper function that gets the last occurence index of the given regex
2446 // in the given string, or -1 if not found
2447 var _lastSearch = function(s, re) {
2448 if (s == '')
2449 return -1;
2450 var l = -1;
2451 var tmp;
2452 while ((tmp = s.search(re)) >= 0) {
2453 if (l < 0) l = 0;
2454 l += tmp;
2455 s = s.substr(tmp + 1);
2456 }
2457 return l;
2458 };
2459
2460 // helper function that counts the occurrences of a given character in
2461 // a given string
2462 var _countChar = function(s, c) {
2463 var n = 0;
2464 for (var i=0; i<s.length; i++)
2465 if (s.charAt(i) == c) ++n;
2466 return n;
2467 };
2468
2469 var queryLower = query.toLowerCase();
2470 var queryAlnum = (queryLower.match(/\w+/) || [''])[0];
2471 var partPrefixAlnumRE = new RegExp('\\b' + queryAlnum);
2472 var partExactAlnumRE = new RegExp('\\b' + queryAlnum + '\\b');
2473
2474 var _resultScoreFn = function(result) {
2475 // scores are calculated based on exact and prefix matches,
2476 // and then number of path separators (dots) from the last
2477 // match (i.e. favoring classes and deep package names)
2478 var score = 1.0;
2479 var labelLower = result.label.toLowerCase();
2480 var t;
2481 t = _lastSearch(labelLower, partExactAlnumRE);
2482 if (t >= 0) {
2483 // exact part match
2484 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2485 score *= 200 / (partsAfter + 1);
2486 } else {
2487 t = _lastSearch(labelLower, partPrefixAlnumRE);
2488 if (t >= 0) {
2489 // part prefix match
2490 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2491 score *= 20 / (partsAfter + 1);
2492 }
2493 }
2494
2495 return score;
2496 };
2497
Scott Main7e447ed2013-02-19 17:22:37 -08002498 for (var i=0; i<matches.length; i++) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002499 // if the API is deprecated, default score is 0; otherwise, perform scoring
2500 if (matches[i].deprecated == "true") {
2501 matches[i].__resultScore = 0;
2502 } else {
2503 matches[i].__resultScore = _resultScoreFn(matches[i]);
2504 }
Scott Mainf5089842012-08-14 16:31:07 -07002505 }
2506
Scott Main7e447ed2013-02-19 17:22:37 -08002507 matches.sort(function(a,b){
Scott Mainf5089842012-08-14 16:31:07 -07002508 var n = b.__resultScore - a.__resultScore;
2509 if (n == 0) // lexicographical sort if scores are the same
2510 n = (a.label < b.label) ? -1 : 1;
2511 return n;
2512 });
2513}
2514
Scott Main7e447ed2013-02-19 17:22:37 -08002515/* Add emphasis to part of string that matches query */
Scott Mainf5089842012-08-14 16:31:07 -07002516function highlight_autocomplete_result_labels(query) {
2517 query = query || '';
Scott Main7e447ed2013-02-19 17:22:37 -08002518 if ((!gMatches || !gMatches.length) && (!gGoogleMatches || !gGoogleMatches.length))
Scott Mainf5089842012-08-14 16:31:07 -07002519 return;
2520
2521 var queryLower = query.toLowerCase();
2522 var queryAlnumDot = (queryLower.match(/[\w\.]+/) || [''])[0];
2523 var queryRE = new RegExp(
2524 '(' + queryAlnumDot.replace(/\./g, '\\.') + ')', 'ig');
2525 for (var i=0; i<gMatches.length; i++) {
2526 gMatches[i].__hilabel = gMatches[i].label.replace(
2527 queryRE, '<b>$1</b>');
2528 }
Scott Main7e447ed2013-02-19 17:22:37 -08002529 for (var i=0; i<gGoogleMatches.length; i++) {
2530 gGoogleMatches[i].__hilabel = gGoogleMatches[i].label.replace(
2531 queryRE, '<b>$1</b>');
2532 }
Scott Mainf5089842012-08-14 16:31:07 -07002533}
2534
2535function search_focus_changed(obj, focused)
2536{
Scott Main3b90aff2013-08-01 18:09:35 -07002537 if (!focused) {
Scott Mainf5089842012-08-14 16:31:07 -07002538 if(obj.value == ""){
2539 $(".search .close").addClass("hide");
2540 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002541 $(".suggest-card").hide();
Scott Mainf5089842012-08-14 16:31:07 -07002542 }
2543}
2544
2545function submit_search() {
2546 var query = document.getElementById('search_autocomplete').value;
2547 location.hash = 'q=' + query;
2548 loadSearchResults();
Dirk Doughertyc3921652014-05-13 16:55:26 -07002549 $("#searchResults").slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002550 return false;
2551}
2552
2553
2554function hideResults() {
Dirk Doughertyc3921652014-05-13 16:55:26 -07002555 $("#searchResults").slideUp('fast', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002556 $(".search .close").addClass("hide");
2557 location.hash = '';
Scott Main3b90aff2013-08-01 18:09:35 -07002558
Scott Mainf5089842012-08-14 16:31:07 -07002559 $("#search_autocomplete").val("").blur();
Scott Main3b90aff2013-08-01 18:09:35 -07002560
Scott Mainf5089842012-08-14 16:31:07 -07002561 // reset the ajax search callback to nothing, so results don't appear unless ENTER
2562 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {});
Scott Main0e76e7e2013-03-12 10:24:07 -07002563
2564 // forcefully regain key-up event control (previously jacked by search api)
2565 $("#search_autocomplete").keyup(function(event) {
2566 return search_changed(event, false, toRoot);
2567 });
2568
Scott Mainf5089842012-08-14 16:31:07 -07002569 return false;
2570}
2571
2572
2573
2574/* ########################################################## */
2575/* ################ CUSTOM SEARCH ENGINE ################## */
2576/* ########################################################## */
2577
Scott Mainf5089842012-08-14 16:31:07 -07002578var searchControl;
Scott Main0e76e7e2013-03-12 10:24:07 -07002579google.load('search', '1', {"callback" : function() {
2580 searchControl = new google.search.SearchControl();
2581 } });
Scott Mainf5089842012-08-14 16:31:07 -07002582
2583function loadSearchResults() {
2584 document.getElementById("search_autocomplete").style.color = "#000";
2585
Scott Mainf5089842012-08-14 16:31:07 -07002586 searchControl = new google.search.SearchControl();
2587
2588 // use our existing search form and use tabs when multiple searchers are used
2589 drawOptions = new google.search.DrawOptions();
2590 drawOptions.setDrawMode(google.search.SearchControl.DRAW_MODE_TABBED);
2591 drawOptions.setInput(document.getElementById("search_autocomplete"));
2592
2593 // configure search result options
2594 searchOptions = new google.search.SearcherOptions();
2595 searchOptions.setExpandMode(GSearchControl.EXPAND_MODE_OPEN);
2596
2597 // configure each of the searchers, for each tab
2598 devSiteSearcher = new google.search.WebSearch();
2599 devSiteSearcher.setUserDefinedLabel("All");
2600 devSiteSearcher.setSiteRestriction("001482626316274216503:zu90b7s047u");
2601
2602 designSearcher = new google.search.WebSearch();
2603 designSearcher.setUserDefinedLabel("Design");
2604 designSearcher.setSiteRestriction("http://developer.android.com/design/");
2605
2606 trainingSearcher = new google.search.WebSearch();
2607 trainingSearcher.setUserDefinedLabel("Training");
2608 trainingSearcher.setSiteRestriction("http://developer.android.com/training/");
2609
2610 guidesSearcher = new google.search.WebSearch();
2611 guidesSearcher.setUserDefinedLabel("Guides");
2612 guidesSearcher.setSiteRestriction("http://developer.android.com/guide/");
2613
2614 referenceSearcher = new google.search.WebSearch();
2615 referenceSearcher.setUserDefinedLabel("Reference");
2616 referenceSearcher.setSiteRestriction("http://developer.android.com/reference/");
2617
Scott Maindf08ada2012-12-03 08:54:37 -08002618 googleSearcher = new google.search.WebSearch();
2619 googleSearcher.setUserDefinedLabel("Google Services");
2620 googleSearcher.setSiteRestriction("http://developer.android.com/google/");
2621
Scott Mainf5089842012-08-14 16:31:07 -07002622 blogSearcher = new google.search.WebSearch();
2623 blogSearcher.setUserDefinedLabel("Blog");
2624 blogSearcher.setSiteRestriction("http://android-developers.blogspot.com");
2625
2626 // add each searcher to the search control
2627 searchControl.addSearcher(devSiteSearcher, searchOptions);
2628 searchControl.addSearcher(designSearcher, searchOptions);
2629 searchControl.addSearcher(trainingSearcher, searchOptions);
2630 searchControl.addSearcher(guidesSearcher, searchOptions);
2631 searchControl.addSearcher(referenceSearcher, searchOptions);
Scott Maindf08ada2012-12-03 08:54:37 -08002632 searchControl.addSearcher(googleSearcher, searchOptions);
Scott Mainf5089842012-08-14 16:31:07 -07002633 searchControl.addSearcher(blogSearcher, searchOptions);
2634
2635 // configure result options
2636 searchControl.setResultSetSize(google.search.Search.LARGE_RESULTSET);
2637 searchControl.setLinkTarget(google.search.Search.LINK_TARGET_SELF);
2638 searchControl.setTimeoutInterval(google.search.SearchControl.TIMEOUT_SHORT);
2639 searchControl.setNoResultsString(google.search.SearchControl.NO_RESULTS_DEFAULT_STRING);
2640
2641 // upon ajax search, refresh the url and search title
2642 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {
2643 updateResultTitle(query);
2644 var query = document.getElementById('search_autocomplete').value;
2645 location.hash = 'q=' + query;
2646 });
2647
Scott Mainde295272013-03-25 15:48:35 -07002648 // once search results load, set up click listeners
2649 searchControl.setSearchCompleteCallback(this, function(control, searcher, query) {
2650 addResultClickListeners();
2651 });
2652
Scott Mainf5089842012-08-14 16:31:07 -07002653 // draw the search results box
2654 searchControl.draw(document.getElementById("leftSearchControl"), drawOptions);
2655
2656 // get query and execute the search
2657 searchControl.execute(decodeURI(getQuery(location.hash)));
2658
2659 document.getElementById("search_autocomplete").focus();
2660 addTabListeners();
2661}
2662// End of loadSearchResults
2663
2664
2665google.setOnLoadCallback(function(){
2666 if (location.hash.indexOf("q=") == -1) {
2667 // if there's no query in the url, don't search and make sure results are hidden
2668 $('#searchResults').hide();
2669 return;
2670 } else {
2671 // first time loading search results for this page
Dirk Doughertyc3921652014-05-13 16:55:26 -07002672 $('#searchResults').slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002673 $(".search .close").removeClass("hide");
2674 loadSearchResults();
2675 }
2676}, true);
2677
smain@google.com9a818f52014-10-03 09:25:59 -07002678/* Adjust the scroll position to account for sticky header, only if the hash matches an id.
2679 This does not handle <a name=""> tags. Some CSS fixes those, but only for reference docs. */
Scott Mainb16376f2014-05-21 20:35:47 -07002680function offsetScrollForSticky() {
smain@google.com11fc0092014-10-16 22:10:00 -07002681 // Ignore if there's no search bar (some special pages have no header)
2682 if ($("#search-container").length < 1) return;
2683
smain@google.com3b77ab52014-06-17 11:57:27 -07002684 var hash = escape(location.hash.substr(1));
2685 var $matchingElement = $("#"+hash);
smain@google.com08f336ea2014-10-03 17:40:00 -07002686 // Sanity check that there's an element with that ID on the page
2687 if ($matchingElement.length) {
Scott Mainb16376f2014-05-21 20:35:47 -07002688 // If the position of the target element is near the top of the page (<20px, where we expect it
2689 // to be because we need to move it down 60px to become in view), then move it down 60px
2690 if (Math.abs($matchingElement.offset().top - $(window).scrollTop()) < 20) {
2691 $(window).scrollTop($(window).scrollTop() - 60);
Scott Mainb16376f2014-05-21 20:35:47 -07002692 }
2693 }
2694}
2695
Scott Mainf5089842012-08-14 16:31:07 -07002696// when an event on the browser history occurs (back, forward, load) requery hash and do search
2697$(window).hashchange( function(){
smain@google.com2f077192014-10-09 18:04:10 -07002698 // Ignore if there's no search bar (some special pages have no header)
2699 if ($("#search-container").length < 1) return;
2700
Dirk Doughertyc3921652014-05-13 16:55:26 -07002701 // If the hash isn't a search query or there's an error in the query,
2702 // then adjust the scroll position to account for sticky header, then exit.
Scott Mainf5089842012-08-14 16:31:07 -07002703 if ((location.hash.indexOf("q=") == -1) || (query == "undefined")) {
2704 // If the results pane is open, close it.
2705 if (!$("#searchResults").is(":hidden")) {
2706 hideResults();
2707 }
Scott Mainb16376f2014-05-21 20:35:47 -07002708 offsetScrollForSticky();
Scott Mainf5089842012-08-14 16:31:07 -07002709 return;
2710 }
2711
2712 // Otherwise, we have a search to do
2713 var query = decodeURI(getQuery(location.hash));
2714 searchControl.execute(query);
Dirk Doughertyc3921652014-05-13 16:55:26 -07002715 $('#searchResults').slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002716 $("#search_autocomplete").focus();
2717 $(".search .close").removeClass("hide");
2718
2719 updateResultTitle(query);
2720});
2721
2722function updateResultTitle(query) {
2723 $("#searchTitle").html("Results for <em>" + escapeHTML(query) + "</em>");
2724}
2725
2726// forcefully regain key-up event control (previously jacked by search api)
2727$("#search_autocomplete").keyup(function(event) {
2728 return search_changed(event, false, toRoot);
2729});
2730
2731// add event listeners to each tab so we can track the browser history
2732function addTabListeners() {
2733 var tabHeaders = $(".gsc-tabHeader");
2734 for (var i = 0; i < tabHeaders.length; i++) {
2735 $(tabHeaders[i]).attr("id",i).click(function() {
2736 /*
2737 // make a copy of the page numbers for the search left pane
2738 setTimeout(function() {
2739 // remove any residual page numbers
2740 $('#searchResults .gsc-tabsArea .gsc-cursor-box.gs-bidi-start-align').remove();
Scott Main3b90aff2013-08-01 18:09:35 -07002741 // move the page numbers to the left position; make a clone,
Scott Mainf5089842012-08-14 16:31:07 -07002742 // because the element is drawn to the DOM only once
Scott Main3b90aff2013-08-01 18:09:35 -07002743 // and because we're going to remove it (previous line),
2744 // we need it to be available to move again as the user navigates
Scott Mainf5089842012-08-14 16:31:07 -07002745 $('#searchResults .gsc-webResult .gsc-cursor-box.gs-bidi-start-align:visible')
2746 .clone().appendTo('#searchResults .gsc-tabsArea');
2747 }, 200);
2748 */
2749 });
2750 }
2751 setTimeout(function(){$(tabHeaders[0]).click()},200);
2752}
2753
Scott Mainde295272013-03-25 15:48:35 -07002754// add analytics tracking events to each result link
2755function addResultClickListeners() {
2756 $("#searchResults a.gs-title").each(function(index, link) {
2757 // When user clicks enter for Google search results, track it
2758 $(link).click(function() {
smain@google.comed677d72014-12-12 10:19:17 -08002759 ga('send', 'event', 'Google Click', 'clicked: ' + $(this).attr('href'),
2760 'query: ' + $("#search_autocomplete").val().toLowerCase());
Scott Mainde295272013-03-25 15:48:35 -07002761 });
2762 });
2763}
2764
Scott Mainf5089842012-08-14 16:31:07 -07002765
2766function getQuery(hash) {
2767 var queryParts = hash.split('=');
2768 return queryParts[1];
2769}
2770
2771/* returns the given string with all HTML brackets converted to entities
2772 TODO: move this to the site's JS library */
2773function escapeHTML(string) {
2774 return string.replace(/</g,"&lt;")
2775 .replace(/>/g,"&gt;");
2776}
2777
2778
2779
2780
2781
2782
2783
2784/* ######################################################## */
2785/* ################# JAVADOC REFERENCE ################### */
2786/* ######################################################## */
2787
Scott Main65511c02012-09-07 15:51:32 -07002788/* Initialize some droiddoc stuff, but only if we're in the reference */
Scott Main52dd2062013-08-15 12:22:28 -07002789if (location.pathname.indexOf("/reference") == 0) {
2790 if(!(location.pathname.indexOf("/reference-gms/packages.html") == 0)
2791 && !(location.pathname.indexOf("/reference-gcm/packages.html") == 0)
2792 && !(location.pathname.indexOf("/reference/com/google") == 0)) {
Robert Ly67d75f12012-12-03 12:53:42 -08002793 $(document).ready(function() {
2794 // init available apis based on user pref
2795 changeApiLevel();
2796 initSidenavHeightResize()
2797 });
2798 }
Scott Main65511c02012-09-07 15:51:32 -07002799}
Scott Mainf5089842012-08-14 16:31:07 -07002800
2801var API_LEVEL_COOKIE = "api_level";
2802var minLevel = 1;
2803var maxLevel = 1;
2804
2805/******* SIDENAV DIMENSIONS ************/
Scott Main3b90aff2013-08-01 18:09:35 -07002806
Scott Mainf5089842012-08-14 16:31:07 -07002807 function initSidenavHeightResize() {
2808 // Change the drag bar size to nicely fit the scrollbar positions
2809 var $dragBar = $(".ui-resizable-s");
2810 $dragBar.css({'width': $dragBar.parent().width() - 5 + "px"});
Scott Main3b90aff2013-08-01 18:09:35 -07002811
2812 $( "#resize-packages-nav" ).resizable({
Scott Mainf5089842012-08-14 16:31:07 -07002813 containment: "#nav-panels",
2814 handles: "s",
2815 alsoResize: "#packages-nav",
2816 resize: function(event, ui) { resizeNav(); }, /* resize the nav while dragging */
2817 stop: function(event, ui) { saveNavPanels(); } /* once stopped, save the sizes to cookie */
2818 });
Scott Main3b90aff2013-08-01 18:09:35 -07002819
Scott Mainf5089842012-08-14 16:31:07 -07002820 }
Scott Main3b90aff2013-08-01 18:09:35 -07002821
Scott Mainf5089842012-08-14 16:31:07 -07002822function updateSidenavFixedWidth() {
Scott Mainf5257812014-05-22 17:26:38 -07002823 if (!sticky) return;
Scott Mainf5089842012-08-14 16:31:07 -07002824 $('#devdoc-nav').css({
2825 'width' : $('#side-nav').css('width'),
2826 'margin' : $('#side-nav').css('margin')
2827 });
2828 $('#devdoc-nav a.totop').css({'display':'block','width':$("#nav").innerWidth()+'px'});
Scott Main3b90aff2013-08-01 18:09:35 -07002829
Scott Mainf5089842012-08-14 16:31:07 -07002830 initSidenavHeightResize();
2831}
2832
2833function updateSidenavFullscreenWidth() {
Scott Mainf5257812014-05-22 17:26:38 -07002834 if (!sticky) return;
Scott Mainf5089842012-08-14 16:31:07 -07002835 $('#devdoc-nav').css({
2836 'width' : $('#side-nav').css('width'),
2837 'margin' : $('#side-nav').css('margin')
2838 });
2839 $('#devdoc-nav .totop').css({'left': 'inherit'});
Scott Main3b90aff2013-08-01 18:09:35 -07002840
Scott Mainf5089842012-08-14 16:31:07 -07002841 initSidenavHeightResize();
2842}
2843
2844function buildApiLevelSelector() {
2845 maxLevel = SINCE_DATA.length;
2846 var userApiLevel = parseInt(readCookie(API_LEVEL_COOKIE));
2847 userApiLevel = userApiLevel == 0 ? maxLevel : userApiLevel; // If there's no cookie (zero), use the max by default
2848
2849 minLevel = parseInt($("#doc-api-level").attr("class"));
2850 // Handle provisional api levels; the provisional level will always be the highest possible level
2851 // Provisional api levels will also have a length; other stuff that's just missing a level won't,
2852 // so leave those kinds of entities at the default level of 1 (for example, the R.styleable class)
2853 if (isNaN(minLevel) && minLevel.length) {
2854 minLevel = maxLevel;
2855 }
2856 var select = $("#apiLevelSelector").html("").change(changeApiLevel);
2857 for (var i = maxLevel-1; i >= 0; i--) {
2858 var option = $("<option />").attr("value",""+SINCE_DATA[i]).append(""+SINCE_DATA[i]);
2859 // if (SINCE_DATA[i] < minLevel) option.addClass("absent"); // always false for strings (codenames)
2860 select.append(option);
2861 }
2862
2863 // get the DOM element and use setAttribute cuz IE6 fails when using jquery .attr('selected',true)
2864 var selectedLevelItem = $("#apiLevelSelector option[value='"+userApiLevel+"']").get(0);
2865 selectedLevelItem.setAttribute('selected',true);
2866}
2867
2868function changeApiLevel() {
2869 maxLevel = SINCE_DATA.length;
2870 var selectedLevel = maxLevel;
2871
2872 selectedLevel = parseInt($("#apiLevelSelector option:selected").val());
2873 toggleVisisbleApis(selectedLevel, "body");
2874
smain@google.com6bdcb982014-11-14 11:53:07 -08002875 writeCookie(API_LEVEL_COOKIE, selectedLevel, null);
Scott Mainf5089842012-08-14 16:31:07 -07002876
2877 if (selectedLevel < minLevel) {
2878 var thing = ($("#jd-header").html().indexOf("package") != -1) ? "package" : "class";
Scott Main8f24ca82012-11-16 10:34:22 -08002879 $("#naMessage").show().html("<div><p><strong>This " + thing
2880 + " requires API level " + minLevel + " or higher.</strong></p>"
2881 + "<p>This document is hidden because your selected API level for the documentation is "
2882 + selectedLevel + ". You can change the documentation API level with the selector "
2883 + "above the left navigation.</p>"
2884 + "<p>For more information about specifying the API level your app requires, "
2885 + "read <a href='" + toRoot + "training/basics/supporting-devices/platforms.html'"
2886 + ">Supporting Different Platform Versions</a>.</p>"
2887 + "<input type='button' value='OK, make this page visible' "
2888 + "title='Change the API level to " + minLevel + "' "
2889 + "onclick='$(\"#apiLevelSelector\").val(\"" + minLevel + "\");changeApiLevel();' />"
2890 + "</div>");
Scott Mainf5089842012-08-14 16:31:07 -07002891 } else {
2892 $("#naMessage").hide();
2893 }
2894}
2895
2896function toggleVisisbleApis(selectedLevel, context) {
2897 var apis = $(".api",context);
2898 apis.each(function(i) {
2899 var obj = $(this);
2900 var className = obj.attr("class");
2901 var apiLevelIndex = className.lastIndexOf("-")+1;
2902 var apiLevelEndIndex = className.indexOf(" ", apiLevelIndex);
2903 apiLevelEndIndex = apiLevelEndIndex != -1 ? apiLevelEndIndex : className.length;
2904 var apiLevel = className.substring(apiLevelIndex, apiLevelEndIndex);
2905 if (apiLevel.length == 0) { // for odd cases when the since data is actually missing, just bail
2906 return;
2907 }
2908 apiLevel = parseInt(apiLevel);
2909
2910 // Handle provisional api levels; if this item's level is the provisional one, set it to the max
2911 var selectedLevelNum = parseInt(selectedLevel)
2912 var apiLevelNum = parseInt(apiLevel);
2913 if (isNaN(apiLevelNum)) {
2914 apiLevelNum = maxLevel;
2915 }
2916
2917 // Grey things out that aren't available and give a tooltip title
2918 if (apiLevelNum > selectedLevelNum) {
2919 obj.addClass("absent").attr("title","Requires API Level \""
Scott Main641c2c22013-10-31 14:48:45 -07002920 + apiLevel + "\" or higher. To reveal, change the target API level "
2921 + "above the left navigation.");
Scott Main3b90aff2013-08-01 18:09:35 -07002922 }
Scott Mainf5089842012-08-14 16:31:07 -07002923 else obj.removeClass("absent").removeAttr("title");
2924 });
2925}
2926
2927
2928
2929
2930/* ################# SIDENAV TREE VIEW ################### */
2931
2932function new_node(me, mom, text, link, children_data, api_level)
2933{
2934 var node = new Object();
2935 node.children = Array();
2936 node.children_data = children_data;
2937 node.depth = mom.depth + 1;
2938
2939 node.li = document.createElement("li");
2940 mom.get_children_ul().appendChild(node.li);
2941
2942 node.label_div = document.createElement("div");
2943 node.label_div.className = "label";
2944 if (api_level != null) {
2945 $(node.label_div).addClass("api");
2946 $(node.label_div).addClass("api-level-"+api_level);
2947 }
2948 node.li.appendChild(node.label_div);
2949
2950 if (children_data != null) {
2951 node.expand_toggle = document.createElement("a");
2952 node.expand_toggle.href = "javascript:void(0)";
2953 node.expand_toggle.onclick = function() {
2954 if (node.expanded) {
2955 $(node.get_children_ul()).slideUp("fast");
2956 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2957 node.expanded = false;
2958 } else {
2959 expand_node(me, node);
2960 }
2961 };
2962 node.label_div.appendChild(node.expand_toggle);
2963
2964 node.plus_img = document.createElement("img");
2965 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2966 node.plus_img.className = "plus";
2967 node.plus_img.width = "8";
2968 node.plus_img.border = "0";
2969 node.expand_toggle.appendChild(node.plus_img);
2970
2971 node.expanded = false;
2972 }
2973
2974 var a = document.createElement("a");
2975 node.label_div.appendChild(a);
2976 node.label = document.createTextNode(text);
2977 a.appendChild(node.label);
2978 if (link) {
2979 a.href = me.toroot + link;
2980 } else {
2981 if (children_data != null) {
2982 a.className = "nolink";
2983 a.href = "javascript:void(0)";
2984 a.onclick = node.expand_toggle.onclick;
2985 // This next line shouldn't be necessary. I'll buy a beer for the first
2986 // person who figures out how to remove this line and have the link
2987 // toggle shut on the first try. --joeo@android.com
2988 node.expanded = false;
2989 }
2990 }
Scott Main3b90aff2013-08-01 18:09:35 -07002991
Scott Mainf5089842012-08-14 16:31:07 -07002992
2993 node.children_ul = null;
2994 node.get_children_ul = function() {
2995 if (!node.children_ul) {
2996 node.children_ul = document.createElement("ul");
2997 node.children_ul.className = "children_ul";
2998 node.children_ul.style.display = "none";
2999 node.li.appendChild(node.children_ul);
3000 }
3001 return node.children_ul;
3002 };
3003
3004 return node;
3005}
3006
Robert Lyd2dd6e52012-11-29 21:28:48 -08003007
3008
3009
Scott Mainf5089842012-08-14 16:31:07 -07003010function expand_node(me, node)
3011{
3012 if (node.children_data && !node.expanded) {
3013 if (node.children_visited) {
3014 $(node.get_children_ul()).slideDown("fast");
3015 } else {
3016 get_node(me, node);
3017 if ($(node.label_div).hasClass("absent")) {
3018 $(node.get_children_ul()).addClass("absent");
Scott Main3b90aff2013-08-01 18:09:35 -07003019 }
Scott Mainf5089842012-08-14 16:31:07 -07003020 $(node.get_children_ul()).slideDown("fast");
3021 }
3022 node.plus_img.src = me.toroot + "assets/images/triangle-opened-small.png";
3023 node.expanded = true;
3024
3025 // perform api level toggling because new nodes are new to the DOM
3026 var selectedLevel = $("#apiLevelSelector option:selected").val();
3027 toggleVisisbleApis(selectedLevel, "#side-nav");
3028 }
3029}
3030
3031function get_node(me, mom)
3032{
3033 mom.children_visited = true;
3034 for (var i in mom.children_data) {
3035 var node_data = mom.children_data[i];
3036 mom.children[i] = new_node(me, mom, node_data[0], node_data[1],
3037 node_data[2], node_data[3]);
3038 }
3039}
3040
3041function this_page_relative(toroot)
3042{
3043 var full = document.location.pathname;
3044 var file = "";
3045 if (toroot.substr(0, 1) == "/") {
3046 if (full.substr(0, toroot.length) == toroot) {
3047 return full.substr(toroot.length);
3048 } else {
3049 // the file isn't under toroot. Fail.
3050 return null;
3051 }
3052 } else {
3053 if (toroot != "./") {
3054 toroot = "./" + toroot;
3055 }
3056 do {
3057 if (toroot.substr(toroot.length-3, 3) == "../" || toroot == "./") {
3058 var pos = full.lastIndexOf("/");
3059 file = full.substr(pos) + file;
3060 full = full.substr(0, pos);
3061 toroot = toroot.substr(0, toroot.length-3);
3062 }
3063 } while (toroot != "" && toroot != "/");
3064 return file.substr(1);
3065 }
3066}
3067
3068function find_page(url, data)
3069{
3070 var nodes = data;
3071 var result = null;
3072 for (var i in nodes) {
3073 var d = nodes[i];
3074 if (d[1] == url) {
3075 return new Array(i);
3076 }
3077 else if (d[2] != null) {
3078 result = find_page(url, d[2]);
3079 if (result != null) {
3080 return (new Array(i).concat(result));
3081 }
3082 }
3083 }
3084 return null;
3085}
3086
Scott Mainf5089842012-08-14 16:31:07 -07003087function init_default_navtree(toroot) {
Scott Main25e73002013-03-27 15:24:06 -07003088 // load json file for navtree data
3089 $.getScript(toRoot + 'navtree_data.js', function(data, textStatus, jqxhr) {
3090 // when the file is loaded, initialize the tree
3091 if(jqxhr.status === 200) {
3092 init_navtree("tree-list", toroot, NAVTREE_DATA);
3093 }
3094 });
Scott Main3b90aff2013-08-01 18:09:35 -07003095
Scott Mainf5089842012-08-14 16:31:07 -07003096 // perform api level toggling because because the whole tree is new to the DOM
3097 var selectedLevel = $("#apiLevelSelector option:selected").val();
3098 toggleVisisbleApis(selectedLevel, "#side-nav");
3099}
3100
3101function init_navtree(navtree_id, toroot, root_nodes)
3102{
3103 var me = new Object();
3104 me.toroot = toroot;
3105 me.node = new Object();
3106
3107 me.node.li = document.getElementById(navtree_id);
3108 me.node.children_data = root_nodes;
3109 me.node.children = new Array();
3110 me.node.children_ul = document.createElement("ul");
3111 me.node.get_children_ul = function() { return me.node.children_ul; };
3112 //me.node.children_ul.className = "children_ul";
3113 me.node.li.appendChild(me.node.children_ul);
3114 me.node.depth = 0;
3115
3116 get_node(me, me.node);
3117
3118 me.this_page = this_page_relative(toroot);
3119 me.breadcrumbs = find_page(me.this_page, root_nodes);
3120 if (me.breadcrumbs != null && me.breadcrumbs.length != 0) {
3121 var mom = me.node;
3122 for (var i in me.breadcrumbs) {
3123 var j = me.breadcrumbs[i];
3124 mom = mom.children[j];
3125 expand_node(me, mom);
3126 }
3127 mom.label_div.className = mom.label_div.className + " selected";
3128 addLoadEvent(function() {
3129 scrollIntoView("nav-tree");
3130 });
3131 }
3132}
3133
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003134
3135
3136
3137
3138
3139
3140
Robert Lyd2dd6e52012-11-29 21:28:48 -08003141/* TODO: eliminate redundancy with non-google functions */
3142function init_google_navtree(navtree_id, toroot, root_nodes)
3143{
3144 var me = new Object();
3145 me.toroot = toroot;
3146 me.node = new Object();
3147
3148 me.node.li = document.getElementById(navtree_id);
3149 me.node.children_data = root_nodes;
3150 me.node.children = new Array();
3151 me.node.children_ul = document.createElement("ul");
3152 me.node.get_children_ul = function() { return me.node.children_ul; };
3153 //me.node.children_ul.className = "children_ul";
3154 me.node.li.appendChild(me.node.children_ul);
3155 me.node.depth = 0;
3156
3157 get_google_node(me, me.node);
Robert Lyd2dd6e52012-11-29 21:28:48 -08003158}
3159
3160function new_google_node(me, mom, text, link, children_data, api_level)
3161{
3162 var node = new Object();
3163 var child;
3164 node.children = Array();
3165 node.children_data = children_data;
3166 node.depth = mom.depth + 1;
3167 node.get_children_ul = function() {
3168 if (!node.children_ul) {
Scott Main3b90aff2013-08-01 18:09:35 -07003169 node.children_ul = document.createElement("ul");
3170 node.children_ul.className = "tree-list-children";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003171 node.li.appendChild(node.children_ul);
3172 }
3173 return node.children_ul;
3174 };
3175 node.li = document.createElement("li");
3176
3177 mom.get_children_ul().appendChild(node.li);
Scott Main3b90aff2013-08-01 18:09:35 -07003178
3179
Robert Lyd2dd6e52012-11-29 21:28:48 -08003180 if(link) {
3181 child = document.createElement("a");
3182
3183 }
3184 else {
3185 child = document.createElement("span");
Scott Mainac71b2b2012-11-30 14:40:58 -08003186 child.className = "tree-list-subtitle";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003187
3188 }
3189 if (children_data != null) {
3190 node.li.className="nav-section";
3191 node.label_div = document.createElement("div");
Scott Main3b90aff2013-08-01 18:09:35 -07003192 node.label_div.className = "nav-section-header-ref";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003193 node.li.appendChild(node.label_div);
3194 get_google_node(me, node);
3195 node.label_div.appendChild(child);
3196 }
3197 else {
3198 node.li.appendChild(child);
3199 }
3200 if(link) {
3201 child.href = me.toroot + link;
3202 }
3203 node.label = document.createTextNode(text);
3204 child.appendChild(node.label);
3205
3206 node.children_ul = null;
3207
3208 return node;
3209}
3210
3211function get_google_node(me, mom)
3212{
3213 mom.children_visited = true;
3214 var linkText;
3215 for (var i in mom.children_data) {
3216 var node_data = mom.children_data[i];
3217 linkText = node_data[0];
3218
3219 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3220 linkText = linkText.substr(19, linkText.length);
3221 }
3222 mom.children[i] = new_google_node(me, mom, linkText, node_data[1],
3223 node_data[2], node_data[3]);
3224 }
3225}
Scott Mainad08f072013-08-20 16:49:57 -07003226
3227
3228
3229
3230
3231
3232/****** NEW version of script to build google and sample navs dynamically ******/
3233// TODO: update Google reference docs to tolerate this new implementation
3234
Scott Maine624b3f2013-09-12 12:56:41 -07003235var NODE_NAME = 0;
3236var NODE_HREF = 1;
3237var NODE_GROUP = 2;
3238var NODE_TAGS = 3;
3239var NODE_CHILDREN = 4;
3240
Scott Mainad08f072013-08-20 16:49:57 -07003241function init_google_navtree2(navtree_id, data)
3242{
3243 var $containerUl = $("#"+navtree_id);
Scott Mainad08f072013-08-20 16:49:57 -07003244 for (var i in data) {
3245 var node_data = data[i];
3246 $containerUl.append(new_google_node2(node_data));
3247 }
3248
Scott Main70557ee2013-10-30 14:47:40 -07003249 // Make all third-generation list items 'sticky' to prevent them from collapsing
3250 $containerUl.find('li li li.nav-section').addClass('sticky');
3251
Scott Mainad08f072013-08-20 16:49:57 -07003252 initExpandableNavItems("#"+navtree_id);
3253}
3254
3255function new_google_node2(node_data)
3256{
Scott Maine624b3f2013-09-12 12:56:41 -07003257 var linkText = node_data[NODE_NAME];
Scott Mainad08f072013-08-20 16:49:57 -07003258 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3259 linkText = linkText.substr(19, linkText.length);
3260 }
3261 var $li = $('<li>');
3262 var $a;
Scott Maine624b3f2013-09-12 12:56:41 -07003263 if (node_data[NODE_HREF] != null) {
Scott Main70557ee2013-10-30 14:47:40 -07003264 $a = $('<a href="' + toRoot + node_data[NODE_HREF] + '" title="' + linkText + '" >'
3265 + linkText + '</a>');
Scott Mainad08f072013-08-20 16:49:57 -07003266 } else {
Scott Main70557ee2013-10-30 14:47:40 -07003267 $a = $('<a href="#" onclick="return false;" title="' + linkText + '" >'
3268 + linkText + '/</a>');
Scott Mainad08f072013-08-20 16:49:57 -07003269 }
3270 var $childUl = $('<ul>');
Scott Maine624b3f2013-09-12 12:56:41 -07003271 if (node_data[NODE_CHILDREN] != null) {
Scott Mainad08f072013-08-20 16:49:57 -07003272 $li.addClass("nav-section");
3273 $a = $('<div class="nav-section-header">').append($a);
Scott Maine624b3f2013-09-12 12:56:41 -07003274 if (node_data[NODE_HREF] == null) $a.addClass('empty');
Scott Mainad08f072013-08-20 16:49:57 -07003275
Scott Maine624b3f2013-09-12 12:56:41 -07003276 for (var i in node_data[NODE_CHILDREN]) {
3277 var child_node_data = node_data[NODE_CHILDREN][i];
Scott Mainad08f072013-08-20 16:49:57 -07003278 $childUl.append(new_google_node2(child_node_data));
3279 }
3280 $li.append($childUl);
3281 }
3282 $li.prepend($a);
3283
3284 return $li;
3285}
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
Robert Lyd2dd6e52012-11-29 21:28:48 -08003297function showGoogleRefTree() {
3298 init_default_google_navtree(toRoot);
3299 init_default_gcm_navtree(toRoot);
Robert Lyd2dd6e52012-11-29 21:28:48 -08003300}
3301
3302function init_default_google_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07003303 // load json file for navtree data
3304 $.getScript(toRoot + 'gms_navtree_data.js', function(data, textStatus, jqxhr) {
3305 // when the file is loaded, initialize the tree
3306 if(jqxhr.status === 200) {
3307 init_google_navtree("gms-tree-list", toroot, GMS_NAVTREE_DATA);
3308 highlightSidenav();
3309 resizeNav();
3310 }
3311 });
Robert Lyd2dd6e52012-11-29 21:28:48 -08003312}
3313
3314function init_default_gcm_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07003315 // load json file for navtree data
3316 $.getScript(toRoot + 'gcm_navtree_data.js', function(data, textStatus, jqxhr) {
3317 // when the file is loaded, initialize the tree
3318 if(jqxhr.status === 200) {
3319 init_google_navtree("gcm-tree-list", toroot, GCM_NAVTREE_DATA);
3320 highlightSidenav();
3321 resizeNav();
3322 }
3323 });
Robert Lyd2dd6e52012-11-29 21:28:48 -08003324}
3325
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003326function showSamplesRefTree() {
3327 init_default_samples_navtree(toRoot);
3328}
3329
3330function init_default_samples_navtree(toroot) {
3331 // load json file for navtree data
3332 $.getScript(toRoot + 'samples_navtree_data.js', function(data, textStatus, jqxhr) {
3333 // when the file is loaded, initialize the tree
3334 if(jqxhr.status === 200) {
Scott Mainf1435b72013-10-30 16:27:38 -07003335 // hack to remove the "about the samples" link then put it back in
3336 // after we nuke the list to remove the dummy static list of samples
3337 var $firstLi = $("#nav.samples-nav > li:first-child").clone();
3338 $("#nav.samples-nav").empty();
3339 $("#nav.samples-nav").append($firstLi);
3340
Scott Mainad08f072013-08-20 16:49:57 -07003341 init_google_navtree2("nav.samples-nav", SAMPLES_NAVTREE_DATA);
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003342 highlightSidenav();
3343 resizeNav();
Scott Main03aca9a2013-10-31 07:20:55 -07003344 if ($("#jd-content #samples").length) {
3345 showSamples();
3346 }
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003347 }
3348 });
3349}
3350
Scott Mainf5089842012-08-14 16:31:07 -07003351/* TOGGLE INHERITED MEMBERS */
3352
3353/* Toggle an inherited class (arrow toggle)
3354 * @param linkObj The link that was clicked.
3355 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3356 * 'null' to simply toggle.
3357 */
3358function toggleInherited(linkObj, expand) {
3359 var base = linkObj.getAttribute("id");
3360 var list = document.getElementById(base + "-list");
3361 var summary = document.getElementById(base + "-summary");
3362 var trigger = document.getElementById(base + "-trigger");
3363 var a = $(linkObj);
3364 if ( (expand == null && a.hasClass("closed")) || expand ) {
3365 list.style.display = "none";
3366 summary.style.display = "block";
3367 trigger.src = toRoot + "assets/images/triangle-opened.png";
3368 a.removeClass("closed");
3369 a.addClass("opened");
3370 } else if ( (expand == null && a.hasClass("opened")) || (expand == false) ) {
3371 list.style.display = "block";
3372 summary.style.display = "none";
3373 trigger.src = toRoot + "assets/images/triangle-closed.png";
3374 a.removeClass("opened");
3375 a.addClass("closed");
3376 }
3377 return false;
3378}
3379
3380/* Toggle all inherited classes in a single table (e.g. all inherited methods)
3381 * @param linkObj The link that was clicked.
3382 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3383 * 'null' to simply toggle.
3384 */
3385function toggleAllInherited(linkObj, expand) {
3386 var a = $(linkObj);
3387 var table = $(a.parent().parent().parent()); // ugly way to get table/tbody
3388 var expandos = $(".jd-expando-trigger", table);
3389 if ( (expand == null && a.text() == "[Expand]") || expand ) {
3390 expandos.each(function(i) {
3391 toggleInherited(this, true);
3392 });
3393 a.text("[Collapse]");
3394 } else if ( (expand == null && a.text() == "[Collapse]") || (expand == false) ) {
3395 expandos.each(function(i) {
3396 toggleInherited(this, false);
3397 });
3398 a.text("[Expand]");
3399 }
3400 return false;
3401}
3402
3403/* Toggle all inherited members in the class (link in the class title)
3404 */
3405function toggleAllClassInherited() {
3406 var a = $("#toggleAllClassInherited"); // get toggle link from class title
3407 var toggles = $(".toggle-all", $("#body-content"));
3408 if (a.text() == "[Expand All]") {
3409 toggles.each(function(i) {
3410 toggleAllInherited(this, true);
3411 });
3412 a.text("[Collapse All]");
3413 } else {
3414 toggles.each(function(i) {
3415 toggleAllInherited(this, false);
3416 });
3417 a.text("[Expand All]");
3418 }
3419 return false;
3420}
3421
3422/* Expand all inherited members in the class. Used when initiating page search */
3423function ensureAllInheritedExpanded() {
3424 var toggles = $(".toggle-all", $("#body-content"));
3425 toggles.each(function(i) {
3426 toggleAllInherited(this, true);
3427 });
3428 $("#toggleAllClassInherited").text("[Collapse All]");
3429}
3430
3431
3432/* HANDLE KEY EVENTS
3433 * - Listen for Ctrl+F (Cmd on Mac) and expand all inherited members (to aid page search)
3434 */
3435var agent = navigator['userAgent'].toLowerCase();
3436var mac = agent.indexOf("macintosh") != -1;
3437
3438$(document).keydown( function(e) {
3439var control = mac ? e.metaKey && !e.ctrlKey : e.ctrlKey; // get ctrl key
3440 if (control && e.which == 70) { // 70 is "F"
3441 ensureAllInheritedExpanded();
3442 }
3443});
Scott Main498d7102013-08-21 15:47:38 -07003444
3445
3446
3447
3448
3449
3450/* On-demand functions */
3451
3452/** Move sample code line numbers out of PRE block and into non-copyable column */
3453function initCodeLineNumbers() {
3454 var numbers = $("#codesample-block a.number");
3455 if (numbers.length) {
3456 $("#codesample-line-numbers").removeClass("hidden").append(numbers);
3457 }
3458
3459 $(document).ready(function() {
3460 // select entire line when clicked
3461 $("span.code-line").click(function() {
3462 if (!shifted) {
3463 selectText(this);
3464 }
3465 });
3466 // invoke line link on double click
3467 $(".code-line").dblclick(function() {
3468 document.location.hash = $(this).attr('id');
3469 });
3470 // highlight the line when hovering on the number
3471 $("#codesample-line-numbers a.number").mouseover(function() {
3472 var id = $(this).attr('href');
3473 $(id).css('background','#e7e7e7');
3474 });
3475 $("#codesample-line-numbers a.number").mouseout(function() {
3476 var id = $(this).attr('href');
3477 $(id).css('background','none');
3478 });
3479 });
3480}
3481
3482// create SHIFT key binder to avoid the selectText method when selecting multiple lines
3483var shifted = false;
3484$(document).bind('keyup keydown', function(e){shifted = e.shiftKey; return true;} );
3485
3486// courtesy of jasonedelman.com
3487function selectText(element) {
3488 var doc = document
3489 , range, selection
3490 ;
3491 if (doc.body.createTextRange) { //ms
3492 range = doc.body.createTextRange();
3493 range.moveToElementText(element);
3494 range.select();
3495 } else if (window.getSelection) { //all others
Scott Main70557ee2013-10-30 14:47:40 -07003496 selection = window.getSelection();
Scott Main498d7102013-08-21 15:47:38 -07003497 range = doc.createRange();
3498 range.selectNodeContents(element);
3499 selection.removeAllRanges();
3500 selection.addRange(range);
3501 }
Scott Main285f0772013-08-22 23:22:09 +00003502}
Scott Main03aca9a2013-10-31 07:20:55 -07003503
3504
3505
3506
3507/** Display links and other information about samples that match the
3508 group specified by the URL */
3509function showSamples() {
3510 var group = $("#samples").attr('class');
3511 $("#samples").html("<p>Here are some samples for <b>" + group + "</b> apps:</p>");
3512
3513 var $ul = $("<ul>");
3514 $selectedLi = $("#nav li.selected");
3515
3516 $selectedLi.children("ul").children("li").each(function() {
3517 var $li = $("<li>").append($(this).find("a").first().clone());
3518 $ul.append($li);
3519 });
3520
3521 $("#samples").append($ul);
3522
3523}
Dirk Doughertyc3921652014-05-13 16:55:26 -07003524
3525
3526
3527/* ########################################################## */
3528/* ################### RESOURCE CARDS ##################### */
3529/* ########################################################## */
3530
3531/** Handle resource queries, collections, and grids (sections). Requires
3532 jd_tag_helpers.js and the *_unified_data.js to be loaded. */
3533
3534(function() {
3535 // Prevent the same resource from being loaded more than once per page.
3536 var addedPageResources = {};
3537
3538 $(document).ready(function() {
3539 $('.resource-widget').each(function() {
3540 initResourceWidget(this);
3541 });
3542
3543 /* Pass the line height to ellipsisfade() to adjust the height of the
3544 text container to show the max number of lines possible, without
3545 showing lines that are cut off. This works with the css ellipsis
3546 classes to fade last text line and apply an ellipsis char. */
3547
Scott Mainb16376f2014-05-21 20:35:47 -07003548 //card text currently uses 15px line height.
Dirk Doughertyc3921652014-05-13 16:55:26 -07003549 var lineHeight = 15;
3550 $('.card-info .text').ellipsisfade(lineHeight);
3551 });
3552
3553 /*
3554 Three types of resource layouts:
3555 Flow - Uses a fixed row-height flow using float left style.
3556 Carousel - Single card slideshow all same dimension absolute.
3557 Stack - Uses fixed columns and flexible element height.
3558 */
3559 function initResourceWidget(widget) {
3560 var $widget = $(widget);
3561 var isFlow = $widget.hasClass('resource-flow-layout'),
3562 isCarousel = $widget.hasClass('resource-carousel-layout'),
3563 isStack = $widget.hasClass('resource-stack-layout');
3564
3565 // find size of widget by pulling out its class name
3566 var sizeCols = 1;
3567 var m = $widget.get(0).className.match(/\bcol-(\d+)\b/);
3568 if (m) {
3569 sizeCols = parseInt(m[1], 10);
3570 }
3571
3572 var opts = {
3573 cardSizes: ($widget.data('cardsizes') || '').split(','),
3574 maxResults: parseInt($widget.data('maxresults') || '100', 10),
3575 itemsPerPage: $widget.data('itemsperpage'),
3576 sortOrder: $widget.data('sortorder'),
3577 query: $widget.data('query'),
3578 section: $widget.data('section'),
Robert Lye7eeb402014-06-03 19:35:24 -07003579 sizeCols: sizeCols,
3580 /* Added by LFL 6/6/14 */
3581 resourceStyle: $widget.data('resourcestyle') || 'card',
3582 stackSort: $widget.data('stacksort') || 'true'
Dirk Doughertyc3921652014-05-13 16:55:26 -07003583 };
3584
3585 // run the search for the set of resources to show
3586
3587 var resources = buildResourceList(opts);
3588
3589 if (isFlow) {
3590 drawResourcesFlowWidget($widget, opts, resources);
3591 } else if (isCarousel) {
3592 drawResourcesCarouselWidget($widget, opts, resources);
3593 } else if (isStack) {
smain@google.com95948b82014-06-16 19:24:25 -07003594 /* Looks like this got removed and is not used, so repurposing for the
3595 homepage style layout.
Robert Lye7eeb402014-06-03 19:35:24 -07003596 Modified by LFL 6/6/14
3597 */
3598 //var sections = buildSectionList(opts);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003599 opts['numStacks'] = $widget.data('numstacks');
Robert Lye7eeb402014-06-03 19:35:24 -07003600 drawResourcesStackWidget($widget, opts, resources/*, sections*/);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003601 }
3602 }
3603
3604 /* Initializes a Resource Carousel Widget */
3605 function drawResourcesCarouselWidget($widget, opts, resources) {
3606 $widget.empty();
3607 var plusone = true; //always show plusone on carousel
3608
3609 $widget.addClass('resource-card slideshow-container')
3610 .append($('<a>').addClass('slideshow-prev').text('Prev'))
3611 .append($('<a>').addClass('slideshow-next').text('Next'));
3612
3613 var css = { 'width': $widget.width() + 'px',
3614 'height': $widget.height() + 'px' };
3615
3616 var $ul = $('<ul>');
3617
3618 for (var i = 0; i < resources.length; ++i) {
Dirk Doughertyc3921652014-05-13 16:55:26 -07003619 var $card = $('<a>')
Robert Lye7eeb402014-06-03 19:35:24 -07003620 .attr('href', cleanUrl(resources[i].url))
Dirk Doughertyc3921652014-05-13 16:55:26 -07003621 .decorateResourceCard(resources[i],plusone);
3622
3623 $('<li>').css(css)
3624 .append($card)
3625 .appendTo($ul);
3626 }
3627
3628 $('<div>').addClass('frame')
3629 .append($ul)
3630 .appendTo($widget);
3631
3632 $widget.dacSlideshow({
3633 auto: true,
3634 btnPrev: '.slideshow-prev',
3635 btnNext: '.slideshow-next'
3636 });
3637 };
3638
Robert Lye7eeb402014-06-03 19:35:24 -07003639 /* Initializes a Resource Card Stack Widget (column-based layout)
3640 Modified by LFL 6/6/14
3641 */
Dirk Doughertyc3921652014-05-13 16:55:26 -07003642 function drawResourcesStackWidget($widget, opts, resources, sections) {
3643 // Don't empty widget, grab all items inside since they will be the first
3644 // items stacked, followed by the resource query
3645 var plusone = true; //by default show plusone on section cards
3646 var cards = $widget.find('.resource-card').detach().toArray();
3647 var numStacks = opts.numStacks || 1;
3648 var $stacks = [];
3649 var urlString;
3650
3651 for (var i = 0; i < numStacks; ++i) {
3652 $stacks[i] = $('<div>').addClass('resource-card-stack')
3653 .appendTo($widget);
3654 }
3655
3656 var sectionResources = [];
3657
3658 // Extract any subsections that are actually resource cards
Robert Lye7eeb402014-06-03 19:35:24 -07003659 if (sections) {
3660 for (var i = 0; i < sections.length; ++i) {
3661 if (!sections[i].sections || !sections[i].sections.length) {
3662 // Render it as a resource card
3663 sectionResources.push(
3664 $('<a>')
3665 .addClass('resource-card section-card')
3666 .attr('href', cleanUrl(sections[i].resource.url))
3667 .decorateResourceCard(sections[i].resource,plusone)[0]
3668 );
Dirk Doughertyc3921652014-05-13 16:55:26 -07003669
Robert Lye7eeb402014-06-03 19:35:24 -07003670 } else {
3671 cards.push(
3672 $('<div>')
3673 .addClass('resource-card section-card-menu')
3674 .decorateResourceSection(sections[i],plusone)[0]
3675 );
3676 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003677 }
3678 }
3679
3680 cards = cards.concat(sectionResources);
3681
3682 for (var i = 0; i < resources.length; ++i) {
Robert Lye7eeb402014-06-03 19:35:24 -07003683 var $card = createResourceElement(resources[i], opts);
smain@google.com95948b82014-06-16 19:24:25 -07003684
Robert Lye7eeb402014-06-03 19:35:24 -07003685 if (opts.resourceStyle.indexOf('related') > -1) {
3686 $card.addClass('related-card');
3687 }
smain@google.com95948b82014-06-16 19:24:25 -07003688
Dirk Doughertyc3921652014-05-13 16:55:26 -07003689 cards.push($card[0]);
3690 }
3691
Robert Lye7eeb402014-06-03 19:35:24 -07003692 if (opts.stackSort != 'false') {
3693 for (var i = 0; i < cards.length; ++i) {
3694 // Find the stack with the shortest height, but give preference to
3695 // left to right order.
3696 var minHeight = $stacks[0].height();
3697 var minIndex = 0;
Dirk Doughertyc3921652014-05-13 16:55:26 -07003698
Robert Lye7eeb402014-06-03 19:35:24 -07003699 for (var j = 1; j < numStacks; ++j) {
3700 var height = $stacks[j].height();
3701 if (height < minHeight - 45) {
3702 minHeight = height;
3703 minIndex = j;
3704 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003705 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003706
Robert Lye7eeb402014-06-03 19:35:24 -07003707 $stacks[minIndex].append($(cards[i]));
3708 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003709 }
3710
3711 };
smain@google.com95948b82014-06-16 19:24:25 -07003712
3713 /*
Robert Lye7eeb402014-06-03 19:35:24 -07003714 Create a resource card using the given resource object and a list of html
3715 configured options. Returns a jquery object containing the element.
3716 */
smain@google.com95948b82014-06-16 19:24:25 -07003717 function createResourceElement(resource, opts, plusone) {
Robert Lye7eeb402014-06-03 19:35:24 -07003718 var $el;
smain@google.com95948b82014-06-16 19:24:25 -07003719
Robert Lye7eeb402014-06-03 19:35:24 -07003720 // The difference here is that generic cards are not entirely clickable
3721 // so its a div instead of an a tag, also the generic one is not given
3722 // the resource-card class so it appears with a transparent background
3723 // and can be styled in whatever way the css setup.
3724 if (opts.resourceStyle == 'generic') {
3725 $el = $('<div>')
3726 .addClass('resource')
3727 .attr('href', cleanUrl(resource.url))
3728 .decorateResource(resource, opts);
3729 } else {
3730 var cls = 'resource resource-card';
smain@google.com95948b82014-06-16 19:24:25 -07003731
Robert Lye7eeb402014-06-03 19:35:24 -07003732 $el = $('<a>')
3733 .addClass(cls)
3734 .attr('href', cleanUrl(resource.url))
3735 .decorateResourceCard(resource, plusone);
3736 }
smain@google.com95948b82014-06-16 19:24:25 -07003737
Robert Lye7eeb402014-06-03 19:35:24 -07003738 return $el;
3739 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003740
3741 /* Initializes a flow widget, see distribute.scss for generating accompanying css */
3742 function drawResourcesFlowWidget($widget, opts, resources) {
3743 $widget.empty();
3744 var cardSizes = opts.cardSizes || ['6x6'];
3745 var i = 0, j = 0;
3746 var plusone = true; // by default show plusone on resource cards
3747
3748 while (i < resources.length) {
3749 var cardSize = cardSizes[j++ % cardSizes.length];
3750 cardSize = cardSize.replace(/^\s+|\s+$/,'');
Dirk Doughertyc3921652014-05-13 16:55:26 -07003751 // Some card sizes do not get a plusone button, such as where space is constrained
3752 // or for cards commonly embedded in docs (to improve overall page speed).
3753 plusone = !((cardSize == "6x2") || (cardSize == "6x3") ||
3754 (cardSize == "9x2") || (cardSize == "9x3") ||
3755 (cardSize == "12x2") || (cardSize == "12x3"));
3756
3757 // A stack has a third dimension which is the number of stacked items
3758 var isStack = cardSize.match(/(\d+)x(\d+)x(\d+)/);
3759 var stackCount = 0;
3760 var $stackDiv = null;
3761
3762 if (isStack) {
3763 // Create a stack container which should have the dimensions defined
3764 // by the product of the items inside.
3765 $stackDiv = $('<div>').addClass('resource-card-stack resource-card-' + isStack[1]
3766 + 'x' + isStack[2] * isStack[3]) .appendTo($widget);
3767 }
3768
3769 // Build each stack item or just a single item
3770 do {
3771 var resource = resources[i];
Dirk Doughertyc3921652014-05-13 16:55:26 -07003772
Robert Lye7eeb402014-06-03 19:35:24 -07003773 var $card = createResourceElement(resources[i], opts, plusone);
smain@google.com95948b82014-06-16 19:24:25 -07003774
3775 $card.addClass('resource-card-' + cardSize +
Robert Lye7eeb402014-06-03 19:35:24 -07003776 ' resource-card-' + resource.type);
smain@google.com95948b82014-06-16 19:24:25 -07003777
Dirk Doughertyc3921652014-05-13 16:55:26 -07003778 if (isStack) {
3779 $card.addClass('resource-card-' + isStack[1] + 'x' + isStack[2]);
3780 if (++stackCount == parseInt(isStack[3])) {
3781 $card.addClass('resource-card-row-stack-last');
3782 stackCount = 0;
3783 }
3784 } else {
3785 stackCount = 0;
3786 }
3787
Robert Lye7eeb402014-06-03 19:35:24 -07003788 $card.appendTo($stackDiv || $widget);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003789
3790 } while (++i < resources.length && stackCount > 0);
3791 }
3792 }
3793
3794 /* Build a site map of resources using a section as a root. */
3795 function buildSectionList(opts) {
3796 if (opts.section && SECTION_BY_ID[opts.section]) {
3797 return SECTION_BY_ID[opts.section].sections || [];
3798 }
3799 return [];
3800 }
3801
3802 function buildResourceList(opts) {
3803 var maxResults = opts.maxResults || 100;
3804
3805 var query = opts.query || '';
3806 var expressions = parseResourceQuery(query);
3807 var addedResourceIndices = {};
3808 var results = [];
3809
3810 for (var i = 0; i < expressions.length; i++) {
3811 var clauses = expressions[i];
3812
3813 // build initial set of resources from first clause
3814 var firstClause = clauses[0];
3815 var resources = [];
3816 switch (firstClause.attr) {
3817 case 'type':
3818 resources = ALL_RESOURCES_BY_TYPE[firstClause.value];
3819 break;
3820 case 'lang':
3821 resources = ALL_RESOURCES_BY_LANG[firstClause.value];
3822 break;
3823 case 'tag':
3824 resources = ALL_RESOURCES_BY_TAG[firstClause.value];
3825 break;
3826 case 'collection':
3827 var urls = RESOURCE_COLLECTIONS[firstClause.value].resources || [];
3828 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3829 break;
3830 case 'section':
3831 var urls = SITE_MAP[firstClause.value].sections || [];
3832 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3833 break;
3834 }
3835 // console.log(firstClause.attr + ':' + firstClause.value);
3836 resources = resources || [];
3837
3838 // use additional clauses to filter corpus
3839 if (clauses.length > 1) {
3840 var otherClauses = clauses.slice(1);
3841 resources = resources.filter(getResourceMatchesClausesFilter(otherClauses));
3842 }
3843
3844 // filter out resources already added
3845 if (i > 1) {
3846 resources = resources.filter(getResourceNotAlreadyAddedFilter(addedResourceIndices));
3847 }
3848
3849 // add to list of already added indices
3850 for (var j = 0; j < resources.length; j++) {
3851 // console.log(resources[j].title);
3852 addedResourceIndices[resources[j].index] = 1;
3853 }
3854
3855 // concat to final results list
3856 results = results.concat(resources);
3857 }
3858
3859 if (opts.sortOrder && results.length) {
3860 var attr = opts.sortOrder;
3861
3862 if (opts.sortOrder == 'random') {
3863 var i = results.length, j, temp;
3864 while (--i) {
3865 j = Math.floor(Math.random() * (i + 1));
3866 temp = results[i];
3867 results[i] = results[j];
3868 results[j] = temp;
3869 }
3870 } else {
3871 var desc = attr.charAt(0) == '-';
3872 if (desc) {
3873 attr = attr.substring(1);
3874 }
3875 results = results.sort(function(x,y) {
3876 return (desc ? -1 : 1) * (parseInt(x[attr], 10) - parseInt(y[attr], 10));
3877 });
3878 }
3879 }
3880
3881 results = results.filter(getResourceNotAlreadyAddedFilter(addedPageResources));
3882 results = results.slice(0, maxResults);
3883
3884 for (var j = 0; j < results.length; ++j) {
3885 addedPageResources[results[j].index] = 1;
3886 }
3887
3888 return results;
3889 }
3890
3891
3892 function getResourceNotAlreadyAddedFilter(addedResourceIndices) {
3893 return function(resource) {
3894 return !addedResourceIndices[resource.index];
3895 };
3896 }
3897
3898
3899 function getResourceMatchesClausesFilter(clauses) {
3900 return function(resource) {
3901 return doesResourceMatchClauses(resource, clauses);
3902 };
3903 }
3904
3905
3906 function doesResourceMatchClauses(resource, clauses) {
3907 for (var i = 0; i < clauses.length; i++) {
3908 var map;
3909 switch (clauses[i].attr) {
3910 case 'type':
3911 map = IS_RESOURCE_OF_TYPE[clauses[i].value];
3912 break;
3913 case 'lang':
3914 map = IS_RESOURCE_IN_LANG[clauses[i].value];
3915 break;
3916 case 'tag':
3917 map = IS_RESOURCE_TAGGED[clauses[i].value];
3918 break;
3919 }
3920
3921 if (!map || (!!clauses[i].negative ? map[resource.index] : !map[resource.index])) {
3922 return clauses[i].negative;
3923 }
3924 }
3925 return true;
3926 }
smain@google.com95948b82014-06-16 19:24:25 -07003927
Robert Lye7eeb402014-06-03 19:35:24 -07003928 function cleanUrl(url)
3929 {
3930 if (url && url.indexOf('//') === -1) {
3931 url = toRoot + url;
3932 }
smain@google.com95948b82014-06-16 19:24:25 -07003933
Robert Lye7eeb402014-06-03 19:35:24 -07003934 return url;
3935 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003936
3937
3938 function parseResourceQuery(query) {
3939 // Parse query into array of expressions (expression e.g. 'tag:foo + type:video')
3940 var expressions = [];
3941 var expressionStrs = query.split(',') || [];
3942 for (var i = 0; i < expressionStrs.length; i++) {
3943 var expr = expressionStrs[i] || '';
3944
3945 // Break expression into clauses (clause e.g. 'tag:foo')
3946 var clauses = [];
3947 var clauseStrs = expr.split(/(?=[\+\-])/);
3948 for (var j = 0; j < clauseStrs.length; j++) {
3949 var clauseStr = clauseStrs[j] || '';
3950
3951 // Get attribute and value from clause (e.g. attribute='tag', value='foo')
3952 var parts = clauseStr.split(':');
3953 var clause = {};
3954
3955 clause.attr = parts[0].replace(/^\s+|\s+$/g,'');
3956 if (clause.attr) {
3957 if (clause.attr.charAt(0) == '+') {
3958 clause.attr = clause.attr.substring(1);
3959 } else if (clause.attr.charAt(0) == '-') {
3960 clause.negative = true;
3961 clause.attr = clause.attr.substring(1);
3962 }
3963 }
3964
3965 if (parts.length > 1) {
3966 clause.value = parts[1].replace(/^\s+|\s+$/g,'');
3967 }
3968
3969 clauses.push(clause);
3970 }
3971
3972 if (!clauses.length) {
3973 continue;
3974 }
3975
3976 expressions.push(clauses);
3977 }
3978
3979 return expressions;
3980 }
3981})();
3982
3983(function($) {
Robert Lye7eeb402014-06-03 19:35:24 -07003984
smain@google.com95948b82014-06-16 19:24:25 -07003985 /*
Robert Lye7eeb402014-06-03 19:35:24 -07003986 Utility method for creating dom for the description area of a card.
3987 Used in decorateResourceCard and decorateResource.
3988 */
3989 function buildResourceCardDescription(resource, plusone) {
3990 var $description = $('<div>').addClass('description ellipsis');
smain@google.com95948b82014-06-16 19:24:25 -07003991
Robert Lye7eeb402014-06-03 19:35:24 -07003992 $description.append($('<div>').addClass('text').html(resource.summary));
smain@google.com95948b82014-06-16 19:24:25 -07003993
Robert Lye7eeb402014-06-03 19:35:24 -07003994 if (resource.cta) {
3995 $description.append($('<a>').addClass('cta').html(resource.cta));
3996 }
smain@google.com95948b82014-06-16 19:24:25 -07003997
Robert Lye7eeb402014-06-03 19:35:24 -07003998 if (plusone) {
smain@google.com95948b82014-06-16 19:24:25 -07003999 var plusurl = resource.url.indexOf("//") > -1 ? resource.url :
Robert Lye7eeb402014-06-03 19:35:24 -07004000 "//developer.android.com/" + resource.url;
smain@google.com95948b82014-06-16 19:24:25 -07004001
Robert Lye7eeb402014-06-03 19:35:24 -07004002 $description.append($('<div>').addClass('util')
4003 .append($('<div>').addClass('g-plusone')
4004 .attr('data-size', 'small')
4005 .attr('data-align', 'right')
4006 .attr('data-href', plusurl)));
4007 }
smain@google.com95948b82014-06-16 19:24:25 -07004008
Robert Lye7eeb402014-06-03 19:35:24 -07004009 return $description;
4010 }
smain@google.com95948b82014-06-16 19:24:25 -07004011
4012
Dirk Doughertyc3921652014-05-13 16:55:26 -07004013 /* Simple jquery function to create dom for a standard resource card */
4014 $.fn.decorateResourceCard = function(resource,plusone) {
4015 var section = resource.group || resource.type;
smain@google.com95948b82014-06-16 19:24:25 -07004016 var imgUrl = resource.image ||
Robert Lye7eeb402014-06-03 19:35:24 -07004017 'assets/images/resource-card-default-android.jpg';
smain@google.com95948b82014-06-16 19:24:25 -07004018
Robert Lye7eeb402014-06-03 19:35:24 -07004019 if (imgUrl.indexOf('//') === -1) {
4020 imgUrl = toRoot + imgUrl;
Dirk Doughertyc3921652014-05-13 16:55:26 -07004021 }
Robert Lye7eeb402014-06-03 19:35:24 -07004022
4023 $('<div>').addClass('card-bg')
smain@google.com95948b82014-06-16 19:24:25 -07004024 .css('background-image', 'url(' + (imgUrl || toRoot +
Robert Lye7eeb402014-06-03 19:35:24 -07004025 'assets/images/resource-card-default-android.jpg') + ')')
Dirk Doughertyc3921652014-05-13 16:55:26 -07004026 .appendTo(this);
smain@google.com95948b82014-06-16 19:24:25 -07004027
Robert Lye7eeb402014-06-03 19:35:24 -07004028 $('<div>').addClass('card-info' + (!resource.summary ? ' empty-desc' : ''))
4029 .append($('<div>').addClass('section').text(section))
4030 .append($('<div>').addClass('title').html(resource.title))
4031 .append(buildResourceCardDescription(resource, plusone))
4032 .appendTo(this);
Dirk Doughertyc3921652014-05-13 16:55:26 -07004033
4034 return this;
4035 };
4036
4037 /* Simple jquery function to create dom for a resource section card (menu) */
4038 $.fn.decorateResourceSection = function(section,plusone) {
4039 var resource = section.resource;
4040 //keep url clean for matching and offline mode handling
4041 var urlPrefix = resource.image.indexOf("//") > -1 ? "" : toRoot;
4042 var $base = $('<a>')
4043 .addClass('card-bg')
4044 .attr('href', resource.url)
4045 .append($('<div>').addClass('card-section-icon')
4046 .append($('<div>').addClass('icon'))
4047 .append($('<div>').addClass('section').html(resource.title)))
4048 .appendTo(this);
4049
4050 var $cardInfo = $('<div>').addClass('card-info').appendTo(this);
4051
4052 if (section.sections && section.sections.length) {
4053 // Recurse the section sub-tree to find a resource image.
4054 var stack = [section];
4055
4056 while (stack.length) {
4057 if (stack[0].resource.image) {
4058 $base.css('background-image', 'url(' + urlPrefix + stack[0].resource.image + ')');
4059 break;
4060 }
4061
4062 if (stack[0].sections) {
4063 stack = stack.concat(stack[0].sections);
4064 }
4065
4066 stack.shift();
4067 }
4068
4069 var $ul = $('<ul>')
4070 .appendTo($cardInfo);
4071
4072 var max = section.sections.length > 3 ? 3 : section.sections.length;
4073
4074 for (var i = 0; i < max; ++i) {
4075
4076 var subResource = section.sections[i];
4077 if (!plusone) {
4078 $('<li>')
4079 .append($('<a>').attr('href', subResource.url)
4080 .append($('<div>').addClass('title').html(subResource.title))
4081 .append($('<div>').addClass('description ellipsis')
4082 .append($('<div>').addClass('text').html(subResource.summary))
4083 .append($('<div>').addClass('util'))))
4084 .appendTo($ul);
4085 } else {
4086 $('<li>')
4087 .append($('<a>').attr('href', subResource.url)
4088 .append($('<div>').addClass('title').html(subResource.title))
4089 .append($('<div>').addClass('description ellipsis')
4090 .append($('<div>').addClass('text').html(subResource.summary))
4091 .append($('<div>').addClass('util')
4092 .append($('<div>').addClass('g-plusone')
4093 .attr('data-size', 'small')
4094 .attr('data-align', 'right')
4095 .attr('data-href', resource.url)))))
4096 .appendTo($ul);
4097 }
4098 }
4099
4100 // Add a more row
4101 if (max < section.sections.length) {
4102 $('<li>')
4103 .append($('<a>').attr('href', resource.url)
4104 .append($('<div>')
4105 .addClass('title')
4106 .text('More')))
4107 .appendTo($ul);
4108 }
4109 } else {
4110 // No sub-resources, just render description?
4111 }
4112
4113 return this;
4114 };
smain@google.com95948b82014-06-16 19:24:25 -07004115
4116
4117
4118
Robert Lye7eeb402014-06-03 19:35:24 -07004119 /* Render other types of resource styles that are not cards. */
4120 $.fn.decorateResource = function(resource, opts) {
smain@google.com95948b82014-06-16 19:24:25 -07004121 var imgUrl = resource.image ||
Robert Lye7eeb402014-06-03 19:35:24 -07004122 'assets/images/resource-card-default-android.jpg';
4123 var linkUrl = resource.url;
smain@google.com95948b82014-06-16 19:24:25 -07004124
Robert Lye7eeb402014-06-03 19:35:24 -07004125 if (imgUrl.indexOf('//') === -1) {
4126 imgUrl = toRoot + imgUrl;
4127 }
smain@google.com95948b82014-06-16 19:24:25 -07004128
Robert Lye7eeb402014-06-03 19:35:24 -07004129 if (linkUrl && linkUrl.indexOf('//') === -1) {
4130 linkUrl = toRoot + linkUrl;
4131 }
4132
4133 $(this).append(
4134 $('<div>').addClass('image')
4135 .css('background-image', 'url(' + imgUrl + ')'),
4136 $('<div>').addClass('info').append(
4137 $('<h4>').addClass('title').html(resource.title),
4138 $('<p>').addClass('summary').html(resource.summary),
4139 $('<a>').attr('href', linkUrl).addClass('cta').html('Learn More')
4140 )
4141 );
4142
4143 return this;
4144 };
Dirk Doughertyc3921652014-05-13 16:55:26 -07004145})(jQuery);
Robert Lye7eeb402014-06-03 19:35:24 -07004146
4147
Dirk Doughertyc3921652014-05-13 16:55:26 -07004148/* Calculate the vertical area remaining */
4149(function($) {
4150 $.fn.ellipsisfade= function(lineHeight) {
4151 this.each(function() {
4152 // get element text
4153 var $this = $(this);
4154 var remainingHeight = $this.parent().parent().height();
4155 $this.parent().siblings().each(function ()
smain@google.comc91ecb72014-06-23 10:22:23 -07004156 {
smain@google.comcda1a9a2014-06-19 17:07:46 -07004157 if ($(this).is(":visible")) {
4158 var h = $(this).height();
4159 remainingHeight = remainingHeight - h;
4160 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07004161 });
4162
4163 adjustedRemainingHeight = ((remainingHeight)/lineHeight>>0)*lineHeight
4164 $this.parent().css({'height': adjustedRemainingHeight});
4165 $this.css({'height': "auto"});
4166 });
4167
4168 return this;
4169 };
4170}) (jQuery);
Robert Lye7eeb402014-06-03 19:35:24 -07004171
4172/*
4173 Fullscreen Carousel
smain@google.com95948b82014-06-16 19:24:25 -07004174
Robert Lye7eeb402014-06-03 19:35:24 -07004175 The following allows for an area at the top of the page that takes over the
smain@google.com95948b82014-06-16 19:24:25 -07004176 entire browser height except for its top offset and an optional bottom
Robert Lye7eeb402014-06-03 19:35:24 -07004177 padding specified as a data attribute.
smain@google.com95948b82014-06-16 19:24:25 -07004178
Robert Lye7eeb402014-06-03 19:35:24 -07004179 HTML:
smain@google.com95948b82014-06-16 19:24:25 -07004180
Robert Lye7eeb402014-06-03 19:35:24 -07004181 <div class="fullscreen-carousel">
4182 <div class="fullscreen-carousel-content">
4183 <!-- content here -->
4184 </div>
4185 <div class="fullscreen-carousel-content">
4186 <!-- content here -->
4187 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004188
Robert Lye7eeb402014-06-03 19:35:24 -07004189 etc ...
smain@google.com95948b82014-06-16 19:24:25 -07004190
Robert Lye7eeb402014-06-03 19:35:24 -07004191 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004192
Robert Lye7eeb402014-06-03 19:35:24 -07004193 Control over how the carousel takes over the screen can mostly be defined in
4194 a css file. Setting min-height on the .fullscreen-carousel-content elements
smain@google.com95948b82014-06-16 19:24:25 -07004195 will prevent them from shrinking to far vertically when the browser is very
Robert Lye7eeb402014-06-03 19:35:24 -07004196 short, and setting max-height on the .fullscreen-carousel itself will prevent
smain@google.com95948b82014-06-16 19:24:25 -07004197 the area from becoming to long in the case that the browser is stretched very
Robert Lye7eeb402014-06-03 19:35:24 -07004198 tall.
smain@google.com95948b82014-06-16 19:24:25 -07004199
Robert Lye7eeb402014-06-03 19:35:24 -07004200 There is limited functionality for having multiple sections since that request
4201 was removed, but it is possible to add .next-arrow and .prev-arrow elements to
4202 scroll between multiple content areas.
4203*/
4204
4205(function() {
4206 $(document).ready(function() {
4207 $('.fullscreen-carousel').each(function() {
4208 initWidget(this);
4209 });
4210 });
4211
4212 function initWidget(widget) {
4213 var $widget = $(widget);
smain@google.com95948b82014-06-16 19:24:25 -07004214
Robert Lye7eeb402014-06-03 19:35:24 -07004215 var topOffset = $widget.offset().top;
4216 var padBottom = parseInt($widget.data('paddingbottom')) || 0;
4217 var maxHeight = 0;
4218 var minHeight = 0;
4219 var $content = $widget.find('.fullscreen-carousel-content');
4220 var $nextArrow = $widget.find('.next-arrow');
4221 var $prevArrow = $widget.find('.prev-arrow');
4222 var $curSection = $($content[0]);
smain@google.com95948b82014-06-16 19:24:25 -07004223
Robert Lye7eeb402014-06-03 19:35:24 -07004224 if ($content.length <= 1) {
4225 $nextArrow.hide();
4226 $prevArrow.hide();
4227 } else {
4228 $nextArrow.click(function() {
4229 var index = ($content.index($curSection) + 1);
4230 $curSection.hide();
4231 $curSection = $($content[index >= $content.length ? 0 : index]);
4232 $curSection.show();
4233 });
smain@google.com95948b82014-06-16 19:24:25 -07004234
Robert Lye7eeb402014-06-03 19:35:24 -07004235 $prevArrow.click(function() {
4236 var index = ($content.index($curSection) - 1);
4237 $curSection.hide();
4238 $curSection = $($content[index < 0 ? $content.length - 1 : 0]);
4239 $curSection.show();
4240 });
4241 }
4242
4243 // Just hide all content sections except first.
4244 $content.each(function(index) {
4245 if ($(this).height() > minHeight) minHeight = $(this).height();
4246 $(this).css({position: 'absolute', display: index > 0 ? 'none' : ''});
4247 });
4248
4249 // Register for changes to window size, and trigger.
4250 $(window).resize(resizeWidget);
4251 resizeWidget();
4252
4253 function resizeWidget() {
4254 var height = $(window).height() - topOffset - padBottom;
4255 $widget.width($(window).width());
smain@google.com95948b82014-06-16 19:24:25 -07004256 $widget.height(height < minHeight ? minHeight :
Robert Lye7eeb402014-06-03 19:35:24 -07004257 (maxHeight && height > maxHeight ? maxHeight : height));
4258 }
smain@google.com95948b82014-06-16 19:24:25 -07004259 }
Robert Lye7eeb402014-06-03 19:35:24 -07004260})();
4261
4262
4263
4264
4265
4266/*
4267 Tab Carousel
smain@google.com95948b82014-06-16 19:24:25 -07004268
Robert Lye7eeb402014-06-03 19:35:24 -07004269 The following allows tab widgets to be installed via the html below. Each
4270 tab content section should have a data-tab attribute matching one of the
4271 nav items'. Also each tab content section should have a width matching the
4272 tab carousel.
smain@google.com95948b82014-06-16 19:24:25 -07004273
Robert Lye7eeb402014-06-03 19:35:24 -07004274 HTML:
smain@google.com95948b82014-06-16 19:24:25 -07004275
Robert Lye7eeb402014-06-03 19:35:24 -07004276 <div class="tab-carousel">
4277 <ul class="tab-nav">
4278 <li><a href="#" data-tab="handsets">Handsets</a>
4279 <li><a href="#" data-tab="wearable">Wearable</a>
4280 <li><a href="#" data-tab="tv">TV</a>
4281 </ul>
smain@google.com95948b82014-06-16 19:24:25 -07004282
Robert Lye7eeb402014-06-03 19:35:24 -07004283 <div class="tab-carousel-content">
4284 <div data-tab="handsets">
4285 <!--Full width content here-->
4286 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004287
Robert Lye7eeb402014-06-03 19:35:24 -07004288 <div data-tab="wearable">
4289 <!--Full width content here-->
4290 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004291
Robert Lye7eeb402014-06-03 19:35:24 -07004292 <div data-tab="tv">
4293 <!--Full width content here-->
4294 </div>
4295 </div>
4296 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004297
Robert Lye7eeb402014-06-03 19:35:24 -07004298*/
4299(function() {
4300 $(document).ready(function() {
4301 $('.tab-carousel').each(function() {
4302 initWidget(this);
4303 });
4304 });
4305
4306 function initWidget(widget) {
4307 var $widget = $(widget);
4308 var $nav = $widget.find('.tab-nav');
4309 var $anchors = $nav.find('[data-tab]');
4310 var $li = $nav.find('li');
4311 var $contentContainer = $widget.find('.tab-carousel-content');
4312 var $tabs = $contentContainer.find('[data-tab]');
4313 var $curTab = $($tabs[0]); // Current tab is first tab.
4314 var width = $widget.width();
4315
4316 // Setup nav interactivity.
4317 $anchors.click(function(evt) {
4318 evt.preventDefault();
4319 var query = '[data-tab=' + $(this).data('tab') + ']';
smain@google.com95948b82014-06-16 19:24:25 -07004320 transitionWidget($tabs.filter(query));
Robert Lye7eeb402014-06-03 19:35:24 -07004321 });
smain@google.com95948b82014-06-16 19:24:25 -07004322
Robert Lye7eeb402014-06-03 19:35:24 -07004323 // Add highlight for navigation on first item.
4324 var $highlight = $('<div>').addClass('highlight')
4325 .css({left:$li.position().left + 'px', width:$li.outerWidth() + 'px'})
4326 .appendTo($nav);
smain@google.com95948b82014-06-16 19:24:25 -07004327
Robert Lye7eeb402014-06-03 19:35:24 -07004328 // Store height since we will change contents to absolute.
4329 $contentContainer.height($contentContainer.height());
smain@google.com95948b82014-06-16 19:24:25 -07004330
Robert Lye7eeb402014-06-03 19:35:24 -07004331 // Absolutely position tabs so they're ready for transition.
4332 $tabs.each(function(index) {
4333 $(this).css({position: 'absolute', left: index > 0 ? width + 'px' : '0'});
4334 });
smain@google.com95948b82014-06-16 19:24:25 -07004335
Robert Lye7eeb402014-06-03 19:35:24 -07004336 function transitionWidget($toTab) {
4337 if (!$curTab.is($toTab)) {
4338 var curIndex = $tabs.index($curTab[0]);
4339 var toIndex = $tabs.index($toTab[0]);
4340 var dir = toIndex > curIndex ? 1 : -1;
smain@google.com95948b82014-06-16 19:24:25 -07004341
Robert Lye7eeb402014-06-03 19:35:24 -07004342 // Animate content sections.
4343 $toTab.css({left:(width * dir) + 'px'});
4344 $curTab.animate({left:(width * -dir) + 'px'});
4345 $toTab.animate({left:'0'});
smain@google.com95948b82014-06-16 19:24:25 -07004346
Robert Lye7eeb402014-06-03 19:35:24 -07004347 // Animate navigation highlight.
smain@google.com95948b82014-06-16 19:24:25 -07004348 $highlight.animate({left:$($li[toIndex]).position().left + 'px',
Robert Lye7eeb402014-06-03 19:35:24 -07004349 width:$($li[toIndex]).outerWidth() + 'px'})
smain@google.com95948b82014-06-16 19:24:25 -07004350
Robert Lye7eeb402014-06-03 19:35:24 -07004351 // Store new current section.
4352 $curTab = $toTab;
4353 }
4354 }
smain@google.com95948b82014-06-16 19:24:25 -07004355 }
Dirk Doughertyb87e3002014-11-18 19:34:34 -08004356})();