blob: c35de27fe12503d6cbb20ce9b6840a8f7fbe5d33 [file] [log] [blame]
Scott Maine4d8f1b2012-06-21 18:03:05 -07001var classesNav;
2var devdocNav;
3var sidenav;
4var cookie_namespace = 'android_developer';
5var NAV_PREF_TREE = "tree";
6var NAV_PREF_PANELS = "panels";
7var nav_pref;
Scott Maine4d8f1b2012-06-21 18:03:05 -07008var isMobile = false; // true if mobile, so we can adjust some layout
Scott Mainf6145542013-04-01 16:38:11 -07009var mPagePath; // initialized in ready() function
Scott Maine4d8f1b2012-06-21 18:03:05 -070010
Scott Main1b3db112012-07-03 14:06:22 -070011var basePath = getBaseUri(location.pathname);
12var SITE_ROOT = toRoot + basePath.substring(1,basePath.indexOf("/",1));
Scott Main7e447ed2013-02-19 17:22:37 -080013var GOOGLE_DATA; // combined data for google service apis, used for search suggest
Scott Main3b90aff2013-08-01 18:09:35 -070014
Scott Main25e73002013-03-27 15:24:06 -070015// Ensure that all ajax getScript() requests allow caching
16$.ajaxSetup({
17 cache: true
18});
Scott Maine4d8f1b2012-06-21 18:03:05 -070019
20/****** ON LOAD SET UP STUFF *********/
21
Scott Maine4d8f1b2012-06-21 18:03:05 -070022$(document).ready(function() {
smain@google.com698fff02014-11-20 20:39:33 -080023
Dirk Doughertyb87e3002014-11-18 19:34:34 -080024 // show lang dialog if the URL includes /intl/
25 //if (location.pathname.substring(0,6) == "/intl/") {
26 // var lang = location.pathname.split('/')[2];
27 // if (lang != getLangPref()) {
28 // $("#langMessage a.yes").attr("onclick","changeLangPref('" + lang
29 // + "', true); $('#langMessage').hide(); return false;");
30 // $("#langMessage .lang." + lang).show();
31 // $("#langMessage").show();
32 // }
33 //}
Scott Main7e447ed2013-02-19 17:22:37 -080034
Scott Main0e76e7e2013-03-12 10:24:07 -070035 // load json file for JD doc search suggestions
Scott Main719acb42013-12-05 16:05:09 -080036 $.getScript(toRoot + 'jd_lists_unified.js');
Scott Main7e447ed2013-02-19 17:22:37 -080037 // load json file for Android API search suggestions
38 $.getScript(toRoot + 'reference/lists.js');
39 // load json files for Google services API suggestions
Scott Main9f2971d2013-02-26 13:07:41 -080040 $.getScript(toRoot + 'reference/gcm_lists.js', function(data, textStatus, jqxhr) {
Scott Main7e447ed2013-02-19 17:22:37 -080041 // once the GCM json (GCM_DATA) is loaded, load the GMS json (GMS_DATA) and merge the data
42 if(jqxhr.status === 200) {
Scott Main9f2971d2013-02-26 13:07:41 -080043 $.getScript(toRoot + 'reference/gms_lists.js', function(data, textStatus, jqxhr) {
Scott Main7e447ed2013-02-19 17:22:37 -080044 if(jqxhr.status === 200) {
45 // combine GCM and GMS data
46 GOOGLE_DATA = GMS_DATA;
47 var start = GOOGLE_DATA.length;
48 for (var i=0; i<GCM_DATA.length; i++) {
49 GOOGLE_DATA.push({id:start+i, label:GCM_DATA[i].label,
50 link:GCM_DATA[i].link, type:GCM_DATA[i].type});
51 }
52 }
53 });
54 }
55 });
56
Scott Main0e76e7e2013-03-12 10:24:07 -070057 // setup keyboard listener for search shortcut
58 $('body').keyup(function(event) {
59 if (event.which == 191) {
60 $('#search_autocomplete').focus();
61 }
62 });
Scott Main015d6162013-01-29 09:01:52 -080063
Scott Maine4d8f1b2012-06-21 18:03:05 -070064 // init the fullscreen toggle click event
65 $('#nav-swap .fullscreen').click(function(){
66 if ($(this).hasClass('disabled')) {
67 toggleFullscreen(true);
68 } else {
69 toggleFullscreen(false);
70 }
71 });
Scott Main3b90aff2013-08-01 18:09:35 -070072
Scott Maine4d8f1b2012-06-21 18:03:05 -070073 // initialize the divs with custom scrollbars
74 $('.scroll-pane').jScrollPane( {verticalGutter:0} );
Scott Main3b90aff2013-08-01 18:09:35 -070075
Scott Maine4d8f1b2012-06-21 18:03:05 -070076 // add HRs below all H2s (except for a few other h2 variants)
Scott Mainc29b3f52014-05-30 21:18:30 -070077 $('h2').not('#qv h2')
78 .not('#tb h2')
79 .not('.sidebox h2')
80 .not('#devdoc-nav h2')
81 .not('h2.norule').css({marginBottom:0})
82 .after('<hr/>');
Scott Maine4d8f1b2012-06-21 18:03:05 -070083
84 // set up the search close button
85 $('.search .close').click(function() {
86 $searchInput = $('#search_autocomplete');
87 $searchInput.attr('value', '');
88 $(this).addClass("hide");
89 $("#search-container").removeClass('active');
90 $("#search_autocomplete").blur();
Scott Main0e76e7e2013-03-12 10:24:07 -070091 search_focus_changed($searchInput.get(), false);
92 hideResults();
Scott Maine4d8f1b2012-06-21 18:03:05 -070093 });
94
95 // Set up quicknav
Scott Main3b90aff2013-08-01 18:09:35 -070096 var quicknav_open = false;
Scott Maine4d8f1b2012-06-21 18:03:05 -070097 $("#btn-quicknav").click(function() {
98 if (quicknav_open) {
99 $(this).removeClass('active');
100 quicknav_open = false;
101 collapse();
102 } else {
103 $(this).addClass('active');
104 quicknav_open = true;
105 expand();
106 }
107 })
Scott Main3b90aff2013-08-01 18:09:35 -0700108
Scott Maine4d8f1b2012-06-21 18:03:05 -0700109 var expand = function() {
110 $('#header-wrap').addClass('quicknav');
111 $('#quicknav').stop().show().animate({opacity:'1'});
112 }
Scott Main3b90aff2013-08-01 18:09:35 -0700113
Scott Maine4d8f1b2012-06-21 18:03:05 -0700114 var collapse = function() {
115 $('#quicknav').stop().animate({opacity:'0'}, 100, function() {
116 $(this).hide();
117 $('#header-wrap').removeClass('quicknav');
118 });
119 }
Scott Main3b90aff2013-08-01 18:09:35 -0700120
121
Scott Maine4d8f1b2012-06-21 18:03:05 -0700122 //Set up search
123 $("#search_autocomplete").focus(function() {
124 $("#search-container").addClass('active');
125 })
126 $("#search-container").mouseover(function() {
127 $("#search-container").addClass('active');
128 $("#search_autocomplete").focus();
129 })
130 $("#search-container").mouseout(function() {
131 if ($("#search_autocomplete").is(":focus")) return;
132 if ($("#search_autocomplete").val() == '') {
133 setTimeout(function(){
134 $("#search-container").removeClass('active');
135 $("#search_autocomplete").blur();
136 },250);
137 }
138 })
139 $("#search_autocomplete").blur(function() {
140 if ($("#search_autocomplete").val() == '') {
141 $("#search-container").removeClass('active');
142 }
143 })
144
Scott Main3b90aff2013-08-01 18:09:35 -0700145
Scott Maine4d8f1b2012-06-21 18:03:05 -0700146 // prep nav expandos
147 var pagePath = document.location.pathname;
148 // account for intl docs by removing the intl/*/ path
149 if (pagePath.indexOf("/intl/") == 0) {
150 pagePath = pagePath.substr(pagePath.indexOf("/",6)); // start after intl/ to get last /
151 }
Scott Mainac2aef52013-02-12 14:15:23 -0800152
Scott Maine4d8f1b2012-06-21 18:03:05 -0700153 if (pagePath.indexOf(SITE_ROOT) == 0) {
154 if (pagePath == '' || pagePath.charAt(pagePath.length - 1) == '/') {
155 pagePath += 'index.html';
156 }
157 }
158
Scott Main01a25452013-02-12 17:32:27 -0800159 // Need a copy of the pagePath before it gets changed in the next block;
160 // it's needed to perform proper tab highlighting in offline docs (see rootDir below)
161 var pagePathOriginal = pagePath;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700162 if (SITE_ROOT.match(/\.\.\//) || SITE_ROOT == '') {
163 // If running locally, SITE_ROOT will be a relative path, so account for that by
164 // finding the relative URL to this page. This will allow us to find links on the page
165 // leading back to this page.
166 var pathParts = pagePath.split('/');
167 var relativePagePathParts = [];
168 var upDirs = (SITE_ROOT.match(/(\.\.\/)+/) || [''])[0].length / 3;
169 for (var i = 0; i < upDirs; i++) {
170 relativePagePathParts.push('..');
171 }
172 for (var i = 0; i < upDirs; i++) {
173 relativePagePathParts.push(pathParts[pathParts.length - (upDirs - i) - 1]);
174 }
175 relativePagePathParts.push(pathParts[pathParts.length - 1]);
176 pagePath = relativePagePathParts.join('/');
177 } else {
178 // Otherwise the page path is already an absolute URL
179 }
180
Scott Mainac2aef52013-02-12 14:15:23 -0800181 // Highlight the header tabs...
182 // highlight Design tab
183 if ($("body").hasClass("design")) {
184 $("#header li.design a").addClass("selected");
Dirk Doughertyc3921652014-05-13 16:55:26 -0700185 $("#sticky-header").addClass("design");
Scott Mainac2aef52013-02-12 14:15:23 -0800186
smain@google.com6040ffa2014-06-13 15:06:23 -0700187 // highlight About tabs
188 } else if ($("body").hasClass("about")) {
189 var rootDir = pagePathOriginal.substring(1,pagePathOriginal.indexOf('/', 1));
190 if (rootDir == "about") {
191 $("#nav-x li.about a").addClass("selected");
192 } else if (rootDir == "wear") {
193 $("#nav-x li.wear a").addClass("selected");
194 } else if (rootDir == "tv") {
195 $("#nav-x li.tv a").addClass("selected");
196 } else if (rootDir == "auto") {
197 $("#nav-x li.auto a").addClass("selected");
198 }
Scott Mainac2aef52013-02-12 14:15:23 -0800199 // highlight Develop tab
200 } else if ($("body").hasClass("develop") || $("body").hasClass("google")) {
201 $("#header li.develop a").addClass("selected");
Dirk Doughertyc3921652014-05-13 16:55:26 -0700202 $("#sticky-header").addClass("develop");
Scott Mainac2aef52013-02-12 14:15:23 -0800203 // In Develop docs, also highlight appropriate sub-tab
Scott Main01a25452013-02-12 17:32:27 -0800204 var rootDir = pagePathOriginal.substring(1,pagePathOriginal.indexOf('/', 1));
Scott Mainac2aef52013-02-12 14:15:23 -0800205 if (rootDir == "training") {
206 $("#nav-x li.training a").addClass("selected");
207 } else if (rootDir == "guide") {
208 $("#nav-x li.guide a").addClass("selected");
209 } else if (rootDir == "reference") {
210 // If the root is reference, but page is also part of Google Services, select Google
211 if ($("body").hasClass("google")) {
212 $("#nav-x li.google a").addClass("selected");
213 } else {
214 $("#nav-x li.reference a").addClass("selected");
215 }
216 } else if ((rootDir == "tools") || (rootDir == "sdk")) {
217 $("#nav-x li.tools a").addClass("selected");
218 } else if ($("body").hasClass("google")) {
219 $("#nav-x li.google a").addClass("selected");
Dirk Dougherty4f7e5152010-09-16 10:43:40 -0700220 } else if ($("body").hasClass("samples")) {
221 $("#nav-x li.samples a").addClass("selected");
Scott Mainac2aef52013-02-12 14:15:23 -0800222 }
223
224 // highlight Distribute tab
225 } else if ($("body").hasClass("distribute")) {
226 $("#header li.distribute a").addClass("selected");
Dirk Doughertyc3921652014-05-13 16:55:26 -0700227 $("#sticky-header").addClass("distribute");
228
229 var baseFrag = pagePathOriginal.indexOf('/', 1) + 1;
230 var secondFrag = pagePathOriginal.substring(baseFrag, pagePathOriginal.indexOf('/', baseFrag));
231 if (secondFrag == "users") {
232 $("#nav-x li.users a").addClass("selected");
233 } else if (secondFrag == "engage") {
234 $("#nav-x li.engage a").addClass("selected");
235 } else if (secondFrag == "monetize") {
236 $("#nav-x li.monetize a").addClass("selected");
237 } else if (secondFrag == "tools") {
238 $("#nav-x li.disttools a").addClass("selected");
239 } else if (secondFrag == "stories") {
240 $("#nav-x li.stories a").addClass("selected");
241 } else if (secondFrag == "essentials") {
242 $("#nav-x li.essentials a").addClass("selected");
243 } else if (secondFrag == "googleplay") {
244 $("#nav-x li.googleplay a").addClass("selected");
245 }
246 } else if ($("body").hasClass("about")) {
247 $("#sticky-header").addClass("about");
Scott Mainb16376f2014-05-21 20:35:47 -0700248 }
Scott Mainac2aef52013-02-12 14:15:23 -0800249
Scott Mainf6145542013-04-01 16:38:11 -0700250 // set global variable so we can highlight the sidenav a bit later (such as for google reference)
251 // and highlight the sidenav
252 mPagePath = pagePath;
253 highlightSidenav();
Dirk Doughertyc3921652014-05-13 16:55:26 -0700254 buildBreadcrumbs();
Scott Mainac2aef52013-02-12 14:15:23 -0800255
Scott Mainf6145542013-04-01 16:38:11 -0700256 // set up prev/next links if they exist
Scott Maine4d8f1b2012-06-21 18:03:05 -0700257 var $selNavLink = $('#nav').find('a[href="' + pagePath + '"]');
Scott Main5a1123e2012-09-26 12:51:28 -0700258 var $selListItem;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700259 if ($selNavLink.length) {
Scott Mainac2aef52013-02-12 14:15:23 -0800260 $selListItem = $selNavLink.closest('li');
Scott Maine4d8f1b2012-06-21 18:03:05 -0700261
262 // set up prev links
263 var $prevLink = [];
264 var $prevListItem = $selListItem.prev('li');
Scott Main3b90aff2013-08-01 18:09:35 -0700265
Scott Maine4d8f1b2012-06-21 18:03:05 -0700266 var crossBoundaries = ($("body.design").length > 0) || ($("body.guide").length > 0) ? true :
267false; // navigate across topic boundaries only in design docs
268 if ($prevListItem.length) {
smain@google.comc91ecb72014-06-23 10:22:23 -0700269 if ($prevListItem.hasClass('nav-section') || crossBoundaries) {
Scott Main5a1123e2012-09-26 12:51:28 -0700270 // jump to last topic of previous section
271 $prevLink = $prevListItem.find('a:last');
272 } else if (!$selListItem.hasClass('nav-section')) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700273 // jump to previous topic in this section
274 $prevLink = $prevListItem.find('a:eq(0)');
275 }
276 } else {
277 // jump to this section's index page (if it exists)
278 var $parentListItem = $selListItem.parents('li');
279 $prevLink = $selListItem.parents('li').find('a');
Scott Main3b90aff2013-08-01 18:09:35 -0700280
Scott Maine4d8f1b2012-06-21 18:03:05 -0700281 // except if cross boundaries aren't allowed, and we're at the top of a section already
282 // (and there's another parent)
Scott Main3b90aff2013-08-01 18:09:35 -0700283 if (!crossBoundaries && $parentListItem.hasClass('nav-section')
Scott Maine4d8f1b2012-06-21 18:03:05 -0700284 && $selListItem.hasClass('nav-section')) {
285 $prevLink = [];
286 }
287 }
288
Scott Maine4d8f1b2012-06-21 18:03:05 -0700289 // set up next links
290 var $nextLink = [];
Scott Maine4d8f1b2012-06-21 18:03:05 -0700291 var startClass = false;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700292 var isCrossingBoundary = false;
Scott Main3b90aff2013-08-01 18:09:35 -0700293
Scott Main1a00f7f2013-10-29 11:11:19 -0700294 if ($selListItem.hasClass('nav-section') && $selListItem.children('div.empty').length == 0) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700295 // we're on an index page, jump to the first topic
Scott Mainb505ca62012-07-26 18:00:14 -0700296 $nextLink = $selListItem.find('ul:eq(0)').find('a:eq(0)');
Scott Maine4d8f1b2012-06-21 18:03:05 -0700297
298 // if there aren't any children, go to the next section (required for About pages)
299 if($nextLink.length == 0) {
300 $nextLink = $selListItem.next('li').find('a');
Scott Mainb505ca62012-07-26 18:00:14 -0700301 } else if ($('.topic-start-link').length) {
302 // as long as there's a child link and there is a "topic start link" (we're on a landing)
303 // then set the landing page "start link" text to be the first doc title
304 $('.topic-start-link').text($nextLink.text().toUpperCase());
Scott Maine4d8f1b2012-06-21 18:03:05 -0700305 }
Scott Main3b90aff2013-08-01 18:09:35 -0700306
Scott Main5a1123e2012-09-26 12:51:28 -0700307 // If the selected page has a description, then it's a class or article homepage
308 if ($selListItem.find('a[description]').length) {
309 // this means we're on a class landing page
Scott Maine4d8f1b2012-06-21 18:03:05 -0700310 startClass = true;
311 }
312 } else {
313 // jump to the next topic in this section (if it exists)
314 $nextLink = $selListItem.next('li').find('a:eq(0)');
Scott Main1a00f7f2013-10-29 11:11:19 -0700315 if ($nextLink.length == 0) {
Scott Main5a1123e2012-09-26 12:51:28 -0700316 isCrossingBoundary = true;
317 // no more topics in this section, jump to the first topic in the next section
smain@google.comabf34112014-06-23 11:39:02 -0700318 $nextLink = $selListItem.parents('li:eq(0)').next('li').find('a:eq(0)');
Scott Main5a1123e2012-09-26 12:51:28 -0700319 if (!$nextLink.length) { // Go up another layer to look for next page (lesson > class > course)
320 $nextLink = $selListItem.parents('li:eq(1)').next('li.nav-section').find('a:eq(0)');
Scott Main1a00f7f2013-10-29 11:11:19 -0700321 if ($nextLink.length == 0) {
322 // if that doesn't work, we're at the end of the list, so disable NEXT link
323 $('.next-page-link').attr('href','').addClass("disabled")
324 .click(function() { return false; });
smain@google.comc91ecb72014-06-23 10:22:23 -0700325 // and completely hide the one in the footer
326 $('.content-footer .next-page-link').hide();
Scott Main1a00f7f2013-10-29 11:11:19 -0700327 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700328 }
329 }
330 }
Scott Main5a1123e2012-09-26 12:51:28 -0700331
332 if (startClass) {
333 $('.start-class-link').attr('href', $nextLink.attr('href')).removeClass("hide");
334
Scott Main3b90aff2013-08-01 18:09:35 -0700335 // if there's no training bar (below the start button),
Scott Main5a1123e2012-09-26 12:51:28 -0700336 // then we need to add a bottom border to button
337 if (!$("#tb").length) {
338 $('.start-class-link').css({'border-bottom':'1px solid #DADADA'});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700339 }
Scott Main5a1123e2012-09-26 12:51:28 -0700340 } else if (isCrossingBoundary && !$('body.design').length) { // Design always crosses boundaries
341 $('.content-footer.next-class').show();
342 $('.next-page-link').attr('href','')
343 .removeClass("hide").addClass("disabled")
344 .click(function() { return false; });
smain@google.comc91ecb72014-06-23 10:22:23 -0700345 // and completely hide the one in the footer
346 $('.content-footer .next-page-link').hide();
Scott Main1a00f7f2013-10-29 11:11:19 -0700347 if ($nextLink.length) {
348 $('.next-class-link').attr('href',$nextLink.attr('href'))
smain@google.com5bc3a1a2014-06-17 20:02:53 -0700349 .removeClass("hide")
350 .append(": " + $nextLink.html());
Scott Main1a00f7f2013-10-29 11:11:19 -0700351 $('.next-class-link').find('.new').empty();
352 }
Scott Main5a1123e2012-09-26 12:51:28 -0700353 } else {
smain@google.com5bc3a1a2014-06-17 20:02:53 -0700354 $('.next-page-link').attr('href', $nextLink.attr('href'))
355 .removeClass("hide");
356 // for the footer link, also add the next page title
357 $('.content-footer .next-page-link').append(": " + $nextLink.html());
Scott Main5a1123e2012-09-26 12:51:28 -0700358 }
359
360 if (!startClass && $prevLink.length) {
361 var prevHref = $prevLink.attr('href');
362 if (prevHref == SITE_ROOT + 'index.html') {
363 // Don't show Previous when it leads to the homepage
364 } else {
365 $('.prev-page-link').attr('href', $prevLink.attr('href')).removeClass("hide");
366 }
Scott Main3b90aff2013-08-01 18:09:35 -0700367 }
Scott Main5a1123e2012-09-26 12:51:28 -0700368
Scott Maine4d8f1b2012-06-21 18:03:05 -0700369 }
Scott Main3b90aff2013-08-01 18:09:35 -0700370
371
372
Scott Main5a1123e2012-09-26 12:51:28 -0700373 // Set up the course landing pages for Training with class names and descriptions
374 if ($('body.trainingcourse').length) {
375 var $classLinks = $selListItem.find('ul li a').not('#nav .nav-section .nav-section ul a');
Scott Maine7d75352014-05-22 15:50:56 -0700376
377 // create an array for all the class descriptions
378 var $classDescriptions = new Array($classLinks.length);
379 var lang = getLangPref();
380 $classLinks.each(function(index) {
381 var langDescr = $(this).attr(lang + "-description");
382 if (typeof langDescr !== 'undefined' && langDescr !== false) {
383 // if there's a class description in the selected language, use that
384 $classDescriptions[index] = langDescr;
385 } else {
386 // otherwise, use the default english description
387 $classDescriptions[index] = $(this).attr("description");
388 }
389 });
Scott Main3b90aff2013-08-01 18:09:35 -0700390
Scott Main5a1123e2012-09-26 12:51:28 -0700391 var $olClasses = $('<ol class="class-list"></ol>');
392 var $liClass;
393 var $imgIcon;
394 var $h2Title;
395 var $pSummary;
396 var $olLessons;
397 var $liLesson;
398 $classLinks.each(function(index) {
399 $liClass = $('<li></li>');
400 $h2Title = $('<a class="title" href="'+$(this).attr('href')+'"><h2>' + $(this).html()+'</h2><span></span></a>');
Scott Maine7d75352014-05-22 15:50:56 -0700401 $pSummary = $('<p class="description">' + $classDescriptions[index] + '</p>');
Scott Main3b90aff2013-08-01 18:09:35 -0700402
Scott Main5a1123e2012-09-26 12:51:28 -0700403 $olLessons = $('<ol class="lesson-list"></ol>');
Scott Main3b90aff2013-08-01 18:09:35 -0700404
Scott Main5a1123e2012-09-26 12:51:28 -0700405 $lessons = $(this).closest('li').find('ul li a');
Scott Main3b90aff2013-08-01 18:09:35 -0700406
Scott Main5a1123e2012-09-26 12:51:28 -0700407 if ($lessons.length) {
Scott Main3b90aff2013-08-01 18:09:35 -0700408 $imgIcon = $('<img src="'+toRoot+'assets/images/resource-tutorial.png" '
409 + ' width="64" height="64" alt=""/>');
Scott Main5a1123e2012-09-26 12:51:28 -0700410 $lessons.each(function(index) {
411 $olLessons.append('<li><a href="'+$(this).attr('href')+'">' + $(this).html()+'</a></li>');
412 });
413 } else {
Scott Main3b90aff2013-08-01 18:09:35 -0700414 $imgIcon = $('<img src="'+toRoot+'assets/images/resource-article.png" '
415 + ' width="64" height="64" alt=""/>');
Scott Main5a1123e2012-09-26 12:51:28 -0700416 $pSummary.addClass('article');
417 }
418
419 $liClass.append($h2Title).append($imgIcon).append($pSummary).append($olLessons);
420 $olClasses.append($liClass);
421 });
422 $('.jd-descr').append($olClasses);
423 }
424
Scott Maine4d8f1b2012-06-21 18:03:05 -0700425 // Set up expand/collapse behavior
Scott Mainad08f072013-08-20 16:49:57 -0700426 initExpandableNavItems("#nav");
Scott Main3b90aff2013-08-01 18:09:35 -0700427
Scott Main3b90aff2013-08-01 18:09:35 -0700428
Scott Maine4d8f1b2012-06-21 18:03:05 -0700429 $(".scroll-pane").scroll(function(event) {
430 event.preventDefault();
431 return false;
432 });
433
434 /* Resize nav height when window height changes */
435 $(window).resize(function() {
436 if ($('#side-nav').length == 0) return;
437 var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
438 setNavBarLeftPos(); // do this even if sidenav isn't fixed because it could become fixed
439 // make sidenav behave when resizing the window and side-scolling is a concern
Scott Mainf5257812014-05-22 17:26:38 -0700440 if (sticky) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700441 if ((stylesheet.attr("disabled") == "disabled") || stylesheet.length == 0) {
442 updateSideNavPosition();
443 } else {
444 updateSidenavFullscreenWidth();
445 }
446 }
447 resizeNav();
448 });
449
450
Scott Maine4d8f1b2012-06-21 18:03:05 -0700451 var navBarLeftPos;
452 if ($('#devdoc-nav').length) {
453 setNavBarLeftPos();
454 }
455
456
Scott Maine4d8f1b2012-06-21 18:03:05 -0700457 // Set up play-on-hover <video> tags.
458 $('video.play-on-hover').bind('click', function(){
459 $(this).get(0).load(); // in case the video isn't seekable
460 $(this).get(0).play();
461 });
462
463 // Set up tooltips
464 var TOOLTIP_MARGIN = 10;
Scott Maindb3678b2012-10-23 14:13:41 -0700465 $('acronym,.tooltip-link').each(function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700466 var $target = $(this);
467 var $tooltip = $('<div>')
468 .addClass('tooltip-box')
Scott Maindb3678b2012-10-23 14:13:41 -0700469 .append($target.attr('title'))
Scott Maine4d8f1b2012-06-21 18:03:05 -0700470 .hide()
471 .appendTo('body');
472 $target.removeAttr('title');
473
474 $target.hover(function() {
475 // in
476 var targetRect = $target.offset();
477 targetRect.width = $target.width();
478 targetRect.height = $target.height();
479
480 $tooltip.css({
481 left: targetRect.left,
482 top: targetRect.top + targetRect.height + TOOLTIP_MARGIN
483 });
484 $tooltip.addClass('below');
485 $tooltip.show();
486 }, function() {
487 // out
488 $tooltip.hide();
489 });
490 });
491
492 // Set up <h2> deeplinks
493 $('h2').click(function() {
494 var id = $(this).attr('id');
495 if (id) {
496 document.location.hash = id;
497 }
498 });
499
500 //Loads the +1 button
501 var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
502 po.src = 'https://apis.google.com/js/plusone.js';
503 var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
504
505
Scott Main3b90aff2013-08-01 18:09:35 -0700506 // Revise the sidenav widths to make room for the scrollbar
Scott Maine4d8f1b2012-06-21 18:03:05 -0700507 // which avoids the visible width from changing each time the bar appears
508 var $sidenav = $("#side-nav");
509 var sidenav_width = parseInt($sidenav.innerWidth());
Scott Main3b90aff2013-08-01 18:09:35 -0700510
Scott Maine4d8f1b2012-06-21 18:03:05 -0700511 $("#devdoc-nav #nav").css("width", sidenav_width - 4 + "px"); // 4px is scrollbar width
512
513
514 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
Scott Main3b90aff2013-08-01 18:09:35 -0700515
Scott Maine4d8f1b2012-06-21 18:03:05 -0700516 if ($(".scroll-pane").length > 1) {
517 // Check if there's a user preference for the panel heights
518 var cookieHeight = readCookie("reference_height");
519 if (cookieHeight) {
520 restoreHeight(cookieHeight);
521 }
522 }
Scott Main3b90aff2013-08-01 18:09:35 -0700523
Scott Main06f3f2c2014-05-30 11:23:00 -0700524 // Resize once loading is finished
Scott Maine4d8f1b2012-06-21 18:03:05 -0700525 resizeNav();
Scott Main06f3f2c2014-05-30 11:23:00 -0700526 // Check if there's an anchor that we need to scroll into view.
527 // A delay is needed, because some browsers do not immediately scroll down to the anchor
528 window.setTimeout(offsetScrollForSticky, 100);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700529
Scott Main015d6162013-01-29 09:01:52 -0800530 /* init the language selector based on user cookie for lang */
531 loadLangPref();
532 changeNavLang(getLangPref());
533
534 /* setup event handlers to ensure the overflow menu is visible while picking lang */
535 $("#language select")
536 .mousedown(function() {
537 $("div.morehover").addClass("hover"); })
538 .blur(function() {
539 $("div.morehover").removeClass("hover"); });
540
541 /* some global variable setup */
542 resizePackagesNav = $("#resize-packages-nav");
543 classesNav = $("#classes-nav");
544 devdocNav = $("#devdoc-nav");
545
546 var cookiePath = "";
547 if (location.href.indexOf("/reference/") != -1) {
548 cookiePath = "reference_";
549 } else if (location.href.indexOf("/guide/") != -1) {
550 cookiePath = "guide_";
551 } else if (location.href.indexOf("/tools/") != -1) {
552 cookiePath = "tools_";
553 } else if (location.href.indexOf("/training/") != -1) {
554 cookiePath = "training_";
555 } else if (location.href.indexOf("/design/") != -1) {
556 cookiePath = "design_";
557 } else if (location.href.indexOf("/distribute/") != -1) {
558 cookiePath = "distribute_";
559 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700560
smain@google.com698fff02014-11-20 20:39:33 -0800561
562 /* setup shadowbox for any videos that want it */
smain@google.comf75ee212014-11-24 09:42:59 -0800563 var $videoLinks = $("a.video-shadowbox-button, a.notice-developers-video");
smain@google.com698fff02014-11-20 20:39:33 -0800564 if ($videoLinks.length) {
565 // if there's at least one, add the shadowbox HTML to the body
566 $('body').prepend(
567'<div id="video-container">'+
568 '<div id="video-frame">'+
569 '<div class="video-close">'+
570 '<span id="icon-video-close" onclick="closeVideo()">&nbsp;</span>'+
571 '</div>'+
572 '<div id="youTubePlayer"></div>'+
573 '</div>'+
574'</div>');
575
576 // loads the IFrame Player API code asynchronously.
577 $.getScript("https://www.youtube.com/iframe_api");
578
579 $videoLinks.each(function() {
580 var videoId = $(this).attr('href').split('?v=')[1];
581 $(this).click(function(event) {
582 event.preventDefault();
583 startYouTubePlayer(videoId);
584 });
585 });
smain@google.com698fff02014-11-20 20:39:33 -0800586 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700587});
Scott Main7e447ed2013-02-19 17:22:37 -0800588// END of the onload event
Scott Maine4d8f1b2012-06-21 18:03:05 -0700589
590
smain@google.com698fff02014-11-20 20:39:33 -0800591var youTubePlayer;
592function onYouTubeIframeAPIReady() {
593}
594
smain@google.com3de83c12014-12-12 19:06:52 -0800595/* Returns the height the shadowbox video should be. It's based on the current
596 height of the "video-frame" element, which is 100% height for the window.
597 Then minus the margin so the video isn't actually the full window height. */
598function getVideoHeight() {
599 var frameHeight = $("#video-frame").height();
600 var marginTop = $("#video-frame").css('margin-top').split('px')[0];
601 return frameHeight - (marginTop * 2);
602}
603
smain@google.com698fff02014-11-20 20:39:33 -0800604function startYouTubePlayer(videoId) {
smain@google.com3de83c12014-12-12 19:06:52 -0800605 $("#video-container").show();
606 $("#video-frame").show();
607
608 // compute the size of the player so it's centered in window
609 var maxWidth = 940; // the width of the web site content
610 var videoAspect = .5625; // based on 1280x720 resolution
611 var maxHeight = maxWidth * videoAspect;
612 var videoHeight = getVideoHeight();
613 var videoWidth = videoHeight / videoAspect;
614 if (videoWidth > maxWidth) {
615 videoWidth = maxWidth;
616 videoHeight = maxHeight;
smain@google.com570c2212014-11-25 19:01:40 -0800617 }
smain@google.com3de83c12014-12-12 19:06:52 -0800618 $("#video-frame").css('width', videoWidth);
619
620 // check if we've already created this player
smain@google.com698fff02014-11-20 20:39:33 -0800621 if (youTubePlayer == null) {
smain@google.com3de83c12014-12-12 19:06:52 -0800622 // check if there's a start time specified
623 var idAndHash = videoId.split("#");
624 var startTime = 0;
625 if (idAndHash.length > 1) {
626 startTime = idAndHash[1].split("t=")[1] != undefined ? idAndHash[1].split("t=")[1] : 0;
627 }
628 // enable localized player
629 var lang = getLangPref();
630 var captionsOn = lang == 'en' ? 0 : 1;
631
smain@google.com698fff02014-11-20 20:39:33 -0800632 youTubePlayer = new YT.Player('youTubePlayer', {
smain@google.com3de83c12014-12-12 19:06:52 -0800633 height: videoHeight,
634 width: videoWidth,
smain@google.com570c2212014-11-25 19:01:40 -0800635 videoId: idAndHash[0],
smain@google.com481f15c2014-12-12 11:57:27 -0800636 playerVars: {start: startTime, hl: lang, cc_load_policy: captionsOn},
smain@google.com698fff02014-11-20 20:39:33 -0800637 events: {
smain@google.comf75ee212014-11-24 09:42:59 -0800638 'onReady': onPlayerReady,
639 'onStateChange': onPlayerStateChange
smain@google.com698fff02014-11-20 20:39:33 -0800640 }
641 });
642 } else {
smain@google.com3de83c12014-12-12 19:06:52 -0800643 // reset the size in case the user adjusted the window since last play
644 youTubePlayer.setSize(videoWidth, videoHeight);
smain@google.com94e0b532014-12-16 19:07:08 -0800645 // if a video different from the one already playing was requested, cue it up
646 if (videoId != youTubePlayer.getVideoUrl().split('?v=')[1]) {
647 youTubePlayer.cueVideoById(videoId);
648 }
smain@google.com698fff02014-11-20 20:39:33 -0800649 youTubePlayer.playVideo();
650 }
smain@google.com698fff02014-11-20 20:39:33 -0800651}
652
653function onPlayerReady(event) {
654 event.target.playVideo();
smain@google.comd24088c2014-12-12 11:31:13 -0800655 // track the start playing event so we know from which page the video was selected
656 ga('send', 'event', 'Videos', 'Start: ' +
657 youTubePlayer.getVideoUrl().split('?v=')[1], 'on: ' + document.location.href);
smain@google.com698fff02014-11-20 20:39:33 -0800658}
659
660function closeVideo() {
661 try {
smain@google.comf75ee212014-11-24 09:42:59 -0800662 youTubePlayer.pauseVideo();
smain@google.com698fff02014-11-20 20:39:33 -0800663 } catch(e) {
664 console.log('Video not available');
smain@google.com698fff02014-11-20 20:39:33 -0800665 }
smain@google.com3de83c12014-12-12 19:06:52 -0800666 $("#video-container").fadeOut(200);
smain@google.com698fff02014-11-20 20:39:33 -0800667}
668
smain@google.comf75ee212014-11-24 09:42:59 -0800669/* Track youtube playback for analytics */
670function onPlayerStateChange(event) {
671 // Video starts, send the video ID
672 if (event.data == YT.PlayerState.PLAYING) {
smain@google.comd24088c2014-12-12 11:31:13 -0800673 ga('send', 'event', 'Videos', 'Play',
674 youTubePlayer.getVideoUrl().split('?v=')[1]);
smain@google.comf75ee212014-11-24 09:42:59 -0800675 }
676 // Video paused, send video ID and video elapsed time
677 if (event.data == YT.PlayerState.PAUSED) {
smain@google.comd24088c2014-12-12 11:31:13 -0800678 ga('send', 'event', 'Videos', 'Paused',
679 youTubePlayer.getVideoUrl().split('?v=')[1], youTubePlayer.getCurrentTime());
smain@google.comf75ee212014-11-24 09:42:59 -0800680 }
681 // Video finished, send video ID and video elapsed time
682 if (event.data == YT.PlayerState.ENDED) {
smain@google.comd24088c2014-12-12 11:31:13 -0800683 ga('send', 'event', 'Videos', 'Finished',
684 youTubePlayer.getVideoUrl().split('?v=')[1], youTubePlayer.getCurrentTime());
smain@google.comf75ee212014-11-24 09:42:59 -0800685 }
686}
687
smain@google.com698fff02014-11-20 20:39:33 -0800688
689
Scott Mainad08f072013-08-20 16:49:57 -0700690function initExpandableNavItems(rootTag) {
691 $(rootTag + ' li.nav-section .nav-section-header').click(function() {
692 var section = $(this).closest('li.nav-section');
693 if (section.hasClass('expanded')) {
Scott Mainf0093852013-08-22 11:37:11 -0700694 /* hide me and descendants */
695 section.find('ul').slideUp(250, function() {
696 // remove 'expanded' class from my section and any children
Scott Mainad08f072013-08-20 16:49:57 -0700697 section.closest('li').removeClass('expanded');
Scott Mainf0093852013-08-22 11:37:11 -0700698 $('li.nav-section', section).removeClass('expanded');
Scott Mainad08f072013-08-20 16:49:57 -0700699 resizeNav();
700 });
701 } else {
702 /* show me */
703 // first hide all other siblings
Scott Main70557ee2013-10-30 14:47:40 -0700704 var $others = $('li.nav-section.expanded', $(this).closest('ul')).not('.sticky');
Scott Mainad08f072013-08-20 16:49:57 -0700705 $others.removeClass('expanded').children('ul').slideUp(250);
706
707 // now expand me
708 section.closest('li').addClass('expanded');
709 section.children('ul').slideDown(250, function() {
710 resizeNav();
711 });
712 }
713 });
Scott Mainf0093852013-08-22 11:37:11 -0700714
715 // Stop expand/collapse behavior when clicking on nav section links
716 // (since we're navigating away from the page)
717 // This selector captures the first instance of <a>, but not those with "#" as the href.
718 $('.nav-section-header').find('a:eq(0)').not('a[href="#"]').click(function(evt) {
719 window.location.href = $(this).attr('href');
720 return false;
721 });
Scott Mainad08f072013-08-20 16:49:57 -0700722}
723
Dirk Doughertyc3921652014-05-13 16:55:26 -0700724
725/** Create the list of breadcrumb links in the sticky header */
726function buildBreadcrumbs() {
727 var $breadcrumbUl = $("#sticky-header ul.breadcrumb");
728 // Add the secondary horizontal nav item, if provided
729 var $selectedSecondNav = $("div#nav-x ul.nav-x a.selected").clone().removeClass("selected");
730 if ($selectedSecondNav.length) {
731 $breadcrumbUl.prepend($("<li>").append($selectedSecondNav))
732 }
733 // Add the primary horizontal nav
734 var $selectedFirstNav = $("div#header-wrap ul.nav-x a.selected").clone().removeClass("selected");
735 // If there's no header nav item, use the logo link and title from alt text
736 if ($selectedFirstNav.length < 1) {
737 $selectedFirstNav = $("<a>")
738 .attr('href', $("div#header .logo a").attr('href'))
739 .text($("div#header .logo img").attr('alt'));
740 }
741 $breadcrumbUl.prepend($("<li>").append($selectedFirstNav));
742}
743
744
745
Scott Maine624b3f2013-09-12 12:56:41 -0700746/** Highlight the current page in sidenav, expanding children as appropriate */
Scott Mainf6145542013-04-01 16:38:11 -0700747function highlightSidenav() {
Scott Maine624b3f2013-09-12 12:56:41 -0700748 // if something is already highlighted, undo it. This is for dynamic navigation (Samples index)
749 if ($("ul#nav li.selected").length) {
750 unHighlightSidenav();
751 }
752 // look for URL in sidenav, including the hash
753 var $selNavLink = $('#nav').find('a[href="' + mPagePath + location.hash + '"]');
754
755 // If the selNavLink is still empty, look for it without the hash
756 if ($selNavLink.length == 0) {
757 $selNavLink = $('#nav').find('a[href="' + mPagePath + '"]');
758 }
759
Scott Mainf6145542013-04-01 16:38:11 -0700760 var $selListItem;
761 if ($selNavLink.length) {
Scott Mainf6145542013-04-01 16:38:11 -0700762 // Find this page's <li> in sidenav and set selected
763 $selListItem = $selNavLink.closest('li');
764 $selListItem.addClass('selected');
Scott Main3b90aff2013-08-01 18:09:35 -0700765
Scott Mainf6145542013-04-01 16:38:11 -0700766 // Traverse up the tree and expand all parent nav-sections
767 $selNavLink.parents('li.nav-section').each(function() {
768 $(this).addClass('expanded');
769 $(this).children('ul').show();
770 });
771 }
772}
773
Scott Maine624b3f2013-09-12 12:56:41 -0700774function unHighlightSidenav() {
775 $("ul#nav li.selected").removeClass("selected");
776 $('ul#nav li.nav-section.expanded').removeClass('expanded').children('ul').hide();
777}
Scott Maine4d8f1b2012-06-21 18:03:05 -0700778
779function toggleFullscreen(enable) {
780 var delay = 20;
781 var enabled = true;
782 var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
783 if (enable) {
784 // Currently NOT USING fullscreen; enable fullscreen
785 stylesheet.removeAttr('disabled');
786 $('#nav-swap .fullscreen').removeClass('disabled');
787 $('#devdoc-nav').css({left:''});
788 setTimeout(updateSidenavFullscreenWidth,delay); // need to wait a moment for css to switch
789 enabled = true;
790 } else {
791 // Currently USING fullscreen; disable fullscreen
792 stylesheet.attr('disabled', 'disabled');
793 $('#nav-swap .fullscreen').addClass('disabled');
794 setTimeout(updateSidenavFixedWidth,delay); // need to wait a moment for css to switch
795 enabled = false;
796 }
smain@google.com6bdcb982014-11-14 11:53:07 -0800797 writeCookie("fullscreen", enabled, null);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700798 setNavBarLeftPos();
799 resizeNav(delay);
800 updateSideNavPosition();
801 setTimeout(initSidenavHeightResize,delay);
802}
803
804
805function setNavBarLeftPos() {
806 navBarLeftPos = $('#body-content').offset().left;
807}
808
809
810function updateSideNavPosition() {
811 var newLeft = $(window).scrollLeft() - navBarLeftPos;
812 $('#devdoc-nav').css({left: -newLeft});
813 $('#devdoc-nav .totop').css({left: -(newLeft - parseInt($('#side-nav').css('margin-left')))});
814}
Scott Main3b90aff2013-08-01 18:09:35 -0700815
Scott Maine4d8f1b2012-06-21 18:03:05 -0700816// TODO: use $(document).ready instead
817function addLoadEvent(newfun) {
818 var current = window.onload;
819 if (typeof window.onload != 'function') {
820 window.onload = newfun;
821 } else {
822 window.onload = function() {
823 current();
824 newfun();
825 }
826 }
827}
828
829var agent = navigator['userAgent'].toLowerCase();
830// If a mobile phone, set flag and do mobile setup
831if ((agent.indexOf("mobile") != -1) || // android, iphone, ipod
832 (agent.indexOf("blackberry") != -1) ||
833 (agent.indexOf("webos") != -1) ||
834 (agent.indexOf("mini") != -1)) { // opera mini browsers
835 isMobile = true;
836}
837
838
Scott Main498d7102013-08-21 15:47:38 -0700839$(document).ready(function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700840 $("pre:not(.no-pretty-print)").addClass("prettyprint");
841 prettyPrint();
Scott Main498d7102013-08-21 15:47:38 -0700842});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700843
Scott Maine4d8f1b2012-06-21 18:03:05 -0700844
845
846
847/* ######### RESIZE THE SIDENAV HEIGHT ########## */
848
849function resizeNav(delay) {
850 var $nav = $("#devdoc-nav");
851 var $window = $(window);
852 var navHeight;
Scott Main3b90aff2013-08-01 18:09:35 -0700853
Scott Maine4d8f1b2012-06-21 18:03:05 -0700854 // Get the height of entire window and the total header height.
855 // Then figure out based on scroll position whether the header is visible
856 var windowHeight = $window.height();
857 var scrollTop = $window.scrollTop();
Dirk Doughertyc3921652014-05-13 16:55:26 -0700858 var headerHeight = $('#header-wrapper').outerHeight();
859 var headerVisible = scrollTop < stickyTop;
Scott Main3b90aff2013-08-01 18:09:35 -0700860
861 // get the height of space between nav and top of window.
Scott Maine4d8f1b2012-06-21 18:03:05 -0700862 // Could be either margin or top position, depending on whether the nav is fixed.
Scott Main3b90aff2013-08-01 18:09:35 -0700863 var topMargin = (parseInt($nav.css('margin-top')) || parseInt($nav.css('top'))) + 1;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700864 // add 1 for the #side-nav bottom margin
Scott Main3b90aff2013-08-01 18:09:35 -0700865
Scott Maine4d8f1b2012-06-21 18:03:05 -0700866 // Depending on whether the header is visible, set the side nav's height.
867 if (headerVisible) {
868 // The sidenav height grows as the header goes off screen
Dirk Doughertyc3921652014-05-13 16:55:26 -0700869 navHeight = windowHeight - (headerHeight - scrollTop) - topMargin;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700870 } else {
871 // Once header is off screen, the nav height is almost full window height
872 navHeight = windowHeight - topMargin;
873 }
Scott Main3b90aff2013-08-01 18:09:35 -0700874
875
876
Scott Maine4d8f1b2012-06-21 18:03:05 -0700877 $scrollPanes = $(".scroll-pane");
878 if ($scrollPanes.length > 1) {
879 // subtract the height of the api level widget and nav swapper from the available nav height
880 navHeight -= ($('#api-nav-header').outerHeight(true) + $('#nav-swap').outerHeight(true));
Scott Main3b90aff2013-08-01 18:09:35 -0700881
Scott Maine4d8f1b2012-06-21 18:03:05 -0700882 $("#swapper").css({height:navHeight + "px"});
883 if ($("#nav-tree").is(":visible")) {
884 $("#nav-tree").css({height:navHeight});
885 }
Scott Main3b90aff2013-08-01 18:09:35 -0700886
887 var classesHeight = navHeight - parseInt($("#resize-packages-nav").css("height")) - 10 + "px";
Scott Maine4d8f1b2012-06-21 18:03:05 -0700888 //subtract 10px to account for drag bar
Scott Main3b90aff2013-08-01 18:09:35 -0700889
890 // if the window becomes small enough to make the class panel height 0,
Scott Maine4d8f1b2012-06-21 18:03:05 -0700891 // then the package panel should begin to shrink
892 if (parseInt(classesHeight) <= 0) {
893 $("#resize-packages-nav").css({height:navHeight - 10}); //subtract 10px for drag bar
894 $("#packages-nav").css({height:navHeight - 10});
895 }
Scott Main3b90aff2013-08-01 18:09:35 -0700896
Scott Maine4d8f1b2012-06-21 18:03:05 -0700897 $("#classes-nav").css({'height':classesHeight, 'margin-top':'10px'});
898 $("#classes-nav .jspContainer").css({height:classesHeight});
Scott Main3b90aff2013-08-01 18:09:35 -0700899
900
Scott Maine4d8f1b2012-06-21 18:03:05 -0700901 } else {
902 $nav.height(navHeight);
903 }
Scott Main3b90aff2013-08-01 18:09:35 -0700904
Scott Maine4d8f1b2012-06-21 18:03:05 -0700905 if (delay) {
906 updateFromResize = true;
907 delayedReInitScrollbars(delay);
908 } else {
909 reInitScrollbars();
910 }
Scott Main3b90aff2013-08-01 18:09:35 -0700911
Scott Maine4d8f1b2012-06-21 18:03:05 -0700912}
913
914var updateScrollbars = false;
915var updateFromResize = false;
916
917/* Re-initialize the scrollbars to account for changed nav size.
918 * This method postpones the actual update by a 1/4 second in order to optimize the
919 * scroll performance while the header is still visible, because re-initializing the
920 * scroll panes is an intensive process.
921 */
922function delayedReInitScrollbars(delay) {
923 // If we're scheduled for an update, but have received another resize request
924 // before the scheduled resize has occured, just ignore the new request
925 // (and wait for the scheduled one).
926 if (updateScrollbars && updateFromResize) {
927 updateFromResize = false;
928 return;
929 }
Scott Main3b90aff2013-08-01 18:09:35 -0700930
Scott Maine4d8f1b2012-06-21 18:03:05 -0700931 // We're scheduled for an update and the update request came from this method's setTimeout
932 if (updateScrollbars && !updateFromResize) {
933 reInitScrollbars();
934 updateScrollbars = false;
935 } else {
936 updateScrollbars = true;
937 updateFromResize = false;
938 setTimeout('delayedReInitScrollbars()',delay);
939 }
940}
941
942/* Re-initialize the scrollbars to account for changed nav size. */
943function reInitScrollbars() {
944 var pane = $(".scroll-pane").each(function(){
945 var api = $(this).data('jsp');
946 if (!api) { setTimeout(reInitScrollbars,300); return;}
947 api.reinitialise( {verticalGutter:0} );
Scott Main3b90aff2013-08-01 18:09:35 -0700948 });
Scott Maine4d8f1b2012-06-21 18:03:05 -0700949 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
950}
951
952
953/* Resize the height of the nav panels in the reference,
954 * and save the new size to a cookie */
955function saveNavPanels() {
956 var basePath = getBaseUri(location.pathname);
957 var section = basePath.substring(1,basePath.indexOf("/",1));
smain@google.com6bdcb982014-11-14 11:53:07 -0800958 writeCookie("height", resizePackagesNav.css("height"), section);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700959}
960
961
962
963function restoreHeight(packageHeight) {
964 $("#resize-packages-nav").height(packageHeight);
965 $("#packages-nav").height(packageHeight);
966 // var classesHeight = navHeight - packageHeight;
967 // $("#classes-nav").css({height:classesHeight});
968 // $("#classes-nav .jspContainer").css({height:classesHeight});
969}
970
971
972
973/* ######### END RESIZE THE SIDENAV HEIGHT ########## */
974
975
976
977
978
Scott Main3b90aff2013-08-01 18:09:35 -0700979/** Scroll the jScrollPane to make the currently selected item visible
Scott Maine4d8f1b2012-06-21 18:03:05 -0700980 This is called when the page finished loading. */
981function scrollIntoView(nav) {
982 var $nav = $("#"+nav);
983 var element = $nav.jScrollPane({/* ...settings... */});
984 var api = element.data('jsp');
985
986 if ($nav.is(':visible')) {
987 var $selected = $(".selected", $nav);
Scott Mainbc729572013-07-30 18:00:51 -0700988 if ($selected.length == 0) {
989 // If no selected item found, exit
990 return;
991 }
Scott Main52dd2062013-08-15 12:22:28 -0700992 // get the selected item's offset from its container nav by measuring the item's offset
993 // relative to the document then subtract the container nav's offset relative to the document
994 var selectedOffset = $selected.offset().top - $nav.offset().top;
995 if (selectedOffset > $nav.height() * .8) { // multiply nav height by .8 so we move up the item
996 // if it's more than 80% down the nav
997 // scroll the item up by an amount equal to 80% the container nav's height
998 api.scrollTo(0, selectedOffset - ($nav.height() * .8), false);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700999 }
1000 }
1001}
1002
1003
1004
1005
1006
1007
1008/* Show popup dialogs */
1009function showDialog(id) {
1010 $dialog = $("#"+id);
1011 $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>');
1012 $dialog.wrapInner('<div/>');
1013 $dialog.removeClass("hide");
1014}
1015
1016
1017
1018
1019
1020/* ######### COOKIES! ########## */
1021
1022function readCookie(cookie) {
1023 var myCookie = cookie_namespace+"_"+cookie+"=";
1024 if (document.cookie) {
1025 var index = document.cookie.indexOf(myCookie);
1026 if (index != -1) {
1027 var valStart = index + myCookie.length;
1028 var valEnd = document.cookie.indexOf(";", valStart);
1029 if (valEnd == -1) {
1030 valEnd = document.cookie.length;
1031 }
1032 var val = document.cookie.substring(valStart, valEnd);
1033 return val;
1034 }
1035 }
1036 return 0;
1037}
1038
smain@google.com6bdcb982014-11-14 11:53:07 -08001039function writeCookie(cookie, val, section) {
Scott Maine4d8f1b2012-06-21 18:03:05 -07001040 if (val==undefined) return;
1041 section = section == null ? "_" : "_"+section+"_";
smain@google.com6bdcb982014-11-14 11:53:07 -08001042 var age = 2*365*24*60*60; // set max-age to 2 years
Scott Main3b90aff2013-08-01 18:09:35 -07001043 var cookieValue = cookie_namespace + section + cookie + "=" + val
smain@google.com80e38f42014-11-03 10:47:12 -08001044 + "; max-age=" + age +"; path=/";
Scott Maine4d8f1b2012-06-21 18:03:05 -07001045 document.cookie = cookieValue;
1046}
1047
1048/* ######### END COOKIES! ########## */
1049
1050
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001051var sticky = false;
Dirk Doughertyc3921652014-05-13 16:55:26 -07001052var stickyTop;
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001053var prevScrollLeft = 0; // used to compare current position to previous position of horiz scroll
Dirk Doughertyc3921652014-05-13 16:55:26 -07001054/* Sets the vertical scoll position at which the sticky bar should appear.
1055 This method is called to reset the position when search results appear or hide */
1056function setStickyTop() {
1057 stickyTop = $('#header-wrapper').outerHeight() - $('#sticky-header').outerHeight();
1058}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001059
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001060/*
Scott Mainb16376f2014-05-21 20:35:47 -07001061 * Displays sticky nav bar on pages when dac header scrolls out of view
Dirk Doughertyc3921652014-05-13 16:55:26 -07001062 */
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001063$(window).scroll(function(event) {
1064
1065 setStickyTop();
1066 var hiding = false;
1067 var $stickyEl = $('#sticky-header');
1068 var $menuEl = $('.menu-container');
1069 // Exit if there's no sidenav
1070 if ($('#side-nav').length == 0) return;
1071 // Exit if the mouse target is a DIV, because that means the event is coming
1072 // from a scrollable div and so there's no need to make adjustments to our layout
1073 if ($(event.target).nodeName == "DIV") {
1074 return;
1075 }
1076
1077 var top = $(window).scrollTop();
1078 // we set the navbar fixed when the scroll position is beyond the height of the site header...
1079 var shouldBeSticky = top >= stickyTop;
1080 // ... except if the document content is shorter than the sidenav height.
1081 // (this is necessary to avoid crazy behavior on OSX Lion due to overscroll bouncing)
1082 if ($("#doc-col").height() < $("#side-nav").height()) {
1083 shouldBeSticky = false;
1084 }
Scott Mainf5257812014-05-22 17:26:38 -07001085 // Account for horizontal scroll
1086 var scrollLeft = $(window).scrollLeft();
1087 // When the sidenav is fixed and user scrolls horizontally, reposition the sidenav to match
1088 if (sticky && (scrollLeft != prevScrollLeft)) {
1089 updateSideNavPosition();
1090 prevScrollLeft = scrollLeft;
1091 }
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001092
1093 // Don't continue if the header is sufficently far away
1094 // (to avoid intensive resizing that slows scrolling)
1095 if (sticky == shouldBeSticky) {
1096 return;
1097 }
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001098
1099 // If sticky header visible and position is now near top, hide sticky
1100 if (sticky && !shouldBeSticky) {
1101 sticky = false;
1102 hiding = true;
1103 // make the sidenav static again
1104 $('#devdoc-nav')
1105 .removeClass('fixed')
1106 .css({'width':'auto','margin':''})
1107 .prependTo('#side-nav');
1108 // delay hide the sticky
1109 $menuEl.removeClass('sticky-menu');
1110 $stickyEl.fadeOut(250);
1111 hiding = false;
1112
1113 // update the sidenaav position for side scrolling
1114 updateSideNavPosition();
1115 } else if (!sticky && shouldBeSticky) {
1116 sticky = true;
1117 $stickyEl.fadeIn(10);
1118 $menuEl.addClass('sticky-menu');
1119
1120 // make the sidenav fixed
1121 var width = $('#devdoc-nav').width();
1122 $('#devdoc-nav')
1123 .addClass('fixed')
1124 .css({'width':width+'px'})
1125 .prependTo('#body-content');
1126
1127 // update the sidenaav position for side scrolling
1128 updateSideNavPosition();
1129
1130 } else if (hiding && top < 15) {
1131 $menuEl.removeClass('sticky-menu');
1132 $stickyEl.hide();
1133 hiding = false;
1134 }
1135 resizeNav(250); // pass true in order to delay the scrollbar re-initialization for performance
1136});
1137
1138/*
1139 * Manages secion card states and nav resize to conclude loading
1140 */
Dirk Doughertyc3921652014-05-13 16:55:26 -07001141(function() {
1142 $(document).ready(function() {
1143
Dirk Doughertyc3921652014-05-13 16:55:26 -07001144 // Stack hover states
1145 $('.section-card-menu').each(function(index, el) {
1146 var height = $(el).height();
1147 $(el).css({height:height+'px', position:'relative'});
1148 var $cardInfo = $(el).find('.card-info');
1149
1150 $cardInfo.css({position: 'absolute', bottom:'0px', left:'0px', right:'0px', overflow:'visible'});
1151 });
1152
Dirk Doughertyc3921652014-05-13 16:55:26 -07001153 });
1154
1155})();
1156
Scott Maine4d8f1b2012-06-21 18:03:05 -07001157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
Scott Maind7026f72013-06-17 15:08:49 -07001170/* MISC LIBRARY FUNCTIONS */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001171
1172
1173
1174
1175
1176function toggle(obj, slide) {
1177 var ul = $("ul:first", obj);
1178 var li = ul.parent();
1179 if (li.hasClass("closed")) {
1180 if (slide) {
1181 ul.slideDown("fast");
1182 } else {
1183 ul.show();
1184 }
1185 li.removeClass("closed");
1186 li.addClass("open");
1187 $(".toggle-img", li).attr("title", "hide pages");
1188 } else {
1189 ul.slideUp("fast");
1190 li.removeClass("open");
1191 li.addClass("closed");
1192 $(".toggle-img", li).attr("title", "show pages");
1193 }
1194}
1195
1196
Scott Maine4d8f1b2012-06-21 18:03:05 -07001197function buildToggleLists() {
1198 $(".toggle-list").each(
1199 function(i) {
1200 $("div:first", this).append("<a class='toggle-img' href='#' title='show pages' onClick='toggle(this.parentNode.parentNode, true); return false;'></a>");
1201 $(this).addClass("closed");
1202 });
1203}
1204
1205
1206
Scott Maind7026f72013-06-17 15:08:49 -07001207function hideNestedItems(list, toggle) {
1208 $list = $(list);
1209 // hide nested lists
1210 if($list.hasClass('showing')) {
1211 $("li ol", $list).hide('fast');
1212 $list.removeClass('showing');
1213 // show nested lists
1214 } else {
1215 $("li ol", $list).show('fast');
1216 $list.addClass('showing');
1217 }
1218 $(".more,.less",$(toggle)).toggle();
1219}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001220
1221
smain@google.com95948b82014-06-16 19:24:25 -07001222/* Call this to add listeners to a <select> element for Studio/Eclipse/Other docs */
1223function setupIdeDocToggle() {
1224 $( "select.ide" ).change(function() {
1225 var selected = $(this).find("option:selected").attr("value");
1226 $(".select-ide").hide();
1227 $(".select-ide."+selected).show();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001228
smain@google.com95948b82014-06-16 19:24:25 -07001229 $("select.ide").val(selected);
1230 });
1231}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256/* REFERENCE NAV SWAP */
1257
1258
1259function getNavPref() {
1260 var v = readCookie('reference_nav');
1261 if (v != NAV_PREF_TREE) {
1262 v = NAV_PREF_PANELS;
1263 }
1264 return v;
1265}
1266
1267function chooseDefaultNav() {
1268 nav_pref = getNavPref();
1269 if (nav_pref == NAV_PREF_TREE) {
1270 $("#nav-panels").toggle();
1271 $("#panel-link").toggle();
1272 $("#nav-tree").toggle();
1273 $("#tree-link").toggle();
1274 }
1275}
1276
1277function swapNav() {
1278 if (nav_pref == NAV_PREF_TREE) {
1279 nav_pref = NAV_PREF_PANELS;
1280 } else {
1281 nav_pref = NAV_PREF_TREE;
1282 init_default_navtree(toRoot);
1283 }
smain@google.com6bdcb982014-11-14 11:53:07 -08001284 writeCookie("nav", nav_pref, "reference");
Scott Maine4d8f1b2012-06-21 18:03:05 -07001285
1286 $("#nav-panels").toggle();
1287 $("#panel-link").toggle();
1288 $("#nav-tree").toggle();
1289 $("#tree-link").toggle();
Scott Main3b90aff2013-08-01 18:09:35 -07001290
Scott Maine4d8f1b2012-06-21 18:03:05 -07001291 resizeNav();
1292
1293 // Gross nasty hack to make tree view show up upon first swap by setting height manually
1294 $("#nav-tree .jspContainer:visible")
1295 .css({'height':$("#nav-tree .jspContainer .jspPane").height() +'px'});
1296 // Another nasty hack to make the scrollbar appear now that we have height
1297 resizeNav();
Scott Main3b90aff2013-08-01 18:09:35 -07001298
Scott Maine4d8f1b2012-06-21 18:03:05 -07001299 if ($("#nav-tree").is(':visible')) {
1300 scrollIntoView("nav-tree");
1301 } else {
1302 scrollIntoView("packages-nav");
1303 scrollIntoView("classes-nav");
1304 }
1305}
1306
1307
1308
Scott Mainf5089842012-08-14 16:31:07 -07001309/* ############################################ */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001310/* ########## LOCALIZATION ############ */
Scott Mainf5089842012-08-14 16:31:07 -07001311/* ############################################ */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001312
1313function getBaseUri(uri) {
1314 var intlUrl = (uri.substring(0,6) == "/intl/");
1315 if (intlUrl) {
1316 base = uri.substring(uri.indexOf('intl/')+5,uri.length);
1317 base = base.substring(base.indexOf('/')+1, base.length);
1318 //alert("intl, returning base url: /" + base);
1319 return ("/" + base);
1320 } else {
1321 //alert("not intl, returning uri as found.");
1322 return uri;
1323 }
1324}
1325
1326function requestAppendHL(uri) {
1327//append "?hl=<lang> to an outgoing request (such as to blog)
1328 var lang = getLangPref();
1329 if (lang) {
1330 var q = 'hl=' + lang;
1331 uri += '?' + q;
1332 window.location = uri;
1333 return false;
1334 } else {
1335 return true;
1336 }
1337}
1338
1339
Scott Maine4d8f1b2012-06-21 18:03:05 -07001340function changeNavLang(lang) {
Scott Main6eb95f12012-10-02 17:12:23 -07001341 var $links = $("#devdoc-nav,#header,#nav-x,.training-nav-top,.content-footer").find("a["+lang+"-lang]");
1342 $links.each(function(i){ // for each link with a translation
1343 var $link = $(this);
1344 if (lang != "en") { // No need to worry about English, because a language change invokes new request
1345 // put the desired language from the attribute as the text
1346 $link.text($link.attr(lang+"-lang"))
Scott Maine4d8f1b2012-06-21 18:03:05 -07001347 }
Scott Main6eb95f12012-10-02 17:12:23 -07001348 });
Scott Maine4d8f1b2012-06-21 18:03:05 -07001349}
1350
Scott Main015d6162013-01-29 09:01:52 -08001351function changeLangPref(lang, submit) {
smain@google.com6bdcb982014-11-14 11:53:07 -08001352 writeCookie("pref_lang", lang, null);
Scott Main015d6162013-01-29 09:01:52 -08001353
1354 // ####### TODO: Remove this condition once we're stable on devsite #######
1355 // This condition is only needed if we still need to support legacy GAE server
1356 if (devsite) {
1357 // Switch language when on Devsite server
1358 if (submit) {
1359 $("#setlang").submit();
1360 }
1361 } else {
1362 // Switch language when on legacy GAE server
Scott Main015d6162013-01-29 09:01:52 -08001363 if (submit) {
1364 window.location = getBaseUri(location.pathname);
1365 }
Scott Maine4d8f1b2012-06-21 18:03:05 -07001366 }
1367}
1368
1369function loadLangPref() {
1370 var lang = readCookie("pref_lang");
1371 if (lang != 0) {
1372 $("#language").find("option[value='"+lang+"']").attr("selected",true);
1373 }
1374}
1375
1376function getLangPref() {
1377 var lang = $("#language").find(":selected").attr("value");
1378 if (!lang) {
1379 lang = readCookie("pref_lang");
1380 }
1381 return (lang != 0) ? lang : 'en';
1382}
1383
1384/* ########## END LOCALIZATION ############ */
1385
1386
1387
1388
1389
1390
1391/* Used to hide and reveal supplemental content, such as long code samples.
1392 See the companion CSS in android-developer-docs.css */
1393function toggleContent(obj) {
Scott Maindc63dda2013-08-22 16:03:21 -07001394 var div = $(obj).closest(".toggle-content");
1395 var toggleMe = $(".toggle-content-toggleme:eq(0)",div);
Scott Maine4d8f1b2012-06-21 18:03:05 -07001396 if (div.hasClass("closed")) { // if it's closed, open it
1397 toggleMe.slideDown();
Scott Maindc63dda2013-08-22 16:03:21 -07001398 $(".toggle-content-text:eq(0)", obj).toggle();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001399 div.removeClass("closed").addClass("open");
Scott Maindc63dda2013-08-22 16:03:21 -07001400 $(".toggle-content-img:eq(0)", div).attr("title", "hide").attr("src", toRoot
Scott Maine4d8f1b2012-06-21 18:03:05 -07001401 + "assets/images/triangle-opened.png");
1402 } else { // if it's open, close it
1403 toggleMe.slideUp('fast', function() { // Wait until the animation is done before closing arrow
Scott Maindc63dda2013-08-22 16:03:21 -07001404 $(".toggle-content-text:eq(0)", obj).toggle();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001405 div.removeClass("open").addClass("closed");
Scott Maindc63dda2013-08-22 16:03:21 -07001406 div.find(".toggle-content").removeClass("open").addClass("closed")
1407 .find(".toggle-content-toggleme").hide();
Scott Main3b90aff2013-08-01 18:09:35 -07001408 $(".toggle-content-img", div).attr("title", "show").attr("src", toRoot
Scott Maine4d8f1b2012-06-21 18:03:05 -07001409 + "assets/images/triangle-closed.png");
1410 });
1411 }
1412 return false;
1413}
Scott Mainf5089842012-08-14 16:31:07 -07001414
1415
Scott Maindb3678b2012-10-23 14:13:41 -07001416/* New version of expandable content */
1417function toggleExpandable(link,id) {
1418 if($(id).is(':visible')) {
1419 $(id).slideUp();
1420 $(link).removeClass('expanded');
1421 } else {
1422 $(id).slideDown();
1423 $(link).addClass('expanded');
1424 }
1425}
1426
1427function hideExpandable(ids) {
1428 $(ids).slideUp();
Scott Main55d99832012-11-12 23:03:59 -08001429 $(ids).prev('h4').find('a.expandable').removeClass('expanded');
Scott Maindb3678b2012-10-23 14:13:41 -07001430}
1431
Scott Mainf5089842012-08-14 16:31:07 -07001432
1433
1434
1435
Scott Main3b90aff2013-08-01 18:09:35 -07001436/*
Scott Mainf5089842012-08-14 16:31:07 -07001437 * Slideshow 1.0
1438 * Used on /index.html and /develop/index.html for carousel
1439 *
1440 * Sample usage:
1441 * HTML -
1442 * <div class="slideshow-container">
1443 * <a href="" class="slideshow-prev">Prev</a>
1444 * <a href="" class="slideshow-next">Next</a>
1445 * <ul>
1446 * <li class="item"><img src="images/marquee1.jpg"></li>
1447 * <li class="item"><img src="images/marquee2.jpg"></li>
1448 * <li class="item"><img src="images/marquee3.jpg"></li>
1449 * <li class="item"><img src="images/marquee4.jpg"></li>
1450 * </ul>
1451 * </div>
1452 *
1453 * <script type="text/javascript">
1454 * $('.slideshow-container').dacSlideshow({
1455 * auto: true,
1456 * btnPrev: '.slideshow-prev',
1457 * btnNext: '.slideshow-next'
1458 * });
1459 * </script>
1460 *
1461 * Options:
1462 * btnPrev: optional identifier for previous button
1463 * btnNext: optional identifier for next button
Scott Maineb410352013-01-14 19:03:40 -08001464 * btnPause: optional identifier for pause button
Scott Mainf5089842012-08-14 16:31:07 -07001465 * auto: whether or not to auto-proceed
1466 * speed: animation speed
1467 * autoTime: time between auto-rotation
1468 * easing: easing function for transition
1469 * start: item to select by default
1470 * scroll: direction to scroll in
1471 * pagination: whether or not to include dotted pagination
1472 *
1473 */
1474
1475 (function($) {
1476 $.fn.dacSlideshow = function(o) {
Scott Main3b90aff2013-08-01 18:09:35 -07001477
Scott Mainf5089842012-08-14 16:31:07 -07001478 //Options - see above
1479 o = $.extend({
1480 btnPrev: null,
1481 btnNext: null,
Scott Maineb410352013-01-14 19:03:40 -08001482 btnPause: null,
Scott Mainf5089842012-08-14 16:31:07 -07001483 auto: true,
1484 speed: 500,
1485 autoTime: 12000,
1486 easing: null,
1487 start: 0,
1488 scroll: 1,
1489 pagination: true
1490
1491 }, o || {});
Scott Main3b90aff2013-08-01 18:09:35 -07001492
1493 //Set up a carousel for each
Scott Mainf5089842012-08-14 16:31:07 -07001494 return this.each(function() {
1495
1496 var running = false;
1497 var animCss = o.vertical ? "top" : "left";
1498 var sizeCss = o.vertical ? "height" : "width";
1499 var div = $(this);
1500 var ul = $("ul", div);
1501 var tLi = $("li", ul);
Scott Main3b90aff2013-08-01 18:09:35 -07001502 var tl = tLi.size();
Scott Mainf5089842012-08-14 16:31:07 -07001503 var timer = null;
1504
1505 var li = $("li", ul);
1506 var itemLength = li.size();
1507 var curr = o.start;
1508
1509 li.css({float: o.vertical ? "none" : "left"});
1510 ul.css({margin: "0", padding: "0", position: "relative", "list-style-type": "none", "z-index": "1"});
1511 div.css({position: "relative", "z-index": "2", left: "0px"});
1512
1513 var liSize = o.vertical ? height(li) : width(li);
1514 var ulSize = liSize * itemLength;
1515 var divSize = liSize;
1516
1517 li.css({width: li.width(), height: li.height()});
1518 ul.css(sizeCss, ulSize+"px").css(animCss, -(curr*liSize));
1519
1520 div.css(sizeCss, divSize+"px");
Scott Main3b90aff2013-08-01 18:09:35 -07001521
Scott Mainf5089842012-08-14 16:31:07 -07001522 //Pagination
1523 if (o.pagination) {
1524 var pagination = $("<div class='pagination'></div>");
1525 var pag_ul = $("<ul></ul>");
1526 if (tl > 1) {
1527 for (var i=0;i<tl;i++) {
1528 var li = $("<li>"+i+"</li>");
1529 pag_ul.append(li);
1530 if (i==o.start) li.addClass('active');
1531 li.click(function() {
1532 go(parseInt($(this).text()));
1533 })
1534 }
1535 pagination.append(pag_ul);
1536 div.append(pagination);
1537 }
1538 }
Scott Main3b90aff2013-08-01 18:09:35 -07001539
Scott Mainf5089842012-08-14 16:31:07 -07001540 //Previous button
1541 if(o.btnPrev)
1542 $(o.btnPrev).click(function(e) {
1543 e.preventDefault();
1544 return go(curr-o.scroll);
1545 });
1546
1547 //Next button
1548 if(o.btnNext)
1549 $(o.btnNext).click(function(e) {
1550 e.preventDefault();
1551 return go(curr+o.scroll);
1552 });
Scott Maineb410352013-01-14 19:03:40 -08001553
1554 //Pause button
1555 if(o.btnPause)
1556 $(o.btnPause).click(function(e) {
1557 e.preventDefault();
1558 if ($(this).hasClass('paused')) {
1559 startRotateTimer();
1560 } else {
1561 pauseRotateTimer();
1562 }
1563 });
Scott Main3b90aff2013-08-01 18:09:35 -07001564
Scott Mainf5089842012-08-14 16:31:07 -07001565 //Auto rotation
1566 if(o.auto) startRotateTimer();
Scott Main3b90aff2013-08-01 18:09:35 -07001567
Scott Mainf5089842012-08-14 16:31:07 -07001568 function startRotateTimer() {
1569 clearInterval(timer);
1570 timer = setInterval(function() {
1571 if (curr == tl-1) {
1572 go(0);
1573 } else {
Scott Main3b90aff2013-08-01 18:09:35 -07001574 go(curr+o.scroll);
1575 }
Scott Mainf5089842012-08-14 16:31:07 -07001576 }, o.autoTime);
Scott Maineb410352013-01-14 19:03:40 -08001577 $(o.btnPause).removeClass('paused');
1578 }
1579
1580 function pauseRotateTimer() {
1581 clearInterval(timer);
1582 $(o.btnPause).addClass('paused');
Scott Mainf5089842012-08-14 16:31:07 -07001583 }
1584
1585 //Go to an item
1586 function go(to) {
1587 if(!running) {
1588
1589 if(to<0) {
1590 to = itemLength-1;
1591 } else if (to>itemLength-1) {
1592 to = 0;
1593 }
1594 curr = to;
1595
1596 running = true;
1597
1598 ul.animate(
1599 animCss == "left" ? { left: -(curr*liSize) } : { top: -(curr*liSize) } , o.speed, o.easing,
1600 function() {
1601 running = false;
1602 }
1603 );
1604
1605 $(o.btnPrev + "," + o.btnNext).removeClass("disabled");
1606 $( (curr-o.scroll<0 && o.btnPrev)
1607 ||
1608 (curr+o.scroll > itemLength && o.btnNext)
1609 ||
1610 []
1611 ).addClass("disabled");
1612
Scott Main3b90aff2013-08-01 18:09:35 -07001613
Scott Mainf5089842012-08-14 16:31:07 -07001614 var nav_items = $('li', pagination);
1615 nav_items.removeClass('active');
1616 nav_items.eq(to).addClass('active');
Scott Main3b90aff2013-08-01 18:09:35 -07001617
Scott Mainf5089842012-08-14 16:31:07 -07001618
1619 }
1620 if(o.auto) startRotateTimer();
1621 return false;
1622 };
1623 });
1624 };
1625
1626 function css(el, prop) {
1627 return parseInt($.css(el[0], prop)) || 0;
1628 };
1629 function width(el) {
1630 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1631 };
1632 function height(el) {
1633 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1634 };
1635
1636 })(jQuery);
1637
1638
Scott Main3b90aff2013-08-01 18:09:35 -07001639/*
Scott Mainf5089842012-08-14 16:31:07 -07001640 * dacSlideshow 1.0
1641 * Used on develop/index.html for side-sliding tabs
1642 *
1643 * Sample usage:
1644 * HTML -
1645 * <div class="slideshow-container">
1646 * <a href="" class="slideshow-prev">Prev</a>
1647 * <a href="" class="slideshow-next">Next</a>
1648 * <ul>
1649 * <li class="item"><img src="images/marquee1.jpg"></li>
1650 * <li class="item"><img src="images/marquee2.jpg"></li>
1651 * <li class="item"><img src="images/marquee3.jpg"></li>
1652 * <li class="item"><img src="images/marquee4.jpg"></li>
1653 * </ul>
1654 * </div>
1655 *
1656 * <script type="text/javascript">
1657 * $('.slideshow-container').dacSlideshow({
1658 * auto: true,
1659 * btnPrev: '.slideshow-prev',
1660 * btnNext: '.slideshow-next'
1661 * });
1662 * </script>
1663 *
1664 * Options:
1665 * btnPrev: optional identifier for previous button
1666 * btnNext: optional identifier for next button
1667 * auto: whether or not to auto-proceed
1668 * speed: animation speed
1669 * autoTime: time between auto-rotation
1670 * easing: easing function for transition
1671 * start: item to select by default
1672 * scroll: direction to scroll in
1673 * pagination: whether or not to include dotted pagination
1674 *
1675 */
1676 (function($) {
1677 $.fn.dacTabbedList = function(o) {
Scott Main3b90aff2013-08-01 18:09:35 -07001678
Scott Mainf5089842012-08-14 16:31:07 -07001679 //Options - see above
1680 o = $.extend({
1681 speed : 250,
1682 easing: null,
1683 nav_id: null,
1684 frame_id: null
1685 }, o || {});
Scott Main3b90aff2013-08-01 18:09:35 -07001686
1687 //Set up a carousel for each
Scott Mainf5089842012-08-14 16:31:07 -07001688 return this.each(function() {
1689
1690 var curr = 0;
1691 var running = false;
1692 var animCss = "margin-left";
1693 var sizeCss = "width";
1694 var div = $(this);
Scott Main3b90aff2013-08-01 18:09:35 -07001695
Scott Mainf5089842012-08-14 16:31:07 -07001696 var nav = $(o.nav_id, div);
1697 var nav_li = $("li", nav);
Scott Main3b90aff2013-08-01 18:09:35 -07001698 var nav_size = nav_li.size();
Scott Mainf5089842012-08-14 16:31:07 -07001699 var frame = div.find(o.frame_id);
1700 var content_width = $(frame).find('ul').width();
1701 //Buttons
1702 $(nav_li).click(function(e) {
1703 go($(nav_li).index($(this)));
1704 })
Scott Main3b90aff2013-08-01 18:09:35 -07001705
Scott Mainf5089842012-08-14 16:31:07 -07001706 //Go to an item
1707 function go(to) {
1708 if(!running) {
1709 curr = to;
1710 running = true;
1711
1712 frame.animate({ 'margin-left' : -(curr*content_width) }, o.speed, o.easing,
1713 function() {
1714 running = false;
1715 }
1716 );
1717
Scott Main3b90aff2013-08-01 18:09:35 -07001718
Scott Mainf5089842012-08-14 16:31:07 -07001719 nav_li.removeClass('active');
1720 nav_li.eq(to).addClass('active');
Scott Main3b90aff2013-08-01 18:09:35 -07001721
Scott Mainf5089842012-08-14 16:31:07 -07001722
1723 }
1724 return false;
1725 };
1726 });
1727 };
1728
1729 function css(el, prop) {
1730 return parseInt($.css(el[0], prop)) || 0;
1731 };
1732 function width(el) {
1733 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1734 };
1735 function height(el) {
1736 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1737 };
1738
1739 })(jQuery);
1740
1741
1742
1743
1744
1745/* ######################################################## */
1746/* ################ SEARCH SUGGESTIONS ################## */
1747/* ######################################################## */
1748
1749
Scott Main7e447ed2013-02-19 17:22:37 -08001750
Scott Main0e76e7e2013-03-12 10:24:07 -07001751var gSelectedIndex = -1; // the index position of currently highlighted suggestion
1752var gSelectedColumn = -1; // which column of suggestion lists is currently focused
1753
Scott Mainf5089842012-08-14 16:31:07 -07001754var gMatches = new Array();
1755var gLastText = "";
Scott Mainf5089842012-08-14 16:31:07 -07001756var gInitialized = false;
Scott Main7e447ed2013-02-19 17:22:37 -08001757var ROW_COUNT_FRAMEWORK = 20; // max number of results in list
1758var gListLength = 0;
1759
1760
1761var gGoogleMatches = new Array();
1762var ROW_COUNT_GOOGLE = 15; // max number of results in list
1763var gGoogleListLength = 0;
Scott Mainf5089842012-08-14 16:31:07 -07001764
Scott Main0e76e7e2013-03-12 10:24:07 -07001765var gDocsMatches = new Array();
1766var ROW_COUNT_DOCS = 100; // max number of results in list
1767var gDocsListLength = 0;
1768
Scott Mainde295272013-03-25 15:48:35 -07001769function onSuggestionClick(link) {
1770 // When user clicks a suggested document, track it
smain@google.comed677d72014-12-12 10:19:17 -08001771 ga('send', 'event', 'Suggestion Click', 'clicked: ' + $(link).attr('href'),
1772 'query: ' + $("#search_autocomplete").val().toLowerCase());
Scott Mainde295272013-03-25 15:48:35 -07001773}
1774
Scott Mainf5089842012-08-14 16:31:07 -07001775function set_item_selected($li, selected)
1776{
1777 if (selected) {
1778 $li.attr('class','jd-autocomplete jd-selected');
1779 } else {
1780 $li.attr('class','jd-autocomplete');
1781 }
1782}
1783
1784function set_item_values(toroot, $li, match)
1785{
1786 var $link = $('a',$li);
1787 $link.html(match.__hilabel || match.label);
1788 $link.attr('href',toroot + match.link);
1789}
1790
Scott Main719acb42013-12-05 16:05:09 -08001791function set_item_values_jd(toroot, $li, match)
1792{
1793 var $link = $('a',$li);
1794 $link.html(match.title);
1795 $link.attr('href',toroot + match.url);
1796}
1797
Scott Main0e76e7e2013-03-12 10:24:07 -07001798function new_suggestion($list) {
Scott Main7e447ed2013-02-19 17:22:37 -08001799 var $li = $("<li class='jd-autocomplete'></li>");
1800 $list.append($li);
1801
1802 $li.mousedown(function() {
1803 window.location = this.firstChild.getAttribute("href");
1804 });
1805 $li.mouseover(function() {
Scott Main0e76e7e2013-03-12 10:24:07 -07001806 $('.search_filtered_wrapper li').removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001807 $(this).addClass('jd-selected');
Scott Main0e76e7e2013-03-12 10:24:07 -07001808 gSelectedColumn = $(".search_filtered:visible").index($(this).closest('.search_filtered'));
1809 gSelectedIndex = $("li", $(".search_filtered:visible")[gSelectedColumn]).index(this);
Scott Main7e447ed2013-02-19 17:22:37 -08001810 });
Scott Mainde295272013-03-25 15:48:35 -07001811 $li.append("<a onclick='onSuggestionClick(this)'></a>");
Scott Main7e447ed2013-02-19 17:22:37 -08001812 $li.attr('class','show-item');
1813 return $li;
1814}
1815
Scott Mainf5089842012-08-14 16:31:07 -07001816function sync_selection_table(toroot)
1817{
Scott Mainf5089842012-08-14 16:31:07 -07001818 var $li; //list item jquery object
1819 var i; //list item iterator
Scott Main7e447ed2013-02-19 17:22:37 -08001820
Scott Main0e76e7e2013-03-12 10:24:07 -07001821 // if there are NO results at all, hide all columns
1822 if (!(gMatches.length > 0) && !(gGoogleMatches.length > 0) && !(gDocsMatches.length > 0)) {
1823 $('.suggest-card').hide(300);
1824 return;
1825 }
1826
1827 // if there are api results
Scott Main7e447ed2013-02-19 17:22:37 -08001828 if ((gMatches.length > 0) || (gGoogleMatches.length > 0)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001829 // reveal suggestion list
1830 $('.suggest-card.dummy').show();
1831 $('.suggest-card.reference').show();
1832 var listIndex = 0; // list index position
Scott Main7e447ed2013-02-19 17:22:37 -08001833
Scott Main0e76e7e2013-03-12 10:24:07 -07001834 // reset the lists
1835 $(".search_filtered_wrapper.reference li").remove();
Scott Main7e447ed2013-02-19 17:22:37 -08001836
Scott Main0e76e7e2013-03-12 10:24:07 -07001837 // ########### ANDROID RESULTS #############
1838 if (gMatches.length > 0) {
Scott Main7e447ed2013-02-19 17:22:37 -08001839
Scott Main0e76e7e2013-03-12 10:24:07 -07001840 // determine android results to show
1841 gListLength = gMatches.length < ROW_COUNT_FRAMEWORK ?
1842 gMatches.length : ROW_COUNT_FRAMEWORK;
1843 for (i=0; i<gListLength; i++) {
1844 var $li = new_suggestion($(".suggest-card.reference ul"));
1845 set_item_values(toroot, $li, gMatches[i]);
1846 set_item_selected($li, i == gSelectedIndex);
1847 }
1848 }
Scott Main7e447ed2013-02-19 17:22:37 -08001849
Scott Main0e76e7e2013-03-12 10:24:07 -07001850 // ########### GOOGLE RESULTS #############
1851 if (gGoogleMatches.length > 0) {
1852 // show header for list
1853 $(".suggest-card.reference ul").append("<li class='header'>in Google Services:</li>");
Scott Main7e447ed2013-02-19 17:22:37 -08001854
Scott Main0e76e7e2013-03-12 10:24:07 -07001855 // determine google results to show
1856 gGoogleListLength = gGoogleMatches.length < ROW_COUNT_GOOGLE ? gGoogleMatches.length : ROW_COUNT_GOOGLE;
1857 for (i=0; i<gGoogleListLength; i++) {
1858 var $li = new_suggestion($(".suggest-card.reference ul"));
1859 set_item_values(toroot, $li, gGoogleMatches[i]);
1860 set_item_selected($li, i == gSelectedIndex);
1861 }
1862 }
Scott Mainf5089842012-08-14 16:31:07 -07001863 } else {
Scott Main0e76e7e2013-03-12 10:24:07 -07001864 $('.suggest-card.reference').hide();
1865 $('.suggest-card.dummy').hide();
1866 }
1867
1868 // ########### JD DOC RESULTS #############
1869 if (gDocsMatches.length > 0) {
1870 // reset the lists
1871 $(".search_filtered_wrapper.docs li").remove();
1872
1873 // determine google results to show
Scott Main719acb42013-12-05 16:05:09 -08001874 // NOTE: The order of the conditions below for the sugg.type MUST BE SPECIFIC:
1875 // The order must match the reverse order that each section appears as a card in
1876 // the suggestion UI... this may be only for the "develop" grouped items though.
Scott Main0e76e7e2013-03-12 10:24:07 -07001877 gDocsListLength = gDocsMatches.length < ROW_COUNT_DOCS ? gDocsMatches.length : ROW_COUNT_DOCS;
1878 for (i=0; i<gDocsListLength; i++) {
1879 var sugg = gDocsMatches[i];
1880 var $li;
1881 if (sugg.type == "design") {
1882 $li = new_suggestion($(".suggest-card.design ul"));
1883 } else
1884 if (sugg.type == "distribute") {
1885 $li = new_suggestion($(".suggest-card.distribute ul"));
1886 } else
Scott Main719acb42013-12-05 16:05:09 -08001887 if (sugg.type == "samples") {
1888 $li = new_suggestion($(".suggest-card.develop .child-card.samples"));
1889 } else
Scott Main0e76e7e2013-03-12 10:24:07 -07001890 if (sugg.type == "training") {
1891 $li = new_suggestion($(".suggest-card.develop .child-card.training"));
1892 } else
Scott Main719acb42013-12-05 16:05:09 -08001893 if (sugg.type == "about"||"guide"||"tools"||"google") {
Scott Main0e76e7e2013-03-12 10:24:07 -07001894 $li = new_suggestion($(".suggest-card.develop .child-card.guides"));
1895 } else {
1896 continue;
1897 }
1898
Scott Main719acb42013-12-05 16:05:09 -08001899 set_item_values_jd(toroot, $li, sugg);
Scott Main0e76e7e2013-03-12 10:24:07 -07001900 set_item_selected($li, i == gSelectedIndex);
1901 }
1902
1903 // add heading and show or hide card
1904 if ($(".suggest-card.design li").length > 0) {
1905 $(".suggest-card.design ul").prepend("<li class='header'>Design:</li>");
1906 $(".suggest-card.design").show(300);
1907 } else {
1908 $('.suggest-card.design').hide(300);
1909 }
1910 if ($(".suggest-card.distribute li").length > 0) {
1911 $(".suggest-card.distribute ul").prepend("<li class='header'>Distribute:</li>");
1912 $(".suggest-card.distribute").show(300);
1913 } else {
1914 $('.suggest-card.distribute').hide(300);
1915 }
1916 if ($(".child-card.guides li").length > 0) {
1917 $(".child-card.guides").prepend("<li class='header'>Guides:</li>");
1918 $(".child-card.guides li").appendTo(".suggest-card.develop ul");
1919 }
1920 if ($(".child-card.training li").length > 0) {
1921 $(".child-card.training").prepend("<li class='header'>Training:</li>");
1922 $(".child-card.training li").appendTo(".suggest-card.develop ul");
1923 }
Scott Main719acb42013-12-05 16:05:09 -08001924 if ($(".child-card.samples li").length > 0) {
1925 $(".child-card.samples").prepend("<li class='header'>Samples:</li>");
1926 $(".child-card.samples li").appendTo(".suggest-card.develop ul");
1927 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001928
1929 if ($(".suggest-card.develop li").length > 0) {
1930 $(".suggest-card.develop").show(300);
1931 } else {
1932 $('.suggest-card.develop').hide(300);
1933 }
1934
1935 } else {
1936 $('.search_filtered_wrapper.docs .suggest-card:not(.dummy)').hide(300);
Scott Mainf5089842012-08-14 16:31:07 -07001937 }
1938}
1939
Scott Main0e76e7e2013-03-12 10:24:07 -07001940/** Called by the search input's onkeydown and onkeyup events.
1941 * Handles navigation with keyboard arrows, Enter key to invoke search,
1942 * otherwise invokes search suggestions on key-up event.
1943 * @param e The JS event
1944 * @param kd True if the event is key-down
Scott Main3b90aff2013-08-01 18:09:35 -07001945 * @param toroot A string for the site's root path
Scott Main0e76e7e2013-03-12 10:24:07 -07001946 * @returns True if the event should bubble up
1947 */
Scott Mainf5089842012-08-14 16:31:07 -07001948function search_changed(e, kd, toroot)
1949{
Scott Main719acb42013-12-05 16:05:09 -08001950 var currentLang = getLangPref();
Scott Mainf5089842012-08-14 16:31:07 -07001951 var search = document.getElementById("search_autocomplete");
1952 var text = search.value.replace(/(^ +)|( +$)/g, '');
Scott Main0e76e7e2013-03-12 10:24:07 -07001953 // get the ul hosting the currently selected item
1954 gSelectedColumn = gSelectedColumn >= 0 ? gSelectedColumn : 0;
1955 var $columns = $(".search_filtered_wrapper").find(".search_filtered:visible");
1956 var $selectedUl = $columns[gSelectedColumn];
1957
Scott Mainf5089842012-08-14 16:31:07 -07001958 // show/hide the close button
1959 if (text != '') {
1960 $(".search .close").removeClass("hide");
1961 } else {
1962 $(".search .close").addClass("hide");
1963 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001964 // 27 = esc
1965 if (e.keyCode == 27) {
1966 // close all search results
1967 if (kd) $('.search .close').trigger('click');
1968 return true;
1969 }
Scott Mainf5089842012-08-14 16:31:07 -07001970 // 13 = enter
Scott Main0e76e7e2013-03-12 10:24:07 -07001971 else if (e.keyCode == 13) {
1972 if (gSelectedIndex < 0) {
1973 $('.suggest-card').hide();
Scott Main7e447ed2013-02-19 17:22:37 -08001974 if ($("#searchResults").is(":hidden") && (search.value != "")) {
1975 // if results aren't showing (and text not empty), return true to allow search to execute
Dirk Doughertyc3921652014-05-13 16:55:26 -07001976 $('body,html').animate({scrollTop:0}, '500', 'swing');
Scott Mainf5089842012-08-14 16:31:07 -07001977 return true;
1978 } else {
1979 // otherwise, results are already showing, so allow ajax to auto refresh the results
1980 // and ignore this Enter press to avoid the reload.
1981 return false;
1982 }
1983 } else if (kd && gSelectedIndex >= 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001984 // click the link corresponding to selected item
1985 $("a",$("li",$selectedUl)[gSelectedIndex]).get()[0].click();
Scott Mainf5089842012-08-14 16:31:07 -07001986 return false;
1987 }
1988 }
Scott Mainb16376f2014-05-21 20:35:47 -07001989 // If Google results are showing, return true to allow ajax search to execute
Scott Main0e76e7e2013-03-12 10:24:07 -07001990 else if ($("#searchResults").is(":visible")) {
Scott Mainb16376f2014-05-21 20:35:47 -07001991 // Also, if search_results is scrolled out of view, scroll to top to make results visible
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001992 if ((sticky ) && (search.value != "")) {
1993 $('body,html').animate({scrollTop:0}, '500', 'swing');
1994 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001995 return true;
1996 }
1997 // 38 UP ARROW
Scott Mainf5089842012-08-14 16:31:07 -07001998 else if (kd && (e.keyCode == 38)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001999 // if the next item is a header, skip it
2000 if ($($("li", $selectedUl)[gSelectedIndex-1]).hasClass("header")) {
Scott Mainf5089842012-08-14 16:31:07 -07002001 gSelectedIndex--;
Scott Main7e447ed2013-02-19 17:22:37 -08002002 }
2003 if (gSelectedIndex >= 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002004 $('li', $selectedUl).removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08002005 gSelectedIndex--;
Scott Main0e76e7e2013-03-12 10:24:07 -07002006 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
2007 // If user reaches top, reset selected column
2008 if (gSelectedIndex < 0) {
2009 gSelectedColumn = -1;
2010 }
Scott Mainf5089842012-08-14 16:31:07 -07002011 }
2012 return false;
2013 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002014 // 40 DOWN ARROW
Scott Mainf5089842012-08-14 16:31:07 -07002015 else if (kd && (e.keyCode == 40)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002016 // if the next item is a header, skip it
2017 if ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header")) {
Scott Mainf5089842012-08-14 16:31:07 -07002018 gSelectedIndex++;
Scott Main7e447ed2013-02-19 17:22:37 -08002019 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002020 if ((gSelectedIndex < $("li", $selectedUl).length-1) ||
2021 ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header"))) {
2022 $('li', $selectedUl).removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08002023 gSelectedIndex++;
Scott Main0e76e7e2013-03-12 10:24:07 -07002024 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
Scott Mainf5089842012-08-14 16:31:07 -07002025 }
2026 return false;
2027 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002028 // Consider left/right arrow navigation
2029 // NOTE: Order of suggest columns are reverse order (index position 0 is on right)
2030 else if (kd && $columns.length > 1 && gSelectedColumn >= 0) {
2031 // 37 LEFT ARROW
2032 // go left only if current column is not left-most column (last column)
2033 if (e.keyCode == 37 && gSelectedColumn < $columns.length - 1) {
2034 $('li', $selectedUl).removeClass('jd-selected');
2035 gSelectedColumn++;
2036 $selectedUl = $columns[gSelectedColumn];
2037 // keep or reset the selected item to last item as appropriate
2038 gSelectedIndex = gSelectedIndex >
2039 $("li", $selectedUl).length-1 ?
2040 $("li", $selectedUl).length-1 : gSelectedIndex;
2041 // if the corresponding item is a header, move down
2042 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
2043 gSelectedIndex++;
2044 }
Scott Main3b90aff2013-08-01 18:09:35 -07002045 // set item selected
Scott Main0e76e7e2013-03-12 10:24:07 -07002046 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
2047 return false;
2048 }
2049 // 39 RIGHT ARROW
2050 // go right only if current column is not the right-most column (first column)
2051 else if (e.keyCode == 39 && gSelectedColumn > 0) {
2052 $('li', $selectedUl).removeClass('jd-selected');
2053 gSelectedColumn--;
2054 $selectedUl = $columns[gSelectedColumn];
2055 // keep or reset the selected item to last item as appropriate
2056 gSelectedIndex = gSelectedIndex >
2057 $("li", $selectedUl).length-1 ?
2058 $("li", $selectedUl).length-1 : gSelectedIndex;
2059 // if the corresponding item is a header, move down
2060 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
2061 gSelectedIndex++;
2062 }
Scott Main3b90aff2013-08-01 18:09:35 -07002063 // set item selected
Scott Main0e76e7e2013-03-12 10:24:07 -07002064 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
2065 return false;
2066 }
2067 }
2068
Scott Main719acb42013-12-05 16:05:09 -08002069 // if key-up event and not arrow down/up/left/right,
2070 // read the search query and add suggestions to gMatches
Scott Main0e76e7e2013-03-12 10:24:07 -07002071 else if (!kd && (e.keyCode != 40)
2072 && (e.keyCode != 38)
2073 && (e.keyCode != 37)
2074 && (e.keyCode != 39)) {
2075 gSelectedIndex = -1;
Scott Mainf5089842012-08-14 16:31:07 -07002076 gMatches = new Array();
2077 matchedCount = 0;
Scott Main7e447ed2013-02-19 17:22:37 -08002078 gGoogleMatches = new Array();
2079 matchedCountGoogle = 0;
Scott Main0e76e7e2013-03-12 10:24:07 -07002080 gDocsMatches = new Array();
2081 matchedCountDocs = 0;
Scott Main7e447ed2013-02-19 17:22:37 -08002082
2083 // Search for Android matches
Scott Mainf5089842012-08-14 16:31:07 -07002084 for (var i=0; i<DATA.length; i++) {
2085 var s = DATA[i];
2086 if (text.length != 0 &&
2087 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
2088 gMatches[matchedCount] = s;
2089 matchedCount++;
2090 }
2091 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002092 rank_autocomplete_api_results(text, gMatches);
Scott Mainf5089842012-08-14 16:31:07 -07002093 for (var i=0; i<gMatches.length; i++) {
2094 var s = gMatches[i];
Scott Main7e447ed2013-02-19 17:22:37 -08002095 }
2096
2097
2098 // Search for Google matches
2099 for (var i=0; i<GOOGLE_DATA.length; i++) {
2100 var s = GOOGLE_DATA[i];
2101 if (text.length != 0 &&
2102 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
2103 gGoogleMatches[matchedCountGoogle] = s;
2104 matchedCountGoogle++;
Scott Mainf5089842012-08-14 16:31:07 -07002105 }
2106 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002107 rank_autocomplete_api_results(text, gGoogleMatches);
Scott Main7e447ed2013-02-19 17:22:37 -08002108 for (var i=0; i<gGoogleMatches.length; i++) {
2109 var s = gGoogleMatches[i];
2110 }
2111
Scott Mainf5089842012-08-14 16:31:07 -07002112 highlight_autocomplete_result_labels(text);
Scott Main0e76e7e2013-03-12 10:24:07 -07002113
2114
2115
Scott Main719acb42013-12-05 16:05:09 -08002116 // Search for matching JD docs
Dirk Dougherty9b7f8f22014-11-01 17:08:56 -07002117 if (text.length >= 2) {
Scott Main719acb42013-12-05 16:05:09 -08002118 // Regex to match only the beginning of a word
2119 var textRegex = new RegExp("\\b" + text.toLowerCase(), "g");
2120
2121
2122 // Search for Training classes
2123 for (var i=0; i<TRAINING_RESOURCES.length; i++) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002124 // current search comparison, with counters for tag and title,
2125 // used later to improve ranking
Scott Main719acb42013-12-05 16:05:09 -08002126 var s = TRAINING_RESOURCES[i];
Scott Main0e76e7e2013-03-12 10:24:07 -07002127 s.matched_tag = 0;
2128 s.matched_title = 0;
2129 var matched = false;
2130
2131 // Check if query matches any tags; work backwards toward 1 to assist ranking
Scott Main719acb42013-12-05 16:05:09 -08002132 for (var j = s.keywords.length - 1; j >= 0; j--) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002133 // it matches a tag
Scott Main719acb42013-12-05 16:05:09 -08002134 if (s.keywords[j].toLowerCase().match(textRegex)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002135 matched = true;
2136 s.matched_tag = j + 1; // add 1 to index position
2137 }
2138 }
Scott Main719acb42013-12-05 16:05:09 -08002139 // Don't consider doc title for lessons (only for class landing pages),
2140 // unless the lesson has a tag that already matches
2141 if ((s.lang == currentLang) &&
2142 (!(s.type == "training" && s.url.indexOf("index.html") == -1) || matched)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002143 // it matches the doc title
Scott Main719acb42013-12-05 16:05:09 -08002144 if (s.title.toLowerCase().match(textRegex)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002145 matched = true;
2146 s.matched_title = 1;
2147 }
2148 }
2149 if (matched) {
2150 gDocsMatches[matchedCountDocs] = s;
2151 matchedCountDocs++;
2152 }
2153 }
Scott Main719acb42013-12-05 16:05:09 -08002154
2155
2156 // Search for API Guides
2157 for (var i=0; i<GUIDE_RESOURCES.length; i++) {
2158 // current search comparison, with counters for tag and title,
2159 // used later to improve ranking
2160 var s = GUIDE_RESOURCES[i];
2161 s.matched_tag = 0;
2162 s.matched_title = 0;
2163 var matched = false;
2164
2165 // Check if query matches any tags; work backwards toward 1 to assist ranking
2166 for (var j = s.keywords.length - 1; j >= 0; j--) {
2167 // it matches a tag
2168 if (s.keywords[j].toLowerCase().match(textRegex)) {
2169 matched = true;
2170 s.matched_tag = j + 1; // add 1 to index position
2171 }
2172 }
2173 // Check if query matches the doc title, but only for current language
2174 if (s.lang == currentLang) {
2175 // if query matches the doc title
2176 if (s.title.toLowerCase().match(textRegex)) {
2177 matched = true;
2178 s.matched_title = 1;
2179 }
2180 }
2181 if (matched) {
2182 gDocsMatches[matchedCountDocs] = s;
2183 matchedCountDocs++;
2184 }
2185 }
2186
2187
2188 // Search for Tools Guides
2189 for (var i=0; i<TOOLS_RESOURCES.length; i++) {
2190 // current search comparison, with counters for tag and title,
2191 // used later to improve ranking
2192 var s = TOOLS_RESOURCES[i];
2193 s.matched_tag = 0;
2194 s.matched_title = 0;
2195 var matched = false;
2196
2197 // Check if query matches any tags; work backwards toward 1 to assist ranking
2198 for (var j = s.keywords.length - 1; j >= 0; j--) {
2199 // it matches a tag
2200 if (s.keywords[j].toLowerCase().match(textRegex)) {
2201 matched = true;
2202 s.matched_tag = j + 1; // add 1 to index position
2203 }
2204 }
2205 // Check if query matches the doc title, but only for current language
2206 if (s.lang == currentLang) {
2207 // if query matches the doc title
2208 if (s.title.toLowerCase().match(textRegex)) {
2209 matched = true;
2210 s.matched_title = 1;
2211 }
2212 }
2213 if (matched) {
2214 gDocsMatches[matchedCountDocs] = s;
2215 matchedCountDocs++;
2216 }
2217 }
2218
2219
2220 // Search for About docs
2221 for (var i=0; i<ABOUT_RESOURCES.length; i++) {
2222 // current search comparison, with counters for tag and title,
2223 // used later to improve ranking
2224 var s = ABOUT_RESOURCES[i];
2225 s.matched_tag = 0;
2226 s.matched_title = 0;
2227 var matched = false;
2228
2229 // Check if query matches any tags; work backwards toward 1 to assist ranking
2230 for (var j = s.keywords.length - 1; j >= 0; j--) {
2231 // it matches a tag
2232 if (s.keywords[j].toLowerCase().match(textRegex)) {
2233 matched = true;
2234 s.matched_tag = j + 1; // add 1 to index position
2235 }
2236 }
2237 // Check if query matches the doc title, but only for current language
2238 if (s.lang == currentLang) {
2239 // if query matches the doc title
2240 if (s.title.toLowerCase().match(textRegex)) {
2241 matched = true;
2242 s.matched_title = 1;
2243 }
2244 }
2245 if (matched) {
2246 gDocsMatches[matchedCountDocs] = s;
2247 matchedCountDocs++;
2248 }
2249 }
2250
2251
2252 // Search for Design guides
2253 for (var i=0; i<DESIGN_RESOURCES.length; i++) {
2254 // current search comparison, with counters for tag and title,
2255 // used later to improve ranking
2256 var s = DESIGN_RESOURCES[i];
2257 s.matched_tag = 0;
2258 s.matched_title = 0;
2259 var matched = false;
2260
2261 // Check if query matches any tags; work backwards toward 1 to assist ranking
2262 for (var j = s.keywords.length - 1; j >= 0; j--) {
2263 // it matches a tag
2264 if (s.keywords[j].toLowerCase().match(textRegex)) {
2265 matched = true;
2266 s.matched_tag = j + 1; // add 1 to index position
2267 }
2268 }
2269 // Check if query matches the doc title, but only for current language
2270 if (s.lang == currentLang) {
2271 // if query matches the doc title
2272 if (s.title.toLowerCase().match(textRegex)) {
2273 matched = true;
2274 s.matched_title = 1;
2275 }
2276 }
2277 if (matched) {
2278 gDocsMatches[matchedCountDocs] = s;
2279 matchedCountDocs++;
2280 }
2281 }
2282
2283
2284 // Search for Distribute guides
2285 for (var i=0; i<DISTRIBUTE_RESOURCES.length; i++) {
2286 // current search comparison, with counters for tag and title,
2287 // used later to improve ranking
2288 var s = DISTRIBUTE_RESOURCES[i];
2289 s.matched_tag = 0;
2290 s.matched_title = 0;
2291 var matched = false;
2292
2293 // Check if query matches any tags; work backwards toward 1 to assist ranking
2294 for (var j = s.keywords.length - 1; j >= 0; j--) {
2295 // it matches a tag
2296 if (s.keywords[j].toLowerCase().match(textRegex)) {
2297 matched = true;
2298 s.matched_tag = j + 1; // add 1 to index position
2299 }
2300 }
2301 // Check if query matches the doc title, but only for current language
2302 if (s.lang == currentLang) {
2303 // if query matches the doc title
2304 if (s.title.toLowerCase().match(textRegex)) {
2305 matched = true;
2306 s.matched_title = 1;
2307 }
2308 }
2309 if (matched) {
2310 gDocsMatches[matchedCountDocs] = s;
2311 matchedCountDocs++;
2312 }
2313 }
2314
2315
2316 // Search for Google guides
2317 for (var i=0; i<GOOGLE_RESOURCES.length; i++) {
2318 // current search comparison, with counters for tag and title,
2319 // used later to improve ranking
2320 var s = GOOGLE_RESOURCES[i];
2321 s.matched_tag = 0;
2322 s.matched_title = 0;
2323 var matched = false;
2324
2325 // Check if query matches any tags; work backwards toward 1 to assist ranking
2326 for (var j = s.keywords.length - 1; j >= 0; j--) {
2327 // it matches a tag
2328 if (s.keywords[j].toLowerCase().match(textRegex)) {
2329 matched = true;
2330 s.matched_tag = j + 1; // add 1 to index position
2331 }
2332 }
2333 // Check if query matches the doc title, but only for current language
2334 if (s.lang == currentLang) {
2335 // if query matches the doc title
2336 if (s.title.toLowerCase().match(textRegex)) {
2337 matched = true;
2338 s.matched_title = 1;
2339 }
2340 }
2341 if (matched) {
2342 gDocsMatches[matchedCountDocs] = s;
2343 matchedCountDocs++;
2344 }
2345 }
2346
2347
2348 // Search for Samples
2349 for (var i=0; i<SAMPLES_RESOURCES.length; i++) {
2350 // current search comparison, with counters for tag and title,
2351 // used later to improve ranking
2352 var s = SAMPLES_RESOURCES[i];
2353 s.matched_tag = 0;
2354 s.matched_title = 0;
2355 var matched = false;
2356 // Check if query matches any tags; work backwards toward 1 to assist ranking
2357 for (var j = s.keywords.length - 1; j >= 0; j--) {
2358 // it matches a tag
2359 if (s.keywords[j].toLowerCase().match(textRegex)) {
2360 matched = true;
2361 s.matched_tag = j + 1; // add 1 to index position
2362 }
2363 }
2364 // Check if query matches the doc title, but only for current language
2365 if (s.lang == currentLang) {
2366 // if query matches the doc title.t
2367 if (s.title.toLowerCase().match(textRegex)) {
2368 matched = true;
2369 s.matched_title = 1;
2370 }
2371 }
2372 if (matched) {
2373 gDocsMatches[matchedCountDocs] = s;
2374 matchedCountDocs++;
2375 }
2376 }
2377
2378 // Rank/sort all the matched pages
Scott Main0e76e7e2013-03-12 10:24:07 -07002379 rank_autocomplete_doc_results(text, gDocsMatches);
2380 }
2381
2382 // draw the suggestions
Scott Mainf5089842012-08-14 16:31:07 -07002383 sync_selection_table(toroot);
2384 return true; // allow the event to bubble up to the search api
2385 }
2386}
2387
Scott Main0e76e7e2013-03-12 10:24:07 -07002388/* Order the jd doc result list based on match quality */
2389function rank_autocomplete_doc_results(query, matches) {
2390 query = query || '';
2391 if (!matches || !matches.length)
2392 return;
2393
2394 var _resultScoreFn = function(match) {
2395 var score = 1.0;
2396
2397 // if the query matched a tag
2398 if (match.matched_tag > 0) {
2399 // multiply score by factor relative to position in tags list (max of 3)
2400 score *= 3 / match.matched_tag;
2401
2402 // if it also matched the title
2403 if (match.matched_title > 0) {
2404 score *= 2;
2405 }
2406 } else if (match.matched_title > 0) {
2407 score *= 3;
2408 }
2409
2410 return score;
2411 };
2412
2413 for (var i=0; i<matches.length; i++) {
2414 matches[i].__resultScore = _resultScoreFn(matches[i]);
2415 }
2416
2417 matches.sort(function(a,b){
2418 var n = b.__resultScore - a.__resultScore;
2419 if (n == 0) // lexicographical sort if scores are the same
2420 n = (a.label < b.label) ? -1 : 1;
2421 return n;
2422 });
2423}
2424
Scott Main7e447ed2013-02-19 17:22:37 -08002425/* Order the result list based on match quality */
Scott Main0e76e7e2013-03-12 10:24:07 -07002426function rank_autocomplete_api_results(query, matches) {
Scott Mainf5089842012-08-14 16:31:07 -07002427 query = query || '';
Scott Main7e447ed2013-02-19 17:22:37 -08002428 if (!matches || !matches.length)
Scott Mainf5089842012-08-14 16:31:07 -07002429 return;
2430
2431 // helper function that gets the last occurence index of the given regex
2432 // in the given string, or -1 if not found
2433 var _lastSearch = function(s, re) {
2434 if (s == '')
2435 return -1;
2436 var l = -1;
2437 var tmp;
2438 while ((tmp = s.search(re)) >= 0) {
2439 if (l < 0) l = 0;
2440 l += tmp;
2441 s = s.substr(tmp + 1);
2442 }
2443 return l;
2444 };
2445
2446 // helper function that counts the occurrences of a given character in
2447 // a given string
2448 var _countChar = function(s, c) {
2449 var n = 0;
2450 for (var i=0; i<s.length; i++)
2451 if (s.charAt(i) == c) ++n;
2452 return n;
2453 };
2454
2455 var queryLower = query.toLowerCase();
2456 var queryAlnum = (queryLower.match(/\w+/) || [''])[0];
2457 var partPrefixAlnumRE = new RegExp('\\b' + queryAlnum);
2458 var partExactAlnumRE = new RegExp('\\b' + queryAlnum + '\\b');
2459
2460 var _resultScoreFn = function(result) {
2461 // scores are calculated based on exact and prefix matches,
2462 // and then number of path separators (dots) from the last
2463 // match (i.e. favoring classes and deep package names)
2464 var score = 1.0;
2465 var labelLower = result.label.toLowerCase();
2466 var t;
2467 t = _lastSearch(labelLower, partExactAlnumRE);
2468 if (t >= 0) {
2469 // exact part match
2470 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2471 score *= 200 / (partsAfter + 1);
2472 } else {
2473 t = _lastSearch(labelLower, partPrefixAlnumRE);
2474 if (t >= 0) {
2475 // part prefix match
2476 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2477 score *= 20 / (partsAfter + 1);
2478 }
2479 }
2480
2481 return score;
2482 };
2483
Scott Main7e447ed2013-02-19 17:22:37 -08002484 for (var i=0; i<matches.length; i++) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002485 // if the API is deprecated, default score is 0; otherwise, perform scoring
2486 if (matches[i].deprecated == "true") {
2487 matches[i].__resultScore = 0;
2488 } else {
2489 matches[i].__resultScore = _resultScoreFn(matches[i]);
2490 }
Scott Mainf5089842012-08-14 16:31:07 -07002491 }
2492
Scott Main7e447ed2013-02-19 17:22:37 -08002493 matches.sort(function(a,b){
Scott Mainf5089842012-08-14 16:31:07 -07002494 var n = b.__resultScore - a.__resultScore;
2495 if (n == 0) // lexicographical sort if scores are the same
2496 n = (a.label < b.label) ? -1 : 1;
2497 return n;
2498 });
2499}
2500
Scott Main7e447ed2013-02-19 17:22:37 -08002501/* Add emphasis to part of string that matches query */
Scott Mainf5089842012-08-14 16:31:07 -07002502function highlight_autocomplete_result_labels(query) {
2503 query = query || '';
Scott Main7e447ed2013-02-19 17:22:37 -08002504 if ((!gMatches || !gMatches.length) && (!gGoogleMatches || !gGoogleMatches.length))
Scott Mainf5089842012-08-14 16:31:07 -07002505 return;
2506
2507 var queryLower = query.toLowerCase();
2508 var queryAlnumDot = (queryLower.match(/[\w\.]+/) || [''])[0];
2509 var queryRE = new RegExp(
2510 '(' + queryAlnumDot.replace(/\./g, '\\.') + ')', 'ig');
2511 for (var i=0; i<gMatches.length; i++) {
2512 gMatches[i].__hilabel = gMatches[i].label.replace(
2513 queryRE, '<b>$1</b>');
2514 }
Scott Main7e447ed2013-02-19 17:22:37 -08002515 for (var i=0; i<gGoogleMatches.length; i++) {
2516 gGoogleMatches[i].__hilabel = gGoogleMatches[i].label.replace(
2517 queryRE, '<b>$1</b>');
2518 }
Scott Mainf5089842012-08-14 16:31:07 -07002519}
2520
2521function search_focus_changed(obj, focused)
2522{
Scott Main3b90aff2013-08-01 18:09:35 -07002523 if (!focused) {
Scott Mainf5089842012-08-14 16:31:07 -07002524 if(obj.value == ""){
2525 $(".search .close").addClass("hide");
2526 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002527 $(".suggest-card").hide();
Scott Mainf5089842012-08-14 16:31:07 -07002528 }
2529}
2530
2531function submit_search() {
2532 var query = document.getElementById('search_autocomplete').value;
2533 location.hash = 'q=' + query;
2534 loadSearchResults();
Dirk Doughertyc3921652014-05-13 16:55:26 -07002535 $("#searchResults").slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002536 return false;
2537}
2538
2539
2540function hideResults() {
Dirk Doughertyc3921652014-05-13 16:55:26 -07002541 $("#searchResults").slideUp('fast', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002542 $(".search .close").addClass("hide");
2543 location.hash = '';
Scott Main3b90aff2013-08-01 18:09:35 -07002544
Scott Mainf5089842012-08-14 16:31:07 -07002545 $("#search_autocomplete").val("").blur();
Scott Main3b90aff2013-08-01 18:09:35 -07002546
Scott Mainf5089842012-08-14 16:31:07 -07002547 // reset the ajax search callback to nothing, so results don't appear unless ENTER
2548 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {});
Scott Main0e76e7e2013-03-12 10:24:07 -07002549
2550 // forcefully regain key-up event control (previously jacked by search api)
2551 $("#search_autocomplete").keyup(function(event) {
2552 return search_changed(event, false, toRoot);
2553 });
2554
Scott Mainf5089842012-08-14 16:31:07 -07002555 return false;
2556}
2557
2558
2559
2560/* ########################################################## */
2561/* ################ CUSTOM SEARCH ENGINE ################## */
2562/* ########################################################## */
2563
Scott Mainf5089842012-08-14 16:31:07 -07002564var searchControl;
Scott Main0e76e7e2013-03-12 10:24:07 -07002565google.load('search', '1', {"callback" : function() {
2566 searchControl = new google.search.SearchControl();
2567 } });
Scott Mainf5089842012-08-14 16:31:07 -07002568
2569function loadSearchResults() {
2570 document.getElementById("search_autocomplete").style.color = "#000";
2571
Scott Mainf5089842012-08-14 16:31:07 -07002572 searchControl = new google.search.SearchControl();
2573
2574 // use our existing search form and use tabs when multiple searchers are used
2575 drawOptions = new google.search.DrawOptions();
2576 drawOptions.setDrawMode(google.search.SearchControl.DRAW_MODE_TABBED);
2577 drawOptions.setInput(document.getElementById("search_autocomplete"));
2578
2579 // configure search result options
2580 searchOptions = new google.search.SearcherOptions();
2581 searchOptions.setExpandMode(GSearchControl.EXPAND_MODE_OPEN);
2582
2583 // configure each of the searchers, for each tab
2584 devSiteSearcher = new google.search.WebSearch();
2585 devSiteSearcher.setUserDefinedLabel("All");
2586 devSiteSearcher.setSiteRestriction("001482626316274216503:zu90b7s047u");
2587
2588 designSearcher = new google.search.WebSearch();
2589 designSearcher.setUserDefinedLabel("Design");
2590 designSearcher.setSiteRestriction("http://developer.android.com/design/");
2591
2592 trainingSearcher = new google.search.WebSearch();
2593 trainingSearcher.setUserDefinedLabel("Training");
2594 trainingSearcher.setSiteRestriction("http://developer.android.com/training/");
2595
2596 guidesSearcher = new google.search.WebSearch();
2597 guidesSearcher.setUserDefinedLabel("Guides");
2598 guidesSearcher.setSiteRestriction("http://developer.android.com/guide/");
2599
2600 referenceSearcher = new google.search.WebSearch();
2601 referenceSearcher.setUserDefinedLabel("Reference");
2602 referenceSearcher.setSiteRestriction("http://developer.android.com/reference/");
2603
Scott Maindf08ada2012-12-03 08:54:37 -08002604 googleSearcher = new google.search.WebSearch();
2605 googleSearcher.setUserDefinedLabel("Google Services");
2606 googleSearcher.setSiteRestriction("http://developer.android.com/google/");
2607
Scott Mainf5089842012-08-14 16:31:07 -07002608 blogSearcher = new google.search.WebSearch();
2609 blogSearcher.setUserDefinedLabel("Blog");
2610 blogSearcher.setSiteRestriction("http://android-developers.blogspot.com");
2611
2612 // add each searcher to the search control
2613 searchControl.addSearcher(devSiteSearcher, searchOptions);
2614 searchControl.addSearcher(designSearcher, searchOptions);
2615 searchControl.addSearcher(trainingSearcher, searchOptions);
2616 searchControl.addSearcher(guidesSearcher, searchOptions);
2617 searchControl.addSearcher(referenceSearcher, searchOptions);
Scott Maindf08ada2012-12-03 08:54:37 -08002618 searchControl.addSearcher(googleSearcher, searchOptions);
Scott Mainf5089842012-08-14 16:31:07 -07002619 searchControl.addSearcher(blogSearcher, searchOptions);
2620
2621 // configure result options
2622 searchControl.setResultSetSize(google.search.Search.LARGE_RESULTSET);
2623 searchControl.setLinkTarget(google.search.Search.LINK_TARGET_SELF);
2624 searchControl.setTimeoutInterval(google.search.SearchControl.TIMEOUT_SHORT);
2625 searchControl.setNoResultsString(google.search.SearchControl.NO_RESULTS_DEFAULT_STRING);
2626
2627 // upon ajax search, refresh the url and search title
2628 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {
2629 updateResultTitle(query);
2630 var query = document.getElementById('search_autocomplete').value;
2631 location.hash = 'q=' + query;
2632 });
2633
Scott Mainde295272013-03-25 15:48:35 -07002634 // once search results load, set up click listeners
2635 searchControl.setSearchCompleteCallback(this, function(control, searcher, query) {
2636 addResultClickListeners();
2637 });
2638
Scott Mainf5089842012-08-14 16:31:07 -07002639 // draw the search results box
2640 searchControl.draw(document.getElementById("leftSearchControl"), drawOptions);
2641
2642 // get query and execute the search
2643 searchControl.execute(decodeURI(getQuery(location.hash)));
2644
2645 document.getElementById("search_autocomplete").focus();
2646 addTabListeners();
2647}
2648// End of loadSearchResults
2649
2650
2651google.setOnLoadCallback(function(){
2652 if (location.hash.indexOf("q=") == -1) {
2653 // if there's no query in the url, don't search and make sure results are hidden
2654 $('#searchResults').hide();
2655 return;
2656 } else {
2657 // first time loading search results for this page
Dirk Doughertyc3921652014-05-13 16:55:26 -07002658 $('#searchResults').slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002659 $(".search .close").removeClass("hide");
2660 loadSearchResults();
2661 }
2662}, true);
2663
smain@google.com9a818f52014-10-03 09:25:59 -07002664/* Adjust the scroll position to account for sticky header, only if the hash matches an id.
2665 This does not handle <a name=""> tags. Some CSS fixes those, but only for reference docs. */
Scott Mainb16376f2014-05-21 20:35:47 -07002666function offsetScrollForSticky() {
smain@google.com11fc0092014-10-16 22:10:00 -07002667 // Ignore if there's no search bar (some special pages have no header)
2668 if ($("#search-container").length < 1) return;
2669
smain@google.com3b77ab52014-06-17 11:57:27 -07002670 var hash = escape(location.hash.substr(1));
2671 var $matchingElement = $("#"+hash);
smain@google.com08f336ea2014-10-03 17:40:00 -07002672 // Sanity check that there's an element with that ID on the page
2673 if ($matchingElement.length) {
Scott Mainb16376f2014-05-21 20:35:47 -07002674 // If the position of the target element is near the top of the page (<20px, where we expect it
2675 // to be because we need to move it down 60px to become in view), then move it down 60px
2676 if (Math.abs($matchingElement.offset().top - $(window).scrollTop()) < 20) {
2677 $(window).scrollTop($(window).scrollTop() - 60);
Scott Mainb16376f2014-05-21 20:35:47 -07002678 }
2679 }
2680}
2681
Scott Mainf5089842012-08-14 16:31:07 -07002682// when an event on the browser history occurs (back, forward, load) requery hash and do search
2683$(window).hashchange( function(){
smain@google.com2f077192014-10-09 18:04:10 -07002684 // Ignore if there's no search bar (some special pages have no header)
2685 if ($("#search-container").length < 1) return;
2686
Dirk Doughertyc3921652014-05-13 16:55:26 -07002687 // If the hash isn't a search query or there's an error in the query,
2688 // then adjust the scroll position to account for sticky header, then exit.
Scott Mainf5089842012-08-14 16:31:07 -07002689 if ((location.hash.indexOf("q=") == -1) || (query == "undefined")) {
2690 // If the results pane is open, close it.
2691 if (!$("#searchResults").is(":hidden")) {
2692 hideResults();
2693 }
Scott Mainb16376f2014-05-21 20:35:47 -07002694 offsetScrollForSticky();
Scott Mainf5089842012-08-14 16:31:07 -07002695 return;
2696 }
2697
2698 // Otherwise, we have a search to do
2699 var query = decodeURI(getQuery(location.hash));
2700 searchControl.execute(query);
Dirk Doughertyc3921652014-05-13 16:55:26 -07002701 $('#searchResults').slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002702 $("#search_autocomplete").focus();
2703 $(".search .close").removeClass("hide");
2704
2705 updateResultTitle(query);
2706});
2707
2708function updateResultTitle(query) {
2709 $("#searchTitle").html("Results for <em>" + escapeHTML(query) + "</em>");
2710}
2711
2712// forcefully regain key-up event control (previously jacked by search api)
2713$("#search_autocomplete").keyup(function(event) {
2714 return search_changed(event, false, toRoot);
2715});
2716
2717// add event listeners to each tab so we can track the browser history
2718function addTabListeners() {
2719 var tabHeaders = $(".gsc-tabHeader");
2720 for (var i = 0; i < tabHeaders.length; i++) {
2721 $(tabHeaders[i]).attr("id",i).click(function() {
2722 /*
2723 // make a copy of the page numbers for the search left pane
2724 setTimeout(function() {
2725 // remove any residual page numbers
2726 $('#searchResults .gsc-tabsArea .gsc-cursor-box.gs-bidi-start-align').remove();
Scott Main3b90aff2013-08-01 18:09:35 -07002727 // move the page numbers to the left position; make a clone,
Scott Mainf5089842012-08-14 16:31:07 -07002728 // because the element is drawn to the DOM only once
Scott Main3b90aff2013-08-01 18:09:35 -07002729 // and because we're going to remove it (previous line),
2730 // we need it to be available to move again as the user navigates
Scott Mainf5089842012-08-14 16:31:07 -07002731 $('#searchResults .gsc-webResult .gsc-cursor-box.gs-bidi-start-align:visible')
2732 .clone().appendTo('#searchResults .gsc-tabsArea');
2733 }, 200);
2734 */
2735 });
2736 }
2737 setTimeout(function(){$(tabHeaders[0]).click()},200);
2738}
2739
Scott Mainde295272013-03-25 15:48:35 -07002740// add analytics tracking events to each result link
2741function addResultClickListeners() {
2742 $("#searchResults a.gs-title").each(function(index, link) {
2743 // When user clicks enter for Google search results, track it
2744 $(link).click(function() {
smain@google.comed677d72014-12-12 10:19:17 -08002745 ga('send', 'event', 'Google Click', 'clicked: ' + $(this).attr('href'),
2746 'query: ' + $("#search_autocomplete").val().toLowerCase());
Scott Mainde295272013-03-25 15:48:35 -07002747 });
2748 });
2749}
2750
Scott Mainf5089842012-08-14 16:31:07 -07002751
2752function getQuery(hash) {
2753 var queryParts = hash.split('=');
2754 return queryParts[1];
2755}
2756
2757/* returns the given string with all HTML brackets converted to entities
2758 TODO: move this to the site's JS library */
2759function escapeHTML(string) {
2760 return string.replace(/</g,"&lt;")
2761 .replace(/>/g,"&gt;");
2762}
2763
2764
2765
2766
2767
2768
2769
2770/* ######################################################## */
2771/* ################# JAVADOC REFERENCE ################### */
2772/* ######################################################## */
2773
Scott Main65511c02012-09-07 15:51:32 -07002774/* Initialize some droiddoc stuff, but only if we're in the reference */
Scott Main52dd2062013-08-15 12:22:28 -07002775if (location.pathname.indexOf("/reference") == 0) {
2776 if(!(location.pathname.indexOf("/reference-gms/packages.html") == 0)
2777 && !(location.pathname.indexOf("/reference-gcm/packages.html") == 0)
2778 && !(location.pathname.indexOf("/reference/com/google") == 0)) {
Robert Ly67d75f12012-12-03 12:53:42 -08002779 $(document).ready(function() {
2780 // init available apis based on user pref
2781 changeApiLevel();
2782 initSidenavHeightResize()
2783 });
2784 }
Scott Main65511c02012-09-07 15:51:32 -07002785}
Scott Mainf5089842012-08-14 16:31:07 -07002786
2787var API_LEVEL_COOKIE = "api_level";
2788var minLevel = 1;
2789var maxLevel = 1;
2790
2791/******* SIDENAV DIMENSIONS ************/
Scott Main3b90aff2013-08-01 18:09:35 -07002792
Scott Mainf5089842012-08-14 16:31:07 -07002793 function initSidenavHeightResize() {
2794 // Change the drag bar size to nicely fit the scrollbar positions
2795 var $dragBar = $(".ui-resizable-s");
2796 $dragBar.css({'width': $dragBar.parent().width() - 5 + "px"});
Scott Main3b90aff2013-08-01 18:09:35 -07002797
2798 $( "#resize-packages-nav" ).resizable({
Scott Mainf5089842012-08-14 16:31:07 -07002799 containment: "#nav-panels",
2800 handles: "s",
2801 alsoResize: "#packages-nav",
2802 resize: function(event, ui) { resizeNav(); }, /* resize the nav while dragging */
2803 stop: function(event, ui) { saveNavPanels(); } /* once stopped, save the sizes to cookie */
2804 });
Scott Main3b90aff2013-08-01 18:09:35 -07002805
Scott Mainf5089842012-08-14 16:31:07 -07002806 }
Scott Main3b90aff2013-08-01 18:09:35 -07002807
Scott Mainf5089842012-08-14 16:31:07 -07002808function updateSidenavFixedWidth() {
Scott Mainf5257812014-05-22 17:26:38 -07002809 if (!sticky) return;
Scott Mainf5089842012-08-14 16:31:07 -07002810 $('#devdoc-nav').css({
2811 'width' : $('#side-nav').css('width'),
2812 'margin' : $('#side-nav').css('margin')
2813 });
2814 $('#devdoc-nav a.totop').css({'display':'block','width':$("#nav").innerWidth()+'px'});
Scott Main3b90aff2013-08-01 18:09:35 -07002815
Scott Mainf5089842012-08-14 16:31:07 -07002816 initSidenavHeightResize();
2817}
2818
2819function updateSidenavFullscreenWidth() {
Scott Mainf5257812014-05-22 17:26:38 -07002820 if (!sticky) return;
Scott Mainf5089842012-08-14 16:31:07 -07002821 $('#devdoc-nav').css({
2822 'width' : $('#side-nav').css('width'),
2823 'margin' : $('#side-nav').css('margin')
2824 });
2825 $('#devdoc-nav .totop').css({'left': 'inherit'});
Scott Main3b90aff2013-08-01 18:09:35 -07002826
Scott Mainf5089842012-08-14 16:31:07 -07002827 initSidenavHeightResize();
2828}
2829
2830function buildApiLevelSelector() {
2831 maxLevel = SINCE_DATA.length;
2832 var userApiLevel = parseInt(readCookie(API_LEVEL_COOKIE));
2833 userApiLevel = userApiLevel == 0 ? maxLevel : userApiLevel; // If there's no cookie (zero), use the max by default
2834
2835 minLevel = parseInt($("#doc-api-level").attr("class"));
2836 // Handle provisional api levels; the provisional level will always be the highest possible level
2837 // Provisional api levels will also have a length; other stuff that's just missing a level won't,
2838 // so leave those kinds of entities at the default level of 1 (for example, the R.styleable class)
2839 if (isNaN(minLevel) && minLevel.length) {
2840 minLevel = maxLevel;
2841 }
2842 var select = $("#apiLevelSelector").html("").change(changeApiLevel);
2843 for (var i = maxLevel-1; i >= 0; i--) {
2844 var option = $("<option />").attr("value",""+SINCE_DATA[i]).append(""+SINCE_DATA[i]);
2845 // if (SINCE_DATA[i] < minLevel) option.addClass("absent"); // always false for strings (codenames)
2846 select.append(option);
2847 }
2848
2849 // get the DOM element and use setAttribute cuz IE6 fails when using jquery .attr('selected',true)
2850 var selectedLevelItem = $("#apiLevelSelector option[value='"+userApiLevel+"']").get(0);
2851 selectedLevelItem.setAttribute('selected',true);
2852}
2853
2854function changeApiLevel() {
2855 maxLevel = SINCE_DATA.length;
2856 var selectedLevel = maxLevel;
2857
2858 selectedLevel = parseInt($("#apiLevelSelector option:selected").val());
2859 toggleVisisbleApis(selectedLevel, "body");
2860
smain@google.com6bdcb982014-11-14 11:53:07 -08002861 writeCookie(API_LEVEL_COOKIE, selectedLevel, null);
Scott Mainf5089842012-08-14 16:31:07 -07002862
2863 if (selectedLevel < minLevel) {
2864 var thing = ($("#jd-header").html().indexOf("package") != -1) ? "package" : "class";
Scott Main8f24ca82012-11-16 10:34:22 -08002865 $("#naMessage").show().html("<div><p><strong>This " + thing
2866 + " requires API level " + minLevel + " or higher.</strong></p>"
2867 + "<p>This document is hidden because your selected API level for the documentation is "
2868 + selectedLevel + ". You can change the documentation API level with the selector "
2869 + "above the left navigation.</p>"
2870 + "<p>For more information about specifying the API level your app requires, "
2871 + "read <a href='" + toRoot + "training/basics/supporting-devices/platforms.html'"
2872 + ">Supporting Different Platform Versions</a>.</p>"
2873 + "<input type='button' value='OK, make this page visible' "
2874 + "title='Change the API level to " + minLevel + "' "
2875 + "onclick='$(\"#apiLevelSelector\").val(\"" + minLevel + "\");changeApiLevel();' />"
2876 + "</div>");
Scott Mainf5089842012-08-14 16:31:07 -07002877 } else {
2878 $("#naMessage").hide();
2879 }
2880}
2881
2882function toggleVisisbleApis(selectedLevel, context) {
2883 var apis = $(".api",context);
2884 apis.each(function(i) {
2885 var obj = $(this);
2886 var className = obj.attr("class");
2887 var apiLevelIndex = className.lastIndexOf("-")+1;
2888 var apiLevelEndIndex = className.indexOf(" ", apiLevelIndex);
2889 apiLevelEndIndex = apiLevelEndIndex != -1 ? apiLevelEndIndex : className.length;
2890 var apiLevel = className.substring(apiLevelIndex, apiLevelEndIndex);
2891 if (apiLevel.length == 0) { // for odd cases when the since data is actually missing, just bail
2892 return;
2893 }
2894 apiLevel = parseInt(apiLevel);
2895
2896 // Handle provisional api levels; if this item's level is the provisional one, set it to the max
2897 var selectedLevelNum = parseInt(selectedLevel)
2898 var apiLevelNum = parseInt(apiLevel);
2899 if (isNaN(apiLevelNum)) {
2900 apiLevelNum = maxLevel;
2901 }
2902
2903 // Grey things out that aren't available and give a tooltip title
2904 if (apiLevelNum > selectedLevelNum) {
2905 obj.addClass("absent").attr("title","Requires API Level \""
Scott Main641c2c22013-10-31 14:48:45 -07002906 + apiLevel + "\" or higher. To reveal, change the target API level "
2907 + "above the left navigation.");
Scott Main3b90aff2013-08-01 18:09:35 -07002908 }
Scott Mainf5089842012-08-14 16:31:07 -07002909 else obj.removeClass("absent").removeAttr("title");
2910 });
2911}
2912
2913
2914
2915
2916/* ################# SIDENAV TREE VIEW ################### */
2917
2918function new_node(me, mom, text, link, children_data, api_level)
2919{
2920 var node = new Object();
2921 node.children = Array();
2922 node.children_data = children_data;
2923 node.depth = mom.depth + 1;
2924
2925 node.li = document.createElement("li");
2926 mom.get_children_ul().appendChild(node.li);
2927
2928 node.label_div = document.createElement("div");
2929 node.label_div.className = "label";
2930 if (api_level != null) {
2931 $(node.label_div).addClass("api");
2932 $(node.label_div).addClass("api-level-"+api_level);
2933 }
2934 node.li.appendChild(node.label_div);
2935
2936 if (children_data != null) {
2937 node.expand_toggle = document.createElement("a");
2938 node.expand_toggle.href = "javascript:void(0)";
2939 node.expand_toggle.onclick = function() {
2940 if (node.expanded) {
2941 $(node.get_children_ul()).slideUp("fast");
2942 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2943 node.expanded = false;
2944 } else {
2945 expand_node(me, node);
2946 }
2947 };
2948 node.label_div.appendChild(node.expand_toggle);
2949
2950 node.plus_img = document.createElement("img");
2951 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2952 node.plus_img.className = "plus";
2953 node.plus_img.width = "8";
2954 node.plus_img.border = "0";
2955 node.expand_toggle.appendChild(node.plus_img);
2956
2957 node.expanded = false;
2958 }
2959
2960 var a = document.createElement("a");
2961 node.label_div.appendChild(a);
2962 node.label = document.createTextNode(text);
2963 a.appendChild(node.label);
2964 if (link) {
2965 a.href = me.toroot + link;
2966 } else {
2967 if (children_data != null) {
2968 a.className = "nolink";
2969 a.href = "javascript:void(0)";
2970 a.onclick = node.expand_toggle.onclick;
2971 // This next line shouldn't be necessary. I'll buy a beer for the first
2972 // person who figures out how to remove this line and have the link
2973 // toggle shut on the first try. --joeo@android.com
2974 node.expanded = false;
2975 }
2976 }
Scott Main3b90aff2013-08-01 18:09:35 -07002977
Scott Mainf5089842012-08-14 16:31:07 -07002978
2979 node.children_ul = null;
2980 node.get_children_ul = function() {
2981 if (!node.children_ul) {
2982 node.children_ul = document.createElement("ul");
2983 node.children_ul.className = "children_ul";
2984 node.children_ul.style.display = "none";
2985 node.li.appendChild(node.children_ul);
2986 }
2987 return node.children_ul;
2988 };
2989
2990 return node;
2991}
2992
Robert Lyd2dd6e52012-11-29 21:28:48 -08002993
2994
2995
Scott Mainf5089842012-08-14 16:31:07 -07002996function expand_node(me, node)
2997{
2998 if (node.children_data && !node.expanded) {
2999 if (node.children_visited) {
3000 $(node.get_children_ul()).slideDown("fast");
3001 } else {
3002 get_node(me, node);
3003 if ($(node.label_div).hasClass("absent")) {
3004 $(node.get_children_ul()).addClass("absent");
Scott Main3b90aff2013-08-01 18:09:35 -07003005 }
Scott Mainf5089842012-08-14 16:31:07 -07003006 $(node.get_children_ul()).slideDown("fast");
3007 }
3008 node.plus_img.src = me.toroot + "assets/images/triangle-opened-small.png";
3009 node.expanded = true;
3010
3011 // perform api level toggling because new nodes are new to the DOM
3012 var selectedLevel = $("#apiLevelSelector option:selected").val();
3013 toggleVisisbleApis(selectedLevel, "#side-nav");
3014 }
3015}
3016
3017function get_node(me, mom)
3018{
3019 mom.children_visited = true;
3020 for (var i in mom.children_data) {
3021 var node_data = mom.children_data[i];
3022 mom.children[i] = new_node(me, mom, node_data[0], node_data[1],
3023 node_data[2], node_data[3]);
3024 }
3025}
3026
3027function this_page_relative(toroot)
3028{
3029 var full = document.location.pathname;
3030 var file = "";
3031 if (toroot.substr(0, 1) == "/") {
3032 if (full.substr(0, toroot.length) == toroot) {
3033 return full.substr(toroot.length);
3034 } else {
3035 // the file isn't under toroot. Fail.
3036 return null;
3037 }
3038 } else {
3039 if (toroot != "./") {
3040 toroot = "./" + toroot;
3041 }
3042 do {
3043 if (toroot.substr(toroot.length-3, 3) == "../" || toroot == "./") {
3044 var pos = full.lastIndexOf("/");
3045 file = full.substr(pos) + file;
3046 full = full.substr(0, pos);
3047 toroot = toroot.substr(0, toroot.length-3);
3048 }
3049 } while (toroot != "" && toroot != "/");
3050 return file.substr(1);
3051 }
3052}
3053
3054function find_page(url, data)
3055{
3056 var nodes = data;
3057 var result = null;
3058 for (var i in nodes) {
3059 var d = nodes[i];
3060 if (d[1] == url) {
3061 return new Array(i);
3062 }
3063 else if (d[2] != null) {
3064 result = find_page(url, d[2]);
3065 if (result != null) {
3066 return (new Array(i).concat(result));
3067 }
3068 }
3069 }
3070 return null;
3071}
3072
Scott Mainf5089842012-08-14 16:31:07 -07003073function init_default_navtree(toroot) {
Scott Main25e73002013-03-27 15:24:06 -07003074 // load json file for navtree data
3075 $.getScript(toRoot + 'navtree_data.js', function(data, textStatus, jqxhr) {
3076 // when the file is loaded, initialize the tree
3077 if(jqxhr.status === 200) {
3078 init_navtree("tree-list", toroot, NAVTREE_DATA);
3079 }
3080 });
Scott Main3b90aff2013-08-01 18:09:35 -07003081
Scott Mainf5089842012-08-14 16:31:07 -07003082 // perform api level toggling because because the whole tree is new to the DOM
3083 var selectedLevel = $("#apiLevelSelector option:selected").val();
3084 toggleVisisbleApis(selectedLevel, "#side-nav");
3085}
3086
3087function init_navtree(navtree_id, toroot, root_nodes)
3088{
3089 var me = new Object();
3090 me.toroot = toroot;
3091 me.node = new Object();
3092
3093 me.node.li = document.getElementById(navtree_id);
3094 me.node.children_data = root_nodes;
3095 me.node.children = new Array();
3096 me.node.children_ul = document.createElement("ul");
3097 me.node.get_children_ul = function() { return me.node.children_ul; };
3098 //me.node.children_ul.className = "children_ul";
3099 me.node.li.appendChild(me.node.children_ul);
3100 me.node.depth = 0;
3101
3102 get_node(me, me.node);
3103
3104 me.this_page = this_page_relative(toroot);
3105 me.breadcrumbs = find_page(me.this_page, root_nodes);
3106 if (me.breadcrumbs != null && me.breadcrumbs.length != 0) {
3107 var mom = me.node;
3108 for (var i in me.breadcrumbs) {
3109 var j = me.breadcrumbs[i];
3110 mom = mom.children[j];
3111 expand_node(me, mom);
3112 }
3113 mom.label_div.className = mom.label_div.className + " selected";
3114 addLoadEvent(function() {
3115 scrollIntoView("nav-tree");
3116 });
3117 }
3118}
3119
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003120
3121
3122
3123
3124
3125
3126
Robert Lyd2dd6e52012-11-29 21:28:48 -08003127/* TODO: eliminate redundancy with non-google functions */
3128function init_google_navtree(navtree_id, toroot, root_nodes)
3129{
3130 var me = new Object();
3131 me.toroot = toroot;
3132 me.node = new Object();
3133
3134 me.node.li = document.getElementById(navtree_id);
3135 me.node.children_data = root_nodes;
3136 me.node.children = new Array();
3137 me.node.children_ul = document.createElement("ul");
3138 me.node.get_children_ul = function() { return me.node.children_ul; };
3139 //me.node.children_ul.className = "children_ul";
3140 me.node.li.appendChild(me.node.children_ul);
3141 me.node.depth = 0;
3142
3143 get_google_node(me, me.node);
Robert Lyd2dd6e52012-11-29 21:28:48 -08003144}
3145
3146function new_google_node(me, mom, text, link, children_data, api_level)
3147{
3148 var node = new Object();
3149 var child;
3150 node.children = Array();
3151 node.children_data = children_data;
3152 node.depth = mom.depth + 1;
3153 node.get_children_ul = function() {
3154 if (!node.children_ul) {
Scott Main3b90aff2013-08-01 18:09:35 -07003155 node.children_ul = document.createElement("ul");
3156 node.children_ul.className = "tree-list-children";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003157 node.li.appendChild(node.children_ul);
3158 }
3159 return node.children_ul;
3160 };
3161 node.li = document.createElement("li");
3162
3163 mom.get_children_ul().appendChild(node.li);
Scott Main3b90aff2013-08-01 18:09:35 -07003164
3165
Robert Lyd2dd6e52012-11-29 21:28:48 -08003166 if(link) {
3167 child = document.createElement("a");
3168
3169 }
3170 else {
3171 child = document.createElement("span");
Scott Mainac71b2b2012-11-30 14:40:58 -08003172 child.className = "tree-list-subtitle";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003173
3174 }
3175 if (children_data != null) {
3176 node.li.className="nav-section";
3177 node.label_div = document.createElement("div");
Scott Main3b90aff2013-08-01 18:09:35 -07003178 node.label_div.className = "nav-section-header-ref";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003179 node.li.appendChild(node.label_div);
3180 get_google_node(me, node);
3181 node.label_div.appendChild(child);
3182 }
3183 else {
3184 node.li.appendChild(child);
3185 }
3186 if(link) {
3187 child.href = me.toroot + link;
3188 }
3189 node.label = document.createTextNode(text);
3190 child.appendChild(node.label);
3191
3192 node.children_ul = null;
3193
3194 return node;
3195}
3196
3197function get_google_node(me, mom)
3198{
3199 mom.children_visited = true;
3200 var linkText;
3201 for (var i in mom.children_data) {
3202 var node_data = mom.children_data[i];
3203 linkText = node_data[0];
3204
3205 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3206 linkText = linkText.substr(19, linkText.length);
3207 }
3208 mom.children[i] = new_google_node(me, mom, linkText, node_data[1],
3209 node_data[2], node_data[3]);
3210 }
3211}
Scott Mainad08f072013-08-20 16:49:57 -07003212
3213
3214
3215
3216
3217
3218/****** NEW version of script to build google and sample navs dynamically ******/
3219// TODO: update Google reference docs to tolerate this new implementation
3220
Scott Maine624b3f2013-09-12 12:56:41 -07003221var NODE_NAME = 0;
3222var NODE_HREF = 1;
3223var NODE_GROUP = 2;
3224var NODE_TAGS = 3;
3225var NODE_CHILDREN = 4;
3226
Scott Mainad08f072013-08-20 16:49:57 -07003227function init_google_navtree2(navtree_id, data)
3228{
3229 var $containerUl = $("#"+navtree_id);
Scott Mainad08f072013-08-20 16:49:57 -07003230 for (var i in data) {
3231 var node_data = data[i];
3232 $containerUl.append(new_google_node2(node_data));
3233 }
3234
Scott Main70557ee2013-10-30 14:47:40 -07003235 // Make all third-generation list items 'sticky' to prevent them from collapsing
3236 $containerUl.find('li li li.nav-section').addClass('sticky');
3237
Scott Mainad08f072013-08-20 16:49:57 -07003238 initExpandableNavItems("#"+navtree_id);
3239}
3240
3241function new_google_node2(node_data)
3242{
Scott Maine624b3f2013-09-12 12:56:41 -07003243 var linkText = node_data[NODE_NAME];
Scott Mainad08f072013-08-20 16:49:57 -07003244 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3245 linkText = linkText.substr(19, linkText.length);
3246 }
3247 var $li = $('<li>');
3248 var $a;
Scott Maine624b3f2013-09-12 12:56:41 -07003249 if (node_data[NODE_HREF] != null) {
Scott Main70557ee2013-10-30 14:47:40 -07003250 $a = $('<a href="' + toRoot + node_data[NODE_HREF] + '" title="' + linkText + '" >'
3251 + linkText + '</a>');
Scott Mainad08f072013-08-20 16:49:57 -07003252 } else {
Scott Main70557ee2013-10-30 14:47:40 -07003253 $a = $('<a href="#" onclick="return false;" title="' + linkText + '" >'
3254 + linkText + '/</a>');
Scott Mainad08f072013-08-20 16:49:57 -07003255 }
3256 var $childUl = $('<ul>');
Scott Maine624b3f2013-09-12 12:56:41 -07003257 if (node_data[NODE_CHILDREN] != null) {
Scott Mainad08f072013-08-20 16:49:57 -07003258 $li.addClass("nav-section");
3259 $a = $('<div class="nav-section-header">').append($a);
Scott Maine624b3f2013-09-12 12:56:41 -07003260 if (node_data[NODE_HREF] == null) $a.addClass('empty');
Scott Mainad08f072013-08-20 16:49:57 -07003261
Scott Maine624b3f2013-09-12 12:56:41 -07003262 for (var i in node_data[NODE_CHILDREN]) {
3263 var child_node_data = node_data[NODE_CHILDREN][i];
Scott Mainad08f072013-08-20 16:49:57 -07003264 $childUl.append(new_google_node2(child_node_data));
3265 }
3266 $li.append($childUl);
3267 }
3268 $li.prepend($a);
3269
3270 return $li;
3271}
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
Robert Lyd2dd6e52012-11-29 21:28:48 -08003283function showGoogleRefTree() {
3284 init_default_google_navtree(toRoot);
3285 init_default_gcm_navtree(toRoot);
Robert Lyd2dd6e52012-11-29 21:28:48 -08003286}
3287
3288function init_default_google_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07003289 // load json file for navtree data
3290 $.getScript(toRoot + 'gms_navtree_data.js', function(data, textStatus, jqxhr) {
3291 // when the file is loaded, initialize the tree
3292 if(jqxhr.status === 200) {
3293 init_google_navtree("gms-tree-list", toroot, GMS_NAVTREE_DATA);
3294 highlightSidenav();
3295 resizeNav();
3296 }
3297 });
Robert Lyd2dd6e52012-11-29 21:28:48 -08003298}
3299
3300function init_default_gcm_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07003301 // load json file for navtree data
3302 $.getScript(toRoot + 'gcm_navtree_data.js', function(data, textStatus, jqxhr) {
3303 // when the file is loaded, initialize the tree
3304 if(jqxhr.status === 200) {
3305 init_google_navtree("gcm-tree-list", toroot, GCM_NAVTREE_DATA);
3306 highlightSidenav();
3307 resizeNav();
3308 }
3309 });
Robert Lyd2dd6e52012-11-29 21:28:48 -08003310}
3311
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003312function showSamplesRefTree() {
3313 init_default_samples_navtree(toRoot);
3314}
3315
3316function init_default_samples_navtree(toroot) {
3317 // load json file for navtree data
3318 $.getScript(toRoot + 'samples_navtree_data.js', function(data, textStatus, jqxhr) {
3319 // when the file is loaded, initialize the tree
3320 if(jqxhr.status === 200) {
Scott Mainf1435b72013-10-30 16:27:38 -07003321 // hack to remove the "about the samples" link then put it back in
3322 // after we nuke the list to remove the dummy static list of samples
3323 var $firstLi = $("#nav.samples-nav > li:first-child").clone();
3324 $("#nav.samples-nav").empty();
3325 $("#nav.samples-nav").append($firstLi);
3326
Scott Mainad08f072013-08-20 16:49:57 -07003327 init_google_navtree2("nav.samples-nav", SAMPLES_NAVTREE_DATA);
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003328 highlightSidenav();
3329 resizeNav();
Scott Main03aca9a2013-10-31 07:20:55 -07003330 if ($("#jd-content #samples").length) {
3331 showSamples();
3332 }
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003333 }
3334 });
3335}
3336
Scott Mainf5089842012-08-14 16:31:07 -07003337/* TOGGLE INHERITED MEMBERS */
3338
3339/* Toggle an inherited class (arrow toggle)
3340 * @param linkObj The link that was clicked.
3341 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3342 * 'null' to simply toggle.
3343 */
3344function toggleInherited(linkObj, expand) {
3345 var base = linkObj.getAttribute("id");
3346 var list = document.getElementById(base + "-list");
3347 var summary = document.getElementById(base + "-summary");
3348 var trigger = document.getElementById(base + "-trigger");
3349 var a = $(linkObj);
3350 if ( (expand == null && a.hasClass("closed")) || expand ) {
3351 list.style.display = "none";
3352 summary.style.display = "block";
3353 trigger.src = toRoot + "assets/images/triangle-opened.png";
3354 a.removeClass("closed");
3355 a.addClass("opened");
3356 } else if ( (expand == null && a.hasClass("opened")) || (expand == false) ) {
3357 list.style.display = "block";
3358 summary.style.display = "none";
3359 trigger.src = toRoot + "assets/images/triangle-closed.png";
3360 a.removeClass("opened");
3361 a.addClass("closed");
3362 }
3363 return false;
3364}
3365
3366/* Toggle all inherited classes in a single table (e.g. all inherited methods)
3367 * @param linkObj The link that was clicked.
3368 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3369 * 'null' to simply toggle.
3370 */
3371function toggleAllInherited(linkObj, expand) {
3372 var a = $(linkObj);
3373 var table = $(a.parent().parent().parent()); // ugly way to get table/tbody
3374 var expandos = $(".jd-expando-trigger", table);
3375 if ( (expand == null && a.text() == "[Expand]") || expand ) {
3376 expandos.each(function(i) {
3377 toggleInherited(this, true);
3378 });
3379 a.text("[Collapse]");
3380 } else if ( (expand == null && a.text() == "[Collapse]") || (expand == false) ) {
3381 expandos.each(function(i) {
3382 toggleInherited(this, false);
3383 });
3384 a.text("[Expand]");
3385 }
3386 return false;
3387}
3388
3389/* Toggle all inherited members in the class (link in the class title)
3390 */
3391function toggleAllClassInherited() {
3392 var a = $("#toggleAllClassInherited"); // get toggle link from class title
3393 var toggles = $(".toggle-all", $("#body-content"));
3394 if (a.text() == "[Expand All]") {
3395 toggles.each(function(i) {
3396 toggleAllInherited(this, true);
3397 });
3398 a.text("[Collapse All]");
3399 } else {
3400 toggles.each(function(i) {
3401 toggleAllInherited(this, false);
3402 });
3403 a.text("[Expand All]");
3404 }
3405 return false;
3406}
3407
3408/* Expand all inherited members in the class. Used when initiating page search */
3409function ensureAllInheritedExpanded() {
3410 var toggles = $(".toggle-all", $("#body-content"));
3411 toggles.each(function(i) {
3412 toggleAllInherited(this, true);
3413 });
3414 $("#toggleAllClassInherited").text("[Collapse All]");
3415}
3416
3417
3418/* HANDLE KEY EVENTS
3419 * - Listen for Ctrl+F (Cmd on Mac) and expand all inherited members (to aid page search)
3420 */
3421var agent = navigator['userAgent'].toLowerCase();
3422var mac = agent.indexOf("macintosh") != -1;
3423
3424$(document).keydown( function(e) {
3425var control = mac ? e.metaKey && !e.ctrlKey : e.ctrlKey; // get ctrl key
3426 if (control && e.which == 70) { // 70 is "F"
3427 ensureAllInheritedExpanded();
3428 }
3429});
Scott Main498d7102013-08-21 15:47:38 -07003430
3431
3432
3433
3434
3435
3436/* On-demand functions */
3437
3438/** Move sample code line numbers out of PRE block and into non-copyable column */
3439function initCodeLineNumbers() {
3440 var numbers = $("#codesample-block a.number");
3441 if (numbers.length) {
3442 $("#codesample-line-numbers").removeClass("hidden").append(numbers);
3443 }
3444
3445 $(document).ready(function() {
3446 // select entire line when clicked
3447 $("span.code-line").click(function() {
3448 if (!shifted) {
3449 selectText(this);
3450 }
3451 });
3452 // invoke line link on double click
3453 $(".code-line").dblclick(function() {
3454 document.location.hash = $(this).attr('id');
3455 });
3456 // highlight the line when hovering on the number
3457 $("#codesample-line-numbers a.number").mouseover(function() {
3458 var id = $(this).attr('href');
3459 $(id).css('background','#e7e7e7');
3460 });
3461 $("#codesample-line-numbers a.number").mouseout(function() {
3462 var id = $(this).attr('href');
3463 $(id).css('background','none');
3464 });
3465 });
3466}
3467
3468// create SHIFT key binder to avoid the selectText method when selecting multiple lines
3469var shifted = false;
3470$(document).bind('keyup keydown', function(e){shifted = e.shiftKey; return true;} );
3471
3472// courtesy of jasonedelman.com
3473function selectText(element) {
3474 var doc = document
3475 , range, selection
3476 ;
3477 if (doc.body.createTextRange) { //ms
3478 range = doc.body.createTextRange();
3479 range.moveToElementText(element);
3480 range.select();
3481 } else if (window.getSelection) { //all others
Scott Main70557ee2013-10-30 14:47:40 -07003482 selection = window.getSelection();
Scott Main498d7102013-08-21 15:47:38 -07003483 range = doc.createRange();
3484 range.selectNodeContents(element);
3485 selection.removeAllRanges();
3486 selection.addRange(range);
3487 }
Scott Main285f0772013-08-22 23:22:09 +00003488}
Scott Main03aca9a2013-10-31 07:20:55 -07003489
3490
3491
3492
3493/** Display links and other information about samples that match the
3494 group specified by the URL */
3495function showSamples() {
3496 var group = $("#samples").attr('class');
3497 $("#samples").html("<p>Here are some samples for <b>" + group + "</b> apps:</p>");
3498
3499 var $ul = $("<ul>");
3500 $selectedLi = $("#nav li.selected");
3501
3502 $selectedLi.children("ul").children("li").each(function() {
3503 var $li = $("<li>").append($(this).find("a").first().clone());
3504 $ul.append($li);
3505 });
3506
3507 $("#samples").append($ul);
3508
3509}
Dirk Doughertyc3921652014-05-13 16:55:26 -07003510
3511
3512
3513/* ########################################################## */
3514/* ################### RESOURCE CARDS ##################### */
3515/* ########################################################## */
3516
3517/** Handle resource queries, collections, and grids (sections). Requires
3518 jd_tag_helpers.js and the *_unified_data.js to be loaded. */
3519
3520(function() {
3521 // Prevent the same resource from being loaded more than once per page.
3522 var addedPageResources = {};
3523
3524 $(document).ready(function() {
3525 $('.resource-widget').each(function() {
3526 initResourceWidget(this);
3527 });
3528
3529 /* Pass the line height to ellipsisfade() to adjust the height of the
3530 text container to show the max number of lines possible, without
3531 showing lines that are cut off. This works with the css ellipsis
3532 classes to fade last text line and apply an ellipsis char. */
3533
Scott Mainb16376f2014-05-21 20:35:47 -07003534 //card text currently uses 15px line height.
Dirk Doughertyc3921652014-05-13 16:55:26 -07003535 var lineHeight = 15;
3536 $('.card-info .text').ellipsisfade(lineHeight);
3537 });
3538
3539 /*
3540 Three types of resource layouts:
3541 Flow - Uses a fixed row-height flow using float left style.
3542 Carousel - Single card slideshow all same dimension absolute.
3543 Stack - Uses fixed columns and flexible element height.
3544 */
3545 function initResourceWidget(widget) {
3546 var $widget = $(widget);
3547 var isFlow = $widget.hasClass('resource-flow-layout'),
3548 isCarousel = $widget.hasClass('resource-carousel-layout'),
3549 isStack = $widget.hasClass('resource-stack-layout');
3550
3551 // find size of widget by pulling out its class name
3552 var sizeCols = 1;
3553 var m = $widget.get(0).className.match(/\bcol-(\d+)\b/);
3554 if (m) {
3555 sizeCols = parseInt(m[1], 10);
3556 }
3557
3558 var opts = {
3559 cardSizes: ($widget.data('cardsizes') || '').split(','),
3560 maxResults: parseInt($widget.data('maxresults') || '100', 10),
3561 itemsPerPage: $widget.data('itemsperpage'),
3562 sortOrder: $widget.data('sortorder'),
3563 query: $widget.data('query'),
3564 section: $widget.data('section'),
Robert Lye7eeb402014-06-03 19:35:24 -07003565 sizeCols: sizeCols,
3566 /* Added by LFL 6/6/14 */
3567 resourceStyle: $widget.data('resourcestyle') || 'card',
3568 stackSort: $widget.data('stacksort') || 'true'
Dirk Doughertyc3921652014-05-13 16:55:26 -07003569 };
3570
3571 // run the search for the set of resources to show
3572
3573 var resources = buildResourceList(opts);
3574
3575 if (isFlow) {
3576 drawResourcesFlowWidget($widget, opts, resources);
3577 } else if (isCarousel) {
3578 drawResourcesCarouselWidget($widget, opts, resources);
3579 } else if (isStack) {
smain@google.com95948b82014-06-16 19:24:25 -07003580 /* Looks like this got removed and is not used, so repurposing for the
3581 homepage style layout.
Robert Lye7eeb402014-06-03 19:35:24 -07003582 Modified by LFL 6/6/14
3583 */
3584 //var sections = buildSectionList(opts);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003585 opts['numStacks'] = $widget.data('numstacks');
Robert Lye7eeb402014-06-03 19:35:24 -07003586 drawResourcesStackWidget($widget, opts, resources/*, sections*/);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003587 }
3588 }
3589
3590 /* Initializes a Resource Carousel Widget */
3591 function drawResourcesCarouselWidget($widget, opts, resources) {
3592 $widget.empty();
3593 var plusone = true; //always show plusone on carousel
3594
3595 $widget.addClass('resource-card slideshow-container')
3596 .append($('<a>').addClass('slideshow-prev').text('Prev'))
3597 .append($('<a>').addClass('slideshow-next').text('Next'));
3598
3599 var css = { 'width': $widget.width() + 'px',
3600 'height': $widget.height() + 'px' };
3601
3602 var $ul = $('<ul>');
3603
3604 for (var i = 0; i < resources.length; ++i) {
Dirk Doughertyc3921652014-05-13 16:55:26 -07003605 var $card = $('<a>')
Robert Lye7eeb402014-06-03 19:35:24 -07003606 .attr('href', cleanUrl(resources[i].url))
Dirk Doughertyc3921652014-05-13 16:55:26 -07003607 .decorateResourceCard(resources[i],plusone);
3608
3609 $('<li>').css(css)
3610 .append($card)
3611 .appendTo($ul);
3612 }
3613
3614 $('<div>').addClass('frame')
3615 .append($ul)
3616 .appendTo($widget);
3617
3618 $widget.dacSlideshow({
3619 auto: true,
3620 btnPrev: '.slideshow-prev',
3621 btnNext: '.slideshow-next'
3622 });
3623 };
3624
Robert Lye7eeb402014-06-03 19:35:24 -07003625 /* Initializes a Resource Card Stack Widget (column-based layout)
3626 Modified by LFL 6/6/14
3627 */
Dirk Doughertyc3921652014-05-13 16:55:26 -07003628 function drawResourcesStackWidget($widget, opts, resources, sections) {
3629 // Don't empty widget, grab all items inside since they will be the first
3630 // items stacked, followed by the resource query
3631 var plusone = true; //by default show plusone on section cards
3632 var cards = $widget.find('.resource-card').detach().toArray();
3633 var numStacks = opts.numStacks || 1;
3634 var $stacks = [];
3635 var urlString;
3636
3637 for (var i = 0; i < numStacks; ++i) {
3638 $stacks[i] = $('<div>').addClass('resource-card-stack')
3639 .appendTo($widget);
3640 }
3641
3642 var sectionResources = [];
3643
3644 // Extract any subsections that are actually resource cards
Robert Lye7eeb402014-06-03 19:35:24 -07003645 if (sections) {
3646 for (var i = 0; i < sections.length; ++i) {
3647 if (!sections[i].sections || !sections[i].sections.length) {
3648 // Render it as a resource card
3649 sectionResources.push(
3650 $('<a>')
3651 .addClass('resource-card section-card')
3652 .attr('href', cleanUrl(sections[i].resource.url))
3653 .decorateResourceCard(sections[i].resource,plusone)[0]
3654 );
Dirk Doughertyc3921652014-05-13 16:55:26 -07003655
Robert Lye7eeb402014-06-03 19:35:24 -07003656 } else {
3657 cards.push(
3658 $('<div>')
3659 .addClass('resource-card section-card-menu')
3660 .decorateResourceSection(sections[i],plusone)[0]
3661 );
3662 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003663 }
3664 }
3665
3666 cards = cards.concat(sectionResources);
3667
3668 for (var i = 0; i < resources.length; ++i) {
Robert Lye7eeb402014-06-03 19:35:24 -07003669 var $card = createResourceElement(resources[i], opts);
smain@google.com95948b82014-06-16 19:24:25 -07003670
Robert Lye7eeb402014-06-03 19:35:24 -07003671 if (opts.resourceStyle.indexOf('related') > -1) {
3672 $card.addClass('related-card');
3673 }
smain@google.com95948b82014-06-16 19:24:25 -07003674
Dirk Doughertyc3921652014-05-13 16:55:26 -07003675 cards.push($card[0]);
3676 }
3677
Robert Lye7eeb402014-06-03 19:35:24 -07003678 if (opts.stackSort != 'false') {
3679 for (var i = 0; i < cards.length; ++i) {
3680 // Find the stack with the shortest height, but give preference to
3681 // left to right order.
3682 var minHeight = $stacks[0].height();
3683 var minIndex = 0;
Dirk Doughertyc3921652014-05-13 16:55:26 -07003684
Robert Lye7eeb402014-06-03 19:35:24 -07003685 for (var j = 1; j < numStacks; ++j) {
3686 var height = $stacks[j].height();
3687 if (height < minHeight - 45) {
3688 minHeight = height;
3689 minIndex = j;
3690 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003691 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003692
Robert Lye7eeb402014-06-03 19:35:24 -07003693 $stacks[minIndex].append($(cards[i]));
3694 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003695 }
3696
3697 };
smain@google.com95948b82014-06-16 19:24:25 -07003698
3699 /*
Robert Lye7eeb402014-06-03 19:35:24 -07003700 Create a resource card using the given resource object and a list of html
3701 configured options. Returns a jquery object containing the element.
3702 */
smain@google.com95948b82014-06-16 19:24:25 -07003703 function createResourceElement(resource, opts, plusone) {
Robert Lye7eeb402014-06-03 19:35:24 -07003704 var $el;
smain@google.com95948b82014-06-16 19:24:25 -07003705
Robert Lye7eeb402014-06-03 19:35:24 -07003706 // The difference here is that generic cards are not entirely clickable
3707 // so its a div instead of an a tag, also the generic one is not given
3708 // the resource-card class so it appears with a transparent background
3709 // and can be styled in whatever way the css setup.
3710 if (opts.resourceStyle == 'generic') {
3711 $el = $('<div>')
3712 .addClass('resource')
3713 .attr('href', cleanUrl(resource.url))
3714 .decorateResource(resource, opts);
3715 } else {
3716 var cls = 'resource resource-card';
smain@google.com95948b82014-06-16 19:24:25 -07003717
Robert Lye7eeb402014-06-03 19:35:24 -07003718 $el = $('<a>')
3719 .addClass(cls)
3720 .attr('href', cleanUrl(resource.url))
3721 .decorateResourceCard(resource, plusone);
3722 }
smain@google.com95948b82014-06-16 19:24:25 -07003723
Robert Lye7eeb402014-06-03 19:35:24 -07003724 return $el;
3725 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003726
3727 /* Initializes a flow widget, see distribute.scss for generating accompanying css */
3728 function drawResourcesFlowWidget($widget, opts, resources) {
3729 $widget.empty();
3730 var cardSizes = opts.cardSizes || ['6x6'];
3731 var i = 0, j = 0;
3732 var plusone = true; // by default show plusone on resource cards
3733
3734 while (i < resources.length) {
3735 var cardSize = cardSizes[j++ % cardSizes.length];
3736 cardSize = cardSize.replace(/^\s+|\s+$/,'');
Dirk Doughertyc3921652014-05-13 16:55:26 -07003737 // Some card sizes do not get a plusone button, such as where space is constrained
3738 // or for cards commonly embedded in docs (to improve overall page speed).
3739 plusone = !((cardSize == "6x2") || (cardSize == "6x3") ||
3740 (cardSize == "9x2") || (cardSize == "9x3") ||
3741 (cardSize == "12x2") || (cardSize == "12x3"));
3742
3743 // A stack has a third dimension which is the number of stacked items
3744 var isStack = cardSize.match(/(\d+)x(\d+)x(\d+)/);
3745 var stackCount = 0;
3746 var $stackDiv = null;
3747
3748 if (isStack) {
3749 // Create a stack container which should have the dimensions defined
3750 // by the product of the items inside.
3751 $stackDiv = $('<div>').addClass('resource-card-stack resource-card-' + isStack[1]
3752 + 'x' + isStack[2] * isStack[3]) .appendTo($widget);
3753 }
3754
3755 // Build each stack item or just a single item
3756 do {
3757 var resource = resources[i];
Dirk Doughertyc3921652014-05-13 16:55:26 -07003758
Robert Lye7eeb402014-06-03 19:35:24 -07003759 var $card = createResourceElement(resources[i], opts, plusone);
smain@google.com95948b82014-06-16 19:24:25 -07003760
3761 $card.addClass('resource-card-' + cardSize +
Robert Lye7eeb402014-06-03 19:35:24 -07003762 ' resource-card-' + resource.type);
smain@google.com95948b82014-06-16 19:24:25 -07003763
Dirk Doughertyc3921652014-05-13 16:55:26 -07003764 if (isStack) {
3765 $card.addClass('resource-card-' + isStack[1] + 'x' + isStack[2]);
3766 if (++stackCount == parseInt(isStack[3])) {
3767 $card.addClass('resource-card-row-stack-last');
3768 stackCount = 0;
3769 }
3770 } else {
3771 stackCount = 0;
3772 }
3773
Robert Lye7eeb402014-06-03 19:35:24 -07003774 $card.appendTo($stackDiv || $widget);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003775
3776 } while (++i < resources.length && stackCount > 0);
3777 }
3778 }
3779
3780 /* Build a site map of resources using a section as a root. */
3781 function buildSectionList(opts) {
3782 if (opts.section && SECTION_BY_ID[opts.section]) {
3783 return SECTION_BY_ID[opts.section].sections || [];
3784 }
3785 return [];
3786 }
3787
3788 function buildResourceList(opts) {
3789 var maxResults = opts.maxResults || 100;
3790
3791 var query = opts.query || '';
3792 var expressions = parseResourceQuery(query);
3793 var addedResourceIndices = {};
3794 var results = [];
3795
3796 for (var i = 0; i < expressions.length; i++) {
3797 var clauses = expressions[i];
3798
3799 // build initial set of resources from first clause
3800 var firstClause = clauses[0];
3801 var resources = [];
3802 switch (firstClause.attr) {
3803 case 'type':
3804 resources = ALL_RESOURCES_BY_TYPE[firstClause.value];
3805 break;
3806 case 'lang':
3807 resources = ALL_RESOURCES_BY_LANG[firstClause.value];
3808 break;
3809 case 'tag':
3810 resources = ALL_RESOURCES_BY_TAG[firstClause.value];
3811 break;
3812 case 'collection':
3813 var urls = RESOURCE_COLLECTIONS[firstClause.value].resources || [];
3814 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3815 break;
3816 case 'section':
3817 var urls = SITE_MAP[firstClause.value].sections || [];
3818 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3819 break;
3820 }
3821 // console.log(firstClause.attr + ':' + firstClause.value);
3822 resources = resources || [];
3823
3824 // use additional clauses to filter corpus
3825 if (clauses.length > 1) {
3826 var otherClauses = clauses.slice(1);
3827 resources = resources.filter(getResourceMatchesClausesFilter(otherClauses));
3828 }
3829
3830 // filter out resources already added
3831 if (i > 1) {
3832 resources = resources.filter(getResourceNotAlreadyAddedFilter(addedResourceIndices));
3833 }
3834
3835 // add to list of already added indices
3836 for (var j = 0; j < resources.length; j++) {
3837 // console.log(resources[j].title);
3838 addedResourceIndices[resources[j].index] = 1;
3839 }
3840
3841 // concat to final results list
3842 results = results.concat(resources);
3843 }
3844
3845 if (opts.sortOrder && results.length) {
3846 var attr = opts.sortOrder;
3847
3848 if (opts.sortOrder == 'random') {
3849 var i = results.length, j, temp;
3850 while (--i) {
3851 j = Math.floor(Math.random() * (i + 1));
3852 temp = results[i];
3853 results[i] = results[j];
3854 results[j] = temp;
3855 }
3856 } else {
3857 var desc = attr.charAt(0) == '-';
3858 if (desc) {
3859 attr = attr.substring(1);
3860 }
3861 results = results.sort(function(x,y) {
3862 return (desc ? -1 : 1) * (parseInt(x[attr], 10) - parseInt(y[attr], 10));
3863 });
3864 }
3865 }
3866
3867 results = results.filter(getResourceNotAlreadyAddedFilter(addedPageResources));
3868 results = results.slice(0, maxResults);
3869
3870 for (var j = 0; j < results.length; ++j) {
3871 addedPageResources[results[j].index] = 1;
3872 }
3873
3874 return results;
3875 }
3876
3877
3878 function getResourceNotAlreadyAddedFilter(addedResourceIndices) {
3879 return function(resource) {
3880 return !addedResourceIndices[resource.index];
3881 };
3882 }
3883
3884
3885 function getResourceMatchesClausesFilter(clauses) {
3886 return function(resource) {
3887 return doesResourceMatchClauses(resource, clauses);
3888 };
3889 }
3890
3891
3892 function doesResourceMatchClauses(resource, clauses) {
3893 for (var i = 0; i < clauses.length; i++) {
3894 var map;
3895 switch (clauses[i].attr) {
3896 case 'type':
3897 map = IS_RESOURCE_OF_TYPE[clauses[i].value];
3898 break;
3899 case 'lang':
3900 map = IS_RESOURCE_IN_LANG[clauses[i].value];
3901 break;
3902 case 'tag':
3903 map = IS_RESOURCE_TAGGED[clauses[i].value];
3904 break;
3905 }
3906
3907 if (!map || (!!clauses[i].negative ? map[resource.index] : !map[resource.index])) {
3908 return clauses[i].negative;
3909 }
3910 }
3911 return true;
3912 }
smain@google.com95948b82014-06-16 19:24:25 -07003913
Robert Lye7eeb402014-06-03 19:35:24 -07003914 function cleanUrl(url)
3915 {
3916 if (url && url.indexOf('//') === -1) {
3917 url = toRoot + url;
3918 }
smain@google.com95948b82014-06-16 19:24:25 -07003919
Robert Lye7eeb402014-06-03 19:35:24 -07003920 return url;
3921 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003922
3923
3924 function parseResourceQuery(query) {
3925 // Parse query into array of expressions (expression e.g. 'tag:foo + type:video')
3926 var expressions = [];
3927 var expressionStrs = query.split(',') || [];
3928 for (var i = 0; i < expressionStrs.length; i++) {
3929 var expr = expressionStrs[i] || '';
3930
3931 // Break expression into clauses (clause e.g. 'tag:foo')
3932 var clauses = [];
3933 var clauseStrs = expr.split(/(?=[\+\-])/);
3934 for (var j = 0; j < clauseStrs.length; j++) {
3935 var clauseStr = clauseStrs[j] || '';
3936
3937 // Get attribute and value from clause (e.g. attribute='tag', value='foo')
3938 var parts = clauseStr.split(':');
3939 var clause = {};
3940
3941 clause.attr = parts[0].replace(/^\s+|\s+$/g,'');
3942 if (clause.attr) {
3943 if (clause.attr.charAt(0) == '+') {
3944 clause.attr = clause.attr.substring(1);
3945 } else if (clause.attr.charAt(0) == '-') {
3946 clause.negative = true;
3947 clause.attr = clause.attr.substring(1);
3948 }
3949 }
3950
3951 if (parts.length > 1) {
3952 clause.value = parts[1].replace(/^\s+|\s+$/g,'');
3953 }
3954
3955 clauses.push(clause);
3956 }
3957
3958 if (!clauses.length) {
3959 continue;
3960 }
3961
3962 expressions.push(clauses);
3963 }
3964
3965 return expressions;
3966 }
3967})();
3968
3969(function($) {
Robert Lye7eeb402014-06-03 19:35:24 -07003970
smain@google.com95948b82014-06-16 19:24:25 -07003971 /*
Robert Lye7eeb402014-06-03 19:35:24 -07003972 Utility method for creating dom for the description area of a card.
3973 Used in decorateResourceCard and decorateResource.
3974 */
3975 function buildResourceCardDescription(resource, plusone) {
3976 var $description = $('<div>').addClass('description ellipsis');
smain@google.com95948b82014-06-16 19:24:25 -07003977
Robert Lye7eeb402014-06-03 19:35:24 -07003978 $description.append($('<div>').addClass('text').html(resource.summary));
smain@google.com95948b82014-06-16 19:24:25 -07003979
Robert Lye7eeb402014-06-03 19:35:24 -07003980 if (resource.cta) {
3981 $description.append($('<a>').addClass('cta').html(resource.cta));
3982 }
smain@google.com95948b82014-06-16 19:24:25 -07003983
Robert Lye7eeb402014-06-03 19:35:24 -07003984 if (plusone) {
smain@google.com95948b82014-06-16 19:24:25 -07003985 var plusurl = resource.url.indexOf("//") > -1 ? resource.url :
Robert Lye7eeb402014-06-03 19:35:24 -07003986 "//developer.android.com/" + resource.url;
smain@google.com95948b82014-06-16 19:24:25 -07003987
Robert Lye7eeb402014-06-03 19:35:24 -07003988 $description.append($('<div>').addClass('util')
3989 .append($('<div>').addClass('g-plusone')
3990 .attr('data-size', 'small')
3991 .attr('data-align', 'right')
3992 .attr('data-href', plusurl)));
3993 }
smain@google.com95948b82014-06-16 19:24:25 -07003994
Robert Lye7eeb402014-06-03 19:35:24 -07003995 return $description;
3996 }
smain@google.com95948b82014-06-16 19:24:25 -07003997
3998
Dirk Doughertyc3921652014-05-13 16:55:26 -07003999 /* Simple jquery function to create dom for a standard resource card */
4000 $.fn.decorateResourceCard = function(resource,plusone) {
4001 var section = resource.group || resource.type;
smain@google.com95948b82014-06-16 19:24:25 -07004002 var imgUrl = resource.image ||
Robert Lye7eeb402014-06-03 19:35:24 -07004003 'assets/images/resource-card-default-android.jpg';
smain@google.com95948b82014-06-16 19:24:25 -07004004
Robert Lye7eeb402014-06-03 19:35:24 -07004005 if (imgUrl.indexOf('//') === -1) {
4006 imgUrl = toRoot + imgUrl;
Dirk Doughertyc3921652014-05-13 16:55:26 -07004007 }
Robert Lye7eeb402014-06-03 19:35:24 -07004008
4009 $('<div>').addClass('card-bg')
smain@google.com95948b82014-06-16 19:24:25 -07004010 .css('background-image', 'url(' + (imgUrl || toRoot +
Robert Lye7eeb402014-06-03 19:35:24 -07004011 'assets/images/resource-card-default-android.jpg') + ')')
Dirk Doughertyc3921652014-05-13 16:55:26 -07004012 .appendTo(this);
smain@google.com95948b82014-06-16 19:24:25 -07004013
Robert Lye7eeb402014-06-03 19:35:24 -07004014 $('<div>').addClass('card-info' + (!resource.summary ? ' empty-desc' : ''))
4015 .append($('<div>').addClass('section').text(section))
4016 .append($('<div>').addClass('title').html(resource.title))
4017 .append(buildResourceCardDescription(resource, plusone))
4018 .appendTo(this);
Dirk Doughertyc3921652014-05-13 16:55:26 -07004019
4020 return this;
4021 };
4022
4023 /* Simple jquery function to create dom for a resource section card (menu) */
4024 $.fn.decorateResourceSection = function(section,plusone) {
4025 var resource = section.resource;
4026 //keep url clean for matching and offline mode handling
4027 var urlPrefix = resource.image.indexOf("//") > -1 ? "" : toRoot;
4028 var $base = $('<a>')
4029 .addClass('card-bg')
4030 .attr('href', resource.url)
4031 .append($('<div>').addClass('card-section-icon')
4032 .append($('<div>').addClass('icon'))
4033 .append($('<div>').addClass('section').html(resource.title)))
4034 .appendTo(this);
4035
4036 var $cardInfo = $('<div>').addClass('card-info').appendTo(this);
4037
4038 if (section.sections && section.sections.length) {
4039 // Recurse the section sub-tree to find a resource image.
4040 var stack = [section];
4041
4042 while (stack.length) {
4043 if (stack[0].resource.image) {
4044 $base.css('background-image', 'url(' + urlPrefix + stack[0].resource.image + ')');
4045 break;
4046 }
4047
4048 if (stack[0].sections) {
4049 stack = stack.concat(stack[0].sections);
4050 }
4051
4052 stack.shift();
4053 }
4054
4055 var $ul = $('<ul>')
4056 .appendTo($cardInfo);
4057
4058 var max = section.sections.length > 3 ? 3 : section.sections.length;
4059
4060 for (var i = 0; i < max; ++i) {
4061
4062 var subResource = section.sections[i];
4063 if (!plusone) {
4064 $('<li>')
4065 .append($('<a>').attr('href', subResource.url)
4066 .append($('<div>').addClass('title').html(subResource.title))
4067 .append($('<div>').addClass('description ellipsis')
4068 .append($('<div>').addClass('text').html(subResource.summary))
4069 .append($('<div>').addClass('util'))))
4070 .appendTo($ul);
4071 } else {
4072 $('<li>')
4073 .append($('<a>').attr('href', subResource.url)
4074 .append($('<div>').addClass('title').html(subResource.title))
4075 .append($('<div>').addClass('description ellipsis')
4076 .append($('<div>').addClass('text').html(subResource.summary))
4077 .append($('<div>').addClass('util')
4078 .append($('<div>').addClass('g-plusone')
4079 .attr('data-size', 'small')
4080 .attr('data-align', 'right')
4081 .attr('data-href', resource.url)))))
4082 .appendTo($ul);
4083 }
4084 }
4085
4086 // Add a more row
4087 if (max < section.sections.length) {
4088 $('<li>')
4089 .append($('<a>').attr('href', resource.url)
4090 .append($('<div>')
4091 .addClass('title')
4092 .text('More')))
4093 .appendTo($ul);
4094 }
4095 } else {
4096 // No sub-resources, just render description?
4097 }
4098
4099 return this;
4100 };
smain@google.com95948b82014-06-16 19:24:25 -07004101
4102
4103
4104
Robert Lye7eeb402014-06-03 19:35:24 -07004105 /* Render other types of resource styles that are not cards. */
4106 $.fn.decorateResource = function(resource, opts) {
smain@google.com95948b82014-06-16 19:24:25 -07004107 var imgUrl = resource.image ||
Robert Lye7eeb402014-06-03 19:35:24 -07004108 'assets/images/resource-card-default-android.jpg';
4109 var linkUrl = resource.url;
smain@google.com95948b82014-06-16 19:24:25 -07004110
Robert Lye7eeb402014-06-03 19:35:24 -07004111 if (imgUrl.indexOf('//') === -1) {
4112 imgUrl = toRoot + imgUrl;
4113 }
smain@google.com95948b82014-06-16 19:24:25 -07004114
Robert Lye7eeb402014-06-03 19:35:24 -07004115 if (linkUrl && linkUrl.indexOf('//') === -1) {
4116 linkUrl = toRoot + linkUrl;
4117 }
4118
4119 $(this).append(
4120 $('<div>').addClass('image')
4121 .css('background-image', 'url(' + imgUrl + ')'),
4122 $('<div>').addClass('info').append(
4123 $('<h4>').addClass('title').html(resource.title),
4124 $('<p>').addClass('summary').html(resource.summary),
4125 $('<a>').attr('href', linkUrl).addClass('cta').html('Learn More')
4126 )
4127 );
4128
4129 return this;
4130 };
Dirk Doughertyc3921652014-05-13 16:55:26 -07004131})(jQuery);
Robert Lye7eeb402014-06-03 19:35:24 -07004132
4133
Dirk Doughertyc3921652014-05-13 16:55:26 -07004134/* Calculate the vertical area remaining */
4135(function($) {
4136 $.fn.ellipsisfade= function(lineHeight) {
4137 this.each(function() {
4138 // get element text
4139 var $this = $(this);
4140 var remainingHeight = $this.parent().parent().height();
4141 $this.parent().siblings().each(function ()
smain@google.comc91ecb72014-06-23 10:22:23 -07004142 {
smain@google.comcda1a9a2014-06-19 17:07:46 -07004143 if ($(this).is(":visible")) {
4144 var h = $(this).height();
4145 remainingHeight = remainingHeight - h;
4146 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07004147 });
4148
4149 adjustedRemainingHeight = ((remainingHeight)/lineHeight>>0)*lineHeight
4150 $this.parent().css({'height': adjustedRemainingHeight});
4151 $this.css({'height': "auto"});
4152 });
4153
4154 return this;
4155 };
4156}) (jQuery);
Robert Lye7eeb402014-06-03 19:35:24 -07004157
4158/*
4159 Fullscreen Carousel
smain@google.com95948b82014-06-16 19:24:25 -07004160
Robert Lye7eeb402014-06-03 19:35:24 -07004161 The following allows for an area at the top of the page that takes over the
smain@google.com95948b82014-06-16 19:24:25 -07004162 entire browser height except for its top offset and an optional bottom
Robert Lye7eeb402014-06-03 19:35:24 -07004163 padding specified as a data attribute.
smain@google.com95948b82014-06-16 19:24:25 -07004164
Robert Lye7eeb402014-06-03 19:35:24 -07004165 HTML:
smain@google.com95948b82014-06-16 19:24:25 -07004166
Robert Lye7eeb402014-06-03 19:35:24 -07004167 <div class="fullscreen-carousel">
4168 <div class="fullscreen-carousel-content">
4169 <!-- content here -->
4170 </div>
4171 <div class="fullscreen-carousel-content">
4172 <!-- content here -->
4173 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004174
Robert Lye7eeb402014-06-03 19:35:24 -07004175 etc ...
smain@google.com95948b82014-06-16 19:24:25 -07004176
Robert Lye7eeb402014-06-03 19:35:24 -07004177 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004178
Robert Lye7eeb402014-06-03 19:35:24 -07004179 Control over how the carousel takes over the screen can mostly be defined in
4180 a css file. Setting min-height on the .fullscreen-carousel-content elements
smain@google.com95948b82014-06-16 19:24:25 -07004181 will prevent them from shrinking to far vertically when the browser is very
Robert Lye7eeb402014-06-03 19:35:24 -07004182 short, and setting max-height on the .fullscreen-carousel itself will prevent
smain@google.com95948b82014-06-16 19:24:25 -07004183 the area from becoming to long in the case that the browser is stretched very
Robert Lye7eeb402014-06-03 19:35:24 -07004184 tall.
smain@google.com95948b82014-06-16 19:24:25 -07004185
Robert Lye7eeb402014-06-03 19:35:24 -07004186 There is limited functionality for having multiple sections since that request
4187 was removed, but it is possible to add .next-arrow and .prev-arrow elements to
4188 scroll between multiple content areas.
4189*/
4190
4191(function() {
4192 $(document).ready(function() {
4193 $('.fullscreen-carousel').each(function() {
4194 initWidget(this);
4195 });
4196 });
4197
4198 function initWidget(widget) {
4199 var $widget = $(widget);
smain@google.com95948b82014-06-16 19:24:25 -07004200
Robert Lye7eeb402014-06-03 19:35:24 -07004201 var topOffset = $widget.offset().top;
4202 var padBottom = parseInt($widget.data('paddingbottom')) || 0;
4203 var maxHeight = 0;
4204 var minHeight = 0;
4205 var $content = $widget.find('.fullscreen-carousel-content');
4206 var $nextArrow = $widget.find('.next-arrow');
4207 var $prevArrow = $widget.find('.prev-arrow');
4208 var $curSection = $($content[0]);
smain@google.com95948b82014-06-16 19:24:25 -07004209
Robert Lye7eeb402014-06-03 19:35:24 -07004210 if ($content.length <= 1) {
4211 $nextArrow.hide();
4212 $prevArrow.hide();
4213 } else {
4214 $nextArrow.click(function() {
4215 var index = ($content.index($curSection) + 1);
4216 $curSection.hide();
4217 $curSection = $($content[index >= $content.length ? 0 : index]);
4218 $curSection.show();
4219 });
smain@google.com95948b82014-06-16 19:24:25 -07004220
Robert Lye7eeb402014-06-03 19:35:24 -07004221 $prevArrow.click(function() {
4222 var index = ($content.index($curSection) - 1);
4223 $curSection.hide();
4224 $curSection = $($content[index < 0 ? $content.length - 1 : 0]);
4225 $curSection.show();
4226 });
4227 }
4228
4229 // Just hide all content sections except first.
4230 $content.each(function(index) {
4231 if ($(this).height() > minHeight) minHeight = $(this).height();
4232 $(this).css({position: 'absolute', display: index > 0 ? 'none' : ''});
4233 });
4234
4235 // Register for changes to window size, and trigger.
4236 $(window).resize(resizeWidget);
4237 resizeWidget();
4238
4239 function resizeWidget() {
4240 var height = $(window).height() - topOffset - padBottom;
4241 $widget.width($(window).width());
smain@google.com95948b82014-06-16 19:24:25 -07004242 $widget.height(height < minHeight ? minHeight :
Robert Lye7eeb402014-06-03 19:35:24 -07004243 (maxHeight && height > maxHeight ? maxHeight : height));
4244 }
smain@google.com95948b82014-06-16 19:24:25 -07004245 }
Robert Lye7eeb402014-06-03 19:35:24 -07004246})();
4247
4248
4249
4250
4251
4252/*
4253 Tab Carousel
smain@google.com95948b82014-06-16 19:24:25 -07004254
Robert Lye7eeb402014-06-03 19:35:24 -07004255 The following allows tab widgets to be installed via the html below. Each
4256 tab content section should have a data-tab attribute matching one of the
4257 nav items'. Also each tab content section should have a width matching the
4258 tab carousel.
smain@google.com95948b82014-06-16 19:24:25 -07004259
Robert Lye7eeb402014-06-03 19:35:24 -07004260 HTML:
smain@google.com95948b82014-06-16 19:24:25 -07004261
Robert Lye7eeb402014-06-03 19:35:24 -07004262 <div class="tab-carousel">
4263 <ul class="tab-nav">
4264 <li><a href="#" data-tab="handsets">Handsets</a>
4265 <li><a href="#" data-tab="wearable">Wearable</a>
4266 <li><a href="#" data-tab="tv">TV</a>
4267 </ul>
smain@google.com95948b82014-06-16 19:24:25 -07004268
Robert Lye7eeb402014-06-03 19:35:24 -07004269 <div class="tab-carousel-content">
4270 <div data-tab="handsets">
4271 <!--Full width content here-->
4272 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004273
Robert Lye7eeb402014-06-03 19:35:24 -07004274 <div data-tab="wearable">
4275 <!--Full width content here-->
4276 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004277
Robert Lye7eeb402014-06-03 19:35:24 -07004278 <div data-tab="tv">
4279 <!--Full width content here-->
4280 </div>
4281 </div>
4282 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004283
Robert Lye7eeb402014-06-03 19:35:24 -07004284*/
4285(function() {
4286 $(document).ready(function() {
4287 $('.tab-carousel').each(function() {
4288 initWidget(this);
4289 });
4290 });
4291
4292 function initWidget(widget) {
4293 var $widget = $(widget);
4294 var $nav = $widget.find('.tab-nav');
4295 var $anchors = $nav.find('[data-tab]');
4296 var $li = $nav.find('li');
4297 var $contentContainer = $widget.find('.tab-carousel-content');
4298 var $tabs = $contentContainer.find('[data-tab]');
4299 var $curTab = $($tabs[0]); // Current tab is first tab.
4300 var width = $widget.width();
4301
4302 // Setup nav interactivity.
4303 $anchors.click(function(evt) {
4304 evt.preventDefault();
4305 var query = '[data-tab=' + $(this).data('tab') + ']';
smain@google.com95948b82014-06-16 19:24:25 -07004306 transitionWidget($tabs.filter(query));
Robert Lye7eeb402014-06-03 19:35:24 -07004307 });
smain@google.com95948b82014-06-16 19:24:25 -07004308
Robert Lye7eeb402014-06-03 19:35:24 -07004309 // Add highlight for navigation on first item.
4310 var $highlight = $('<div>').addClass('highlight')
4311 .css({left:$li.position().left + 'px', width:$li.outerWidth() + 'px'})
4312 .appendTo($nav);
smain@google.com95948b82014-06-16 19:24:25 -07004313
Robert Lye7eeb402014-06-03 19:35:24 -07004314 // Store height since we will change contents to absolute.
4315 $contentContainer.height($contentContainer.height());
smain@google.com95948b82014-06-16 19:24:25 -07004316
Robert Lye7eeb402014-06-03 19:35:24 -07004317 // Absolutely position tabs so they're ready for transition.
4318 $tabs.each(function(index) {
4319 $(this).css({position: 'absolute', left: index > 0 ? width + 'px' : '0'});
4320 });
smain@google.com95948b82014-06-16 19:24:25 -07004321
Robert Lye7eeb402014-06-03 19:35:24 -07004322 function transitionWidget($toTab) {
4323 if (!$curTab.is($toTab)) {
4324 var curIndex = $tabs.index($curTab[0]);
4325 var toIndex = $tabs.index($toTab[0]);
4326 var dir = toIndex > curIndex ? 1 : -1;
smain@google.com95948b82014-06-16 19:24:25 -07004327
Robert Lye7eeb402014-06-03 19:35:24 -07004328 // Animate content sections.
4329 $toTab.css({left:(width * dir) + 'px'});
4330 $curTab.animate({left:(width * -dir) + 'px'});
4331 $toTab.animate({left:'0'});
smain@google.com95948b82014-06-16 19:24:25 -07004332
Robert Lye7eeb402014-06-03 19:35:24 -07004333 // Animate navigation highlight.
smain@google.com95948b82014-06-16 19:24:25 -07004334 $highlight.animate({left:$($li[toIndex]).position().left + 'px',
Robert Lye7eeb402014-06-03 19:35:24 -07004335 width:$($li[toIndex]).outerWidth() + 'px'})
smain@google.com95948b82014-06-16 19:24:25 -07004336
Robert Lye7eeb402014-06-03 19:35:24 -07004337 // Store new current section.
4338 $curTab = $toTab;
4339 }
4340 }
smain@google.com95948b82014-06-16 19:24:25 -07004341 }
Dirk Doughertyb87e3002014-11-18 19:34:34 -08004342})();