blob: 7f4be4e16e0234a204653e54f15b4c87ddf74f76 [file] [log] [blame]
Dirk Dougherty541b4942014-02-14 18:31:53 -08001var classesNav;
2var devdocNav;
3var sidenav;
4var cookie_namespace = 'android_developer';
5var NAV_PREF_TREE = "tree";
6var NAV_PREF_PANELS = "panels";
7var nav_pref;
8var isMobile = false; // true if mobile, so we can adjust some layout
9var mPagePath; // initialized in ready() function
10
11var basePath = getBaseUri(location.pathname);
12var SITE_ROOT = toRoot + basePath.substring(1,basePath.indexOf("/",1));
13var GOOGLE_DATA; // combined data for google service apis, used for search suggest
14
15// Ensure that all ajax getScript() requests allow caching
16$.ajaxSetup({
17 cache: true
18});
19
20/****** ON LOAD SET UP STUFF *********/
21
Dirk Dougherty541b4942014-02-14 18:31:53 -080022$(document).ready(function() {
23
Dirk Doughertyff233cc2015-05-04 14:37:05 -070024 // 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 //}
34
Dirk Dougherty541b4942014-02-14 18:31:53 -080035 // load json file for JD doc search suggestions
36 $.getScript(toRoot + 'jd_lists_unified.js');
37 // load json file for Android API search suggestions
38 $.getScript(toRoot + 'reference/lists.js');
39 // load json files for Google services API suggestions
40 $.getScript(toRoot + 'reference/gcm_lists.js', function(data, textStatus, jqxhr) {
41 // once the GCM json (GCM_DATA) is loaded, load the GMS json (GMS_DATA) and merge the data
42 if(jqxhr.status === 200) {
43 $.getScript(toRoot + 'reference/gms_lists.js', function(data, textStatus, jqxhr) {
44 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
57 // setup keyboard listener for search shortcut
58 $('body').keyup(function(event) {
59 if (event.which == 191) {
60 $('#search_autocomplete').focus();
61 }
62 });
63
64 // 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 });
72
73 // initialize the divs with custom scrollbars
74 $('.scroll-pane').jScrollPane( {verticalGutter:0} );
75
76 // add HRs below all H2s (except for a few other h2 variants)
Dirk Doughertyff233cc2015-05-04 14:37:05 -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/>');
Dirk Dougherty541b4942014-02-14 18:31:53 -080083
84 // set up the search close button
Dirk Dougherty29e93432015-05-05 18:17:13 -070085 $('.search .close').click(function() {
Dirk Dougherty541b4942014-02-14 18:31:53 -080086 $searchInput = $('#search_autocomplete');
87 $searchInput.attr('value', '');
88 $(this).addClass("hide");
89 $("#search-container").removeClass('active');
90 $("#search_autocomplete").blur();
91 search_focus_changed($searchInput.get(), false);
92 hideResults();
93 });
94
Dirk Dougherty29e93432015-05-05 18:17:13 -070095 // Set up quicknav
96 var quicknav_open = false;
97 $("#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 })
108
109 var expand = function() {
110 $('#header-wrap').addClass('quicknav');
111 $('#quicknav').stop().show().animate({opacity:'1'});
112 }
113
114 var collapse = function() {
115 $('#quicknav').stop().animate({opacity:'0'}, 100, function() {
116 $(this).hide();
117 $('#header-wrap').removeClass('quicknav');
118 });
119 }
120
Dirk Dougherty541b4942014-02-14 18:31:53 -0800121
122 //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
145
146 // 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 }
152
153 if (pagePath.indexOf(SITE_ROOT) == 0) {
154 if (pagePath == '' || pagePath.charAt(pagePath.length - 1) == '/') {
155 pagePath += 'index.html';
156 }
157 }
158
159 // 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;
162 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
181 // Highlight the header tabs...
182 // highlight Design tab
Dirk Dougherty032a4942015-05-04 18:17:33 -0700183 if ($("body").hasClass("design")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700184 $("#header li.design a").addClass("selected");
185 $("#sticky-header").addClass("design");
186
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700187 // highlight About tabs
188 } else if ($("body").hasClass("about")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700189 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");
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700198 }
Dirk Dougherty541b4942014-02-14 18:31:53 -0800199 // highlight Develop tab
200 } else if ($("body").hasClass("develop") || $("body").hasClass("google")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700201 $("#header li.develop a").addClass("selected");
202 $("#sticky-header").addClass("develop");
Dirk Dougherty541b4942014-02-14 18:31:53 -0800203 // In Develop docs, also highlight appropriate sub-tab
Dirk Dougherty29e93432015-05-05 18:17:13 -0700204 var rootDir = pagePathOriginal.substring(1,pagePathOriginal.indexOf('/', 1));
205 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") {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800210 // If the root is reference, but page is also part of Google Services, select Google
211 if ($("body").hasClass("google")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700212 $("#nav-x li.google a").addClass("selected");
Dirk Dougherty541b4942014-02-14 18:31:53 -0800213 } else {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700214 $("#nav-x li.reference a").addClass("selected");
Dirk Dougherty541b4942014-02-14 18:31:53 -0800215 }
Dirk Dougherty29e93432015-05-05 18:17:13 -0700216 } else if ((rootDir == "tools") || (rootDir == "sdk")) {
217 $("#nav-x li.tools a").addClass("selected");
Dirk Dougherty541b4942014-02-14 18:31:53 -0800218 } else if ($("body").hasClass("google")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700219 $("#nav-x li.google a").addClass("selected");
Dirk Dougherty541b4942014-02-14 18:31:53 -0800220 } else if ($("body").hasClass("samples")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700221 $("#nav-x li.samples a").addClass("selected");
Dirk Dougherty541b4942014-02-14 18:31:53 -0800222 }
Dirk Dougherty29e93432015-05-05 18:17:13 -0700223
Dirk Dougherty541b4942014-02-14 18:31:53 -0800224 // highlight Distribute tab
225 } else if ($("body").hasClass("distribute")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700226 $("#header li.distribute a").addClass("selected");
227 $("#sticky-header").addClass("distribute");
Dirk Dougherty541b4942014-02-14 18:31:53 -0800228
Dirk Dougherty29e93432015-05-05 18:17:13 -0700229 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 == "analyze") {
238 $("#nav-x li.analyze a").addClass("selected");
239 } else if (secondFrag == "tools") {
240 $("#nav-x li.disttools a").addClass("selected");
241 } else if (secondFrag == "stories") {
242 $("#nav-x li.stories a").addClass("selected");
243 } else if (secondFrag == "essentials") {
244 $("#nav-x li.essentials a").addClass("selected");
245 } else if (secondFrag == "googleplay") {
246 $("#nav-x li.googleplay a").addClass("selected");
Dirk Dougherty08032402014-02-15 10:14:35 -0800247 }
Dirk Dougherty29e93432015-05-05 18:17:13 -0700248 } else if ($("body").hasClass("about")) {
249 $("#sticky-header").addClass("about");
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700250 }
Dirk Dougherty318fb972014-04-08 18:46:53 -0700251
Dirk Dougherty541b4942014-02-14 18:31:53 -0800252 // set global variable so we can highlight the sidenav a bit later (such as for google reference)
253 // and highlight the sidenav
254 mPagePath = pagePath;
255 highlightSidenav();
Scott Main20cf2a92014-04-02 21:57:20 -0700256 buildBreadcrumbs();
Dirk Dougherty541b4942014-02-14 18:31:53 -0800257
258 // set up prev/next links if they exist
259 var $selNavLink = $('#nav').find('a[href="' + pagePath + '"]');
260 var $selListItem;
261 if ($selNavLink.length) {
262 $selListItem = $selNavLink.closest('li');
263
264 // set up prev links
265 var $prevLink = [];
266 var $prevListItem = $selListItem.prev('li');
267
268 var crossBoundaries = ($("body.design").length > 0) || ($("body.guide").length > 0) ? true :
269false; // navigate across topic boundaries only in design docs
270 if ($prevListItem.length) {
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700271 if ($prevListItem.hasClass('nav-section') || crossBoundaries) {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800272 // jump to last topic of previous section
273 $prevLink = $prevListItem.find('a:last');
274 } else if (!$selListItem.hasClass('nav-section')) {
275 // jump to previous topic in this section
276 $prevLink = $prevListItem.find('a:eq(0)');
277 }
278 } else {
279 // jump to this section's index page (if it exists)
280 var $parentListItem = $selListItem.parents('li');
281 $prevLink = $selListItem.parents('li').find('a');
282
283 // except if cross boundaries aren't allowed, and we're at the top of a section already
284 // (and there's another parent)
285 if (!crossBoundaries && $parentListItem.hasClass('nav-section')
286 && $selListItem.hasClass('nav-section')) {
287 $prevLink = [];
288 }
289 }
290
291 // set up next links
292 var $nextLink = [];
293 var startClass = false;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800294 var isCrossingBoundary = false;
295
296 if ($selListItem.hasClass('nav-section') && $selListItem.children('div.empty').length == 0) {
297 // we're on an index page, jump to the first topic
298 $nextLink = $selListItem.find('ul:eq(0)').find('a:eq(0)');
299
300 // if there aren't any children, go to the next section (required for About pages)
301 if($nextLink.length == 0) {
302 $nextLink = $selListItem.next('li').find('a');
303 } else if ($('.topic-start-link').length) {
304 // as long as there's a child link and there is a "topic start link" (we're on a landing)
305 // then set the landing page "start link" text to be the first doc title
306 $('.topic-start-link').text($nextLink.text().toUpperCase());
307 }
308
309 // If the selected page has a description, then it's a class or article homepage
310 if ($selListItem.find('a[description]').length) {
311 // this means we're on a class landing page
312 startClass = true;
313 }
314 } else {
315 // jump to the next topic in this section (if it exists)
316 $nextLink = $selListItem.next('li').find('a:eq(0)');
317 if ($nextLink.length == 0) {
318 isCrossingBoundary = true;
319 // no more topics in this section, jump to the first topic in the next section
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700320 $nextLink = $selListItem.parents('li:eq(0)').next('li').find('a:eq(0)');
Dirk Dougherty541b4942014-02-14 18:31:53 -0800321 if (!$nextLink.length) { // Go up another layer to look for next page (lesson > class > course)
322 $nextLink = $selListItem.parents('li:eq(1)').next('li.nav-section').find('a:eq(0)');
323 if ($nextLink.length == 0) {
324 // if that doesn't work, we're at the end of the list, so disable NEXT link
325 $('.next-page-link').attr('href','').addClass("disabled")
326 .click(function() { return false; });
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700327 // and completely hide the one in the footer
328 $('.content-footer .next-page-link').hide();
Dirk Dougherty541b4942014-02-14 18:31:53 -0800329 }
330 }
331 }
332 }
333
334 if (startClass) {
335 $('.start-class-link').attr('href', $nextLink.attr('href')).removeClass("hide");
336
337 // if there's no training bar (below the start button),
338 // then we need to add a bottom border to button
339 if (!$("#tb").length) {
340 $('.start-class-link').css({'border-bottom':'1px solid #DADADA'});
341 }
342 } else if (isCrossingBoundary && !$('body.design').length) { // Design always crosses boundaries
343 $('.content-footer.next-class').show();
344 $('.next-page-link').attr('href','')
345 .removeClass("hide").addClass("disabled")
346 .click(function() { return false; });
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700347 // and completely hide the one in the footer
348 $('.content-footer .next-page-link').hide();
Dirk Dougherty541b4942014-02-14 18:31:53 -0800349 if ($nextLink.length) {
350 $('.next-class-link').attr('href',$nextLink.attr('href'))
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700351 .removeClass("hide")
352 .append(": " + $nextLink.html());
Dirk Dougherty541b4942014-02-14 18:31:53 -0800353 $('.next-class-link').find('.new').empty();
354 }
355 } else {
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700356 $('.next-page-link').attr('href', $nextLink.attr('href'))
357 .removeClass("hide");
358 // for the footer link, also add the next page title
359 $('.content-footer .next-page-link').append(": " + $nextLink.html());
Dirk Dougherty541b4942014-02-14 18:31:53 -0800360 }
361
362 if (!startClass && $prevLink.length) {
363 var prevHref = $prevLink.attr('href');
364 if (prevHref == SITE_ROOT + 'index.html') {
365 // Don't show Previous when it leads to the homepage
366 } else {
367 $('.prev-page-link').attr('href', $prevLink.attr('href')).removeClass("hide");
368 }
369 }
370
Dirk Dougherty541b4942014-02-14 18:31:53 -0800371 }
372
373
374
375 // Set up the course landing pages for Training with class names and descriptions
376 if ($('body.trainingcourse').length) {
377 var $classLinks = $selListItem.find('ul li a').not('#nav .nav-section .nav-section ul a');
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700378
379 // create an array for all the class descriptions
380 var $classDescriptions = new Array($classLinks.length);
381 var lang = getLangPref();
382 $classLinks.each(function(index) {
383 var langDescr = $(this).attr(lang + "-description");
384 if (typeof langDescr !== 'undefined' && langDescr !== false) {
385 // if there's a class description in the selected language, use that
386 $classDescriptions[index] = langDescr;
387 } else {
388 // otherwise, use the default english description
389 $classDescriptions[index] = $(this).attr("description");
390 }
391 });
Dirk Dougherty541b4942014-02-14 18:31:53 -0800392
393 var $olClasses = $('<ol class="class-list"></ol>');
394 var $liClass;
Dirk Dougherty29e93432015-05-05 18:17:13 -0700395 var $imgIcon;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800396 var $h2Title;
397 var $pSummary;
398 var $olLessons;
399 var $liLesson;
400 $classLinks.each(function(index) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700401 $liClass = $('<li></li>');
Dirk Dougherty541b4942014-02-14 18:31:53 -0800402 $h2Title = $('<a class="title" href="'+$(this).attr('href')+'"><h2>' + $(this).html()+'</h2><span></span></a>');
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700403 $pSummary = $('<p class="description">' + $classDescriptions[index] + '</p>');
Dirk Dougherty541b4942014-02-14 18:31:53 -0800404
405 $olLessons = $('<ol class="lesson-list"></ol>');
406
407 $lessons = $(this).closest('li').find('ul li a');
408
409 if ($lessons.length) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700410 $imgIcon = $('<img src="'+toRoot+'assets/images/resource-tutorial.png" '
411 + ' width="64" height="64" alt=""/>');
Dirk Dougherty541b4942014-02-14 18:31:53 -0800412 $lessons.each(function(index) {
413 $olLessons.append('<li><a href="'+$(this).attr('href')+'">' + $(this).html()+'</a></li>');
414 });
415 } else {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700416 $imgIcon = $('<img src="'+toRoot+'assets/images/resource-article.png" '
417 + ' width="64" height="64" alt=""/>');
Dirk Dougherty541b4942014-02-14 18:31:53 -0800418 $pSummary.addClass('article');
419 }
420
Dirk Dougherty29e93432015-05-05 18:17:13 -0700421 $liClass.append($h2Title).append($imgIcon).append($pSummary).append($olLessons);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800422 $olClasses.append($liClass);
423 });
424 $('.jd-descr').append($olClasses);
425 }
426
427 // Set up expand/collapse behavior
428 initExpandableNavItems("#nav");
429
430
431 $(".scroll-pane").scroll(function(event) {
432 event.preventDefault();
433 return false;
434 });
435
436 /* Resize nav height when window height changes */
437 $(window).resize(function() {
438 if ($('#side-nav').length == 0) return;
439 var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
440 setNavBarLeftPos(); // do this even if sidenav isn't fixed because it could become fixed
441 // make sidenav behave when resizing the window and side-scolling is a concern
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700442 if (sticky) {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800443 if ((stylesheet.attr("disabled") == "disabled") || stylesheet.length == 0) {
444 updateSideNavPosition();
445 } else {
446 updateSidenavFullscreenWidth();
447 }
448 }
449 resizeNav();
450 });
451
452
Dirk Dougherty541b4942014-02-14 18:31:53 -0800453 var navBarLeftPos;
454 if ($('#devdoc-nav').length) {
455 setNavBarLeftPos();
456 }
457
458
459 // Set up play-on-hover <video> tags.
460 $('video.play-on-hover').bind('click', function(){
461 $(this).get(0).load(); // in case the video isn't seekable
462 $(this).get(0).play();
463 });
464
465 // Set up tooltips
466 var TOOLTIP_MARGIN = 10;
467 $('acronym,.tooltip-link').each(function() {
468 var $target = $(this);
469 var $tooltip = $('<div>')
470 .addClass('tooltip-box')
471 .append($target.attr('title'))
472 .hide()
473 .appendTo('body');
474 $target.removeAttr('title');
475
476 $target.hover(function() {
477 // in
478 var targetRect = $target.offset();
479 targetRect.width = $target.width();
480 targetRect.height = $target.height();
481
482 $tooltip.css({
483 left: targetRect.left,
484 top: targetRect.top + targetRect.height + TOOLTIP_MARGIN
485 });
486 $tooltip.addClass('below');
487 $tooltip.show();
488 }, function() {
489 // out
490 $tooltip.hide();
491 });
492 });
493
494 // Set up <h2> deeplinks
495 $('h2').click(function() {
496 var id = $(this).attr('id');
497 if (id) {
498 document.location.hash = id;
499 }
500 });
501
502 //Loads the +1 button
503 var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
504 po.src = 'https://apis.google.com/js/plusone.js';
505 var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
506
Dirk Dougherty29e93432015-05-05 18:17:13 -0700507
508 // Revise the sidenav widths to make room for the scrollbar
509 // which avoids the visible width from changing each time the bar appears
510 var $sidenav = $("#side-nav");
511 var sidenav_width = parseInt($sidenav.innerWidth());
512
513 $("#devdoc-nav #nav").css("width", sidenav_width - 4 + "px"); // 4px is scrollbar width
514
515
Dirk Dougherty541b4942014-02-14 18:31:53 -0800516 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
517
518 if ($(".scroll-pane").length > 1) {
519 // Check if there's a user preference for the panel heights
520 var cookieHeight = readCookie("reference_height");
521 if (cookieHeight) {
522 restoreHeight(cookieHeight);
523 }
524 }
525
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700526 // Resize once loading is finished
Dirk Dougherty541b4942014-02-14 18:31:53 -0800527 resizeNav();
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700528 // Check if there's an anchor that we need to scroll into view.
529 // A delay is needed, because some browsers do not immediately scroll down to the anchor
530 window.setTimeout(offsetScrollForSticky, 100);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800531
532 /* init the language selector based on user cookie for lang */
533 loadLangPref();
534 changeNavLang(getLangPref());
535
536 /* setup event handlers to ensure the overflow menu is visible while picking lang */
537 $("#language select")
538 .mousedown(function() {
539 $("div.morehover").addClass("hover"); })
540 .blur(function() {
541 $("div.morehover").removeClass("hover"); });
542
543 /* some global variable setup */
544 resizePackagesNav = $("#resize-packages-nav");
545 classesNav = $("#classes-nav");
546 devdocNav = $("#devdoc-nav");
547
548 var cookiePath = "";
549 if (location.href.indexOf("/reference/") != -1) {
550 cookiePath = "reference_";
551 } else if (location.href.indexOf("/guide/") != -1) {
552 cookiePath = "guide_";
553 } else if (location.href.indexOf("/tools/") != -1) {
554 cookiePath = "tools_";
555 } else if (location.href.indexOf("/training/") != -1) {
556 cookiePath = "training_";
557 } else if (location.href.indexOf("/design/") != -1) {
558 cookiePath = "design_";
559 } else if (location.href.indexOf("/distribute/") != -1) {
560 cookiePath = "distribute_";
561 }
562
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700563
564 /* setup shadowbox for any videos that want it */
565 var $videoLinks = $("a.video-shadowbox-button, a.notice-developers-video");
566 if ($videoLinks.length) {
567 // if there's at least one, add the shadowbox HTML to the body
568 $('body').prepend(
569'<div id="video-container">'+
570 '<div id="video-frame">'+
571 '<div class="video-close">'+
572 '<span id="icon-video-close" onclick="closeVideo()">&nbsp;</span>'+
573 '</div>'+
574 '<div id="youTubePlayer"></div>'+
575 '</div>'+
576'</div>');
577
578 // loads the IFrame Player API code asynchronously.
579 $.getScript("https://www.youtube.com/iframe_api");
580
581 $videoLinks.each(function() {
582 var videoId = $(this).attr('href').split('?v=')[1];
583 $(this).click(function(event) {
584 event.preventDefault();
585 startYouTubePlayer(videoId);
586 });
587 });
588 }
Dirk Dougherty541b4942014-02-14 18:31:53 -0800589});
590// END of the onload event
591
592
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700593var youTubePlayer;
594function onYouTubeIframeAPIReady() {
595}
596
597/* Returns the height the shadowbox video should be. It's based on the current
598 height of the "video-frame" element, which is 100% height for the window.
599 Then minus the margin so the video isn't actually the full window height. */
600function getVideoHeight() {
601 var frameHeight = $("#video-frame").height();
602 var marginTop = $("#video-frame").css('margin-top').split('px')[0];
603 return frameHeight - (marginTop * 2);
604}
605
606var mPlayerPaused = false;
607
608function startYouTubePlayer(videoId) {
609 $("#video-container").show();
610 $("#video-frame").show();
611 mPlayerPaused = false;
612
613 // compute the size of the player so it's centered in window
614 var maxWidth = 940; // the width of the web site content
615 var videoAspect = .5625; // based on 1280x720 resolution
616 var maxHeight = maxWidth * videoAspect;
617 var videoHeight = getVideoHeight();
618 var videoWidth = videoHeight / videoAspect;
619 if (videoWidth > maxWidth) {
620 videoWidth = maxWidth;
621 videoHeight = maxHeight;
622 }
623 $("#video-frame").css('width', videoWidth);
624
625 // check if we've already created this player
626 if (youTubePlayer == null) {
627 // check if there's a start time specified
628 var idAndHash = videoId.split("#");
629 var startTime = 0;
630 if (idAndHash.length > 1) {
631 startTime = idAndHash[1].split("t=")[1] != undefined ? idAndHash[1].split("t=")[1] : 0;
632 }
633 // enable localized player
634 var lang = getLangPref();
635 var captionsOn = lang == 'en' ? 0 : 1;
636
637 youTubePlayer = new YT.Player('youTubePlayer', {
638 height: videoHeight,
639 width: videoWidth,
640 videoId: idAndHash[0],
641 playerVars: {start: startTime, hl: lang, cc_load_policy: captionsOn},
642 events: {
643 'onReady': onPlayerReady,
644 'onStateChange': onPlayerStateChange
645 }
646 });
647 } else {
648 // reset the size in case the user adjusted the window since last play
649 youTubePlayer.setSize(videoWidth, videoHeight);
650 // if a video different from the one already playing was requested, cue it up
651 if (videoId != youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0]) {
652 youTubePlayer.cueVideoById(videoId);
653 }
654 youTubePlayer.playVideo();
655 }
656}
657
658function onPlayerReady(event) {
659 event.target.playVideo();
660 mPlayerPaused = false;
661}
662
663function closeVideo() {
664 try {
665 youTubePlayer.pauseVideo();
666 } catch(e) {
667 }
668 $("#video-container").fadeOut(200);
669}
670
671/* Track youtube playback for analytics */
672function onPlayerStateChange(event) {
673 // Video starts, send the video ID
674 if (event.data == YT.PlayerState.PLAYING) {
675 if (mPlayerPaused) {
676 ga('send', 'event', 'Videos', 'Resume',
677 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0]);
678 } else {
679 // track the start playing event so we know from which page the video was selected
680 ga('send', 'event', 'Videos', 'Start: ' +
681 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0],
682 'on: ' + document.location.href);
683 }
684 mPlayerPaused = false;
685 }
686 // Video paused, send video ID and video elapsed time
687 if (event.data == YT.PlayerState.PAUSED) {
688 ga('send', 'event', 'Videos', 'Paused',
689 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0],
690 youTubePlayer.getCurrentTime());
691 mPlayerPaused = true;
692 }
693 // Video finished, send video ID and video elapsed time
694 if (event.data == YT.PlayerState.ENDED) {
695 ga('send', 'event', 'Videos', 'Finished',
696 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0],
697 youTubePlayer.getCurrentTime());
698 mPlayerPaused = true;
699 }
700}
701
702
703
Dirk Dougherty541b4942014-02-14 18:31:53 -0800704function initExpandableNavItems(rootTag) {
705 $(rootTag + ' li.nav-section .nav-section-header').click(function() {
706 var section = $(this).closest('li.nav-section');
707 if (section.hasClass('expanded')) {
708 /* hide me and descendants */
709 section.find('ul').slideUp(250, function() {
710 // remove 'expanded' class from my section and any children
711 section.closest('li').removeClass('expanded');
712 $('li.nav-section', section).removeClass('expanded');
713 resizeNav();
714 });
715 } else {
716 /* show me */
717 // first hide all other siblings
718 var $others = $('li.nav-section.expanded', $(this).closest('ul')).not('.sticky');
719 $others.removeClass('expanded').children('ul').slideUp(250);
720
721 // now expand me
722 section.closest('li').addClass('expanded');
723 section.children('ul').slideDown(250, function() {
724 resizeNav();
725 });
726 }
727 });
728
729 // Stop expand/collapse behavior when clicking on nav section links
730 // (since we're navigating away from the page)
731 // This selector captures the first instance of <a>, but not those with "#" as the href.
732 $('.nav-section-header').find('a:eq(0)').not('a[href="#"]').click(function(evt) {
733 window.location.href = $(this).attr('href');
734 return false;
735 });
736}
737
Scott Main20cf2a92014-04-02 21:57:20 -0700738
739/** Create the list of breadcrumb links in the sticky header */
740function buildBreadcrumbs() {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700741 var $breadcrumbUl = $("#sticky-header ul.breadcrumb");
Scott Main20cf2a92014-04-02 21:57:20 -0700742 // Add the secondary horizontal nav item, if provided
Dirk Dougherty29e93432015-05-05 18:17:13 -0700743 var $selectedSecondNav = $("div#nav-x ul.nav-x a.selected").clone().removeClass("selected");
Scott Main20cf2a92014-04-02 21:57:20 -0700744 if ($selectedSecondNav.length) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700745 $breadcrumbUl.prepend($("<li>").append($selectedSecondNav))
Scott Main20cf2a92014-04-02 21:57:20 -0700746 }
747 // Add the primary horizontal nav
Dirk Dougherty29e93432015-05-05 18:17:13 -0700748 var $selectedFirstNav = $("div#header-wrap ul.nav-x a.selected").clone().removeClass("selected");
Scott Main7a6ab432014-05-09 10:00:14 -0700749 // If there's no header nav item, use the logo link and title from alt text
750 if ($selectedFirstNav.length < 1) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700751 $selectedFirstNav = $("<a>")
Scott Main7a6ab432014-05-09 10:00:14 -0700752 .attr('href', $("div#header .logo a").attr('href'))
753 .text($("div#header .logo img").attr('alt'));
754 }
Dirk Dougherty29e93432015-05-05 18:17:13 -0700755 $breadcrumbUl.prepend($("<li>").append($selectedFirstNav));
Scott Main20cf2a92014-04-02 21:57:20 -0700756}
757
758
759
Dirk Dougherty541b4942014-02-14 18:31:53 -0800760/** Highlight the current page in sidenav, expanding children as appropriate */
761function highlightSidenav() {
762 // if something is already highlighted, undo it. This is for dynamic navigation (Samples index)
763 if ($("ul#nav li.selected").length) {
764 unHighlightSidenav();
765 }
766 // look for URL in sidenav, including the hash
767 var $selNavLink = $('#nav').find('a[href="' + mPagePath + location.hash + '"]');
768
769 // If the selNavLink is still empty, look for it without the hash
770 if ($selNavLink.length == 0) {
771 $selNavLink = $('#nav').find('a[href="' + mPagePath + '"]');
772 }
773
774 var $selListItem;
775 if ($selNavLink.length) {
776 // Find this page's <li> in sidenav and set selected
777 $selListItem = $selNavLink.closest('li');
778 $selListItem.addClass('selected');
779
780 // Traverse up the tree and expand all parent nav-sections
781 $selNavLink.parents('li.nav-section').each(function() {
782 $(this).addClass('expanded');
783 $(this).children('ul').show();
784 });
785 }
786}
787
788function unHighlightSidenav() {
789 $("ul#nav li.selected").removeClass("selected");
790 $('ul#nav li.nav-section.expanded').removeClass('expanded').children('ul').hide();
791}
792
793function toggleFullscreen(enable) {
794 var delay = 20;
795 var enabled = true;
796 var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
797 if (enable) {
798 // Currently NOT USING fullscreen; enable fullscreen
799 stylesheet.removeAttr('disabled');
800 $('#nav-swap .fullscreen').removeClass('disabled');
801 $('#devdoc-nav').css({left:''});
802 setTimeout(updateSidenavFullscreenWidth,delay); // need to wait a moment for css to switch
803 enabled = true;
804 } else {
805 // Currently USING fullscreen; disable fullscreen
806 stylesheet.attr('disabled', 'disabled');
807 $('#nav-swap .fullscreen').addClass('disabled');
808 setTimeout(updateSidenavFixedWidth,delay); // need to wait a moment for css to switch
809 enabled = false;
810 }
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700811 writeCookie("fullscreen", enabled, null);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800812 setNavBarLeftPos();
813 resizeNav(delay);
814 updateSideNavPosition();
815 setTimeout(initSidenavHeightResize,delay);
816}
817
818
819function setNavBarLeftPos() {
820 navBarLeftPos = $('#body-content').offset().left;
821}
822
823
824function updateSideNavPosition() {
825 var newLeft = $(window).scrollLeft() - navBarLeftPos;
826 $('#devdoc-nav').css({left: -newLeft});
Dirk Dougherty29e93432015-05-05 18:17:13 -0700827 $('#devdoc-nav .totop').css({left: -(newLeft - parseInt($('#side-nav').css('margin-left')))});
Dirk Dougherty541b4942014-02-14 18:31:53 -0800828}
829
830// TODO: use $(document).ready instead
831function addLoadEvent(newfun) {
832 var current = window.onload;
833 if (typeof window.onload != 'function') {
834 window.onload = newfun;
835 } else {
836 window.onload = function() {
837 current();
838 newfun();
839 }
840 }
841}
842
843var agent = navigator['userAgent'].toLowerCase();
844// If a mobile phone, set flag and do mobile setup
845if ((agent.indexOf("mobile") != -1) || // android, iphone, ipod
846 (agent.indexOf("blackberry") != -1) ||
847 (agent.indexOf("webos") != -1) ||
848 (agent.indexOf("mini") != -1)) { // opera mini browsers
849 isMobile = true;
850}
851
852
853$(document).ready(function() {
854 $("pre:not(.no-pretty-print)").addClass("prettyprint");
855 prettyPrint();
856});
857
858
859
860
861/* ######### RESIZE THE SIDENAV HEIGHT ########## */
862
863function resizeNav(delay) {
864 var $nav = $("#devdoc-nav");
865 var $window = $(window);
866 var navHeight;
867
868 // Get the height of entire window and the total header height.
869 // Then figure out based on scroll position whether the header is visible
870 var windowHeight = $window.height();
871 var scrollTop = $window.scrollTop();
Scott Main20cf2a92014-04-02 21:57:20 -0700872 var headerHeight = $('#header-wrapper').outerHeight();
873 var headerVisible = scrollTop < stickyTop;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800874
875 // get the height of space between nav and top of window.
876 // Could be either margin or top position, depending on whether the nav is fixed.
877 var topMargin = (parseInt($nav.css('margin-top')) || parseInt($nav.css('top'))) + 1;
878 // add 1 for the #side-nav bottom margin
879
880 // Depending on whether the header is visible, set the side nav's height.
881 if (headerVisible) {
882 // The sidenav height grows as the header goes off screen
Scott Main20cf2a92014-04-02 21:57:20 -0700883 navHeight = windowHeight - (headerHeight - scrollTop) - topMargin;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800884 } else {
885 // Once header is off screen, the nav height is almost full window height
886 navHeight = windowHeight - topMargin;
887 }
888
889
890
891 $scrollPanes = $(".scroll-pane");
892 if ($scrollPanes.length > 1) {
893 // subtract the height of the api level widget and nav swapper from the available nav height
894 navHeight -= ($('#api-nav-header').outerHeight(true) + $('#nav-swap').outerHeight(true));
895
896 $("#swapper").css({height:navHeight + "px"});
897 if ($("#nav-tree").is(":visible")) {
898 $("#nav-tree").css({height:navHeight});
899 }
900
901 var classesHeight = navHeight - parseInt($("#resize-packages-nav").css("height")) - 10 + "px";
902 //subtract 10px to account for drag bar
903
904 // if the window becomes small enough to make the class panel height 0,
905 // then the package panel should begin to shrink
906 if (parseInt(classesHeight) <= 0) {
907 $("#resize-packages-nav").css({height:navHeight - 10}); //subtract 10px for drag bar
908 $("#packages-nav").css({height:navHeight - 10});
909 }
910
911 $("#classes-nav").css({'height':classesHeight, 'margin-top':'10px'});
912 $("#classes-nav .jspContainer").css({height:classesHeight});
913
914
915 } else {
916 $nav.height(navHeight);
917 }
918
919 if (delay) {
920 updateFromResize = true;
921 delayedReInitScrollbars(delay);
922 } else {
923 reInitScrollbars();
924 }
925
926}
927
928var updateScrollbars = false;
929var updateFromResize = false;
930
931/* Re-initialize the scrollbars to account for changed nav size.
932 * This method postpones the actual update by a 1/4 second in order to optimize the
933 * scroll performance while the header is still visible, because re-initializing the
934 * scroll panes is an intensive process.
935 */
936function delayedReInitScrollbars(delay) {
937 // If we're scheduled for an update, but have received another resize request
938 // before the scheduled resize has occured, just ignore the new request
939 // (and wait for the scheduled one).
940 if (updateScrollbars && updateFromResize) {
941 updateFromResize = false;
942 return;
943 }
944
945 // We're scheduled for an update and the update request came from this method's setTimeout
946 if (updateScrollbars && !updateFromResize) {
947 reInitScrollbars();
948 updateScrollbars = false;
949 } else {
950 updateScrollbars = true;
951 updateFromResize = false;
952 setTimeout('delayedReInitScrollbars()',delay);
953 }
954}
955
956/* Re-initialize the scrollbars to account for changed nav size. */
957function reInitScrollbars() {
958 var pane = $(".scroll-pane").each(function(){
959 var api = $(this).data('jsp');
960 if (!api) { setTimeout(reInitScrollbars,300); return;}
961 api.reinitialise( {verticalGutter:0} );
962 });
963 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
964}
965
966
967/* Resize the height of the nav panels in the reference,
968 * and save the new size to a cookie */
969function saveNavPanels() {
970 var basePath = getBaseUri(location.pathname);
971 var section = basePath.substring(1,basePath.indexOf("/",1));
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700972 writeCookie("height", resizePackagesNav.css("height"), section);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800973}
974
975
976
977function restoreHeight(packageHeight) {
978 $("#resize-packages-nav").height(packageHeight);
979 $("#packages-nav").height(packageHeight);
980 // var classesHeight = navHeight - packageHeight;
981 // $("#classes-nav").css({height:classesHeight});
982 // $("#classes-nav .jspContainer").css({height:classesHeight});
983}
984
985
986
987/* ######### END RESIZE THE SIDENAV HEIGHT ########## */
988
989
990
991
992
993/** Scroll the jScrollPane to make the currently selected item visible
994 This is called when the page finished loading. */
995function scrollIntoView(nav) {
996 var $nav = $("#"+nav);
997 var element = $nav.jScrollPane({/* ...settings... */});
998 var api = element.data('jsp');
999
1000 if ($nav.is(':visible')) {
1001 var $selected = $(".selected", $nav);
1002 if ($selected.length == 0) {
1003 // If no selected item found, exit
1004 return;
1005 }
1006 // get the selected item's offset from its container nav by measuring the item's offset
1007 // relative to the document then subtract the container nav's offset relative to the document
1008 var selectedOffset = $selected.offset().top - $nav.offset().top;
1009 if (selectedOffset > $nav.height() * .8) { // multiply nav height by .8 so we move up the item
1010 // if it's more than 80% down the nav
1011 // scroll the item up by an amount equal to 80% the container nav's height
1012 api.scrollTo(0, selectedOffset - ($nav.height() * .8), false);
1013 }
1014 }
1015}
1016
1017
1018
1019
1020
1021
1022/* Show popup dialogs */
1023function showDialog(id) {
1024 $dialog = $("#"+id);
1025 $dialog.prepend('<div class="box-border"><div class="top"> <div class="left"></div> <div class="right"></div></div><div class="bottom"> <div class="left"></div> <div class="right"></div> </div> </div>');
1026 $dialog.wrapInner('<div/>');
1027 $dialog.removeClass("hide");
1028}
1029
1030
1031
1032
1033
1034/* ######### COOKIES! ########## */
1035
1036function readCookie(cookie) {
1037 var myCookie = cookie_namespace+"_"+cookie+"=";
1038 if (document.cookie) {
1039 var index = document.cookie.indexOf(myCookie);
1040 if (index != -1) {
1041 var valStart = index + myCookie.length;
1042 var valEnd = document.cookie.indexOf(";", valStart);
1043 if (valEnd == -1) {
1044 valEnd = document.cookie.length;
1045 }
1046 var val = document.cookie.substring(valStart, valEnd);
1047 return val;
1048 }
1049 }
1050 return 0;
1051}
1052
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001053function writeCookie(cookie, val, section) {
Dirk Dougherty541b4942014-02-14 18:31:53 -08001054 if (val==undefined) return;
1055 section = section == null ? "_" : "_"+section+"_";
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001056 var age = 2*365*24*60*60; // set max-age to 2 years
Dirk Dougherty541b4942014-02-14 18:31:53 -08001057 var cookieValue = cookie_namespace + section + cookie + "=" + val
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001058 + "; max-age=" + age +"; path=/";
Dirk Dougherty541b4942014-02-14 18:31:53 -08001059 document.cookie = cookieValue;
1060}
1061
1062/* ######### END COOKIES! ########## */
1063
1064
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001065var sticky = false;
Scott Maind6a8e662014-04-12 16:40:48 -07001066var stickyTop;
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001067var prevScrollLeft = 0; // used to compare current position to previous position of horiz scroll
Scott Maind6a8e662014-04-12 16:40:48 -07001068/* Sets the vertical scoll position at which the sticky bar should appear.
1069 This method is called to reset the position when search results appear or hide */
1070function setStickyTop() {
Dirk Dougherty29e93432015-05-05 18:17:13 -07001071 stickyTop = $('#header-wrapper').outerHeight() - $('#sticky-header').outerHeight();
Scott Maind6a8e662014-04-12 16:40:48 -07001072}
1073
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001074/*
1075 * Displays sticky nav bar on pages when dac header scrolls out of view
1076 */
1077$(window).scroll(function(event) {
Dirk Dougherty541b4942014-02-14 18:31:53 -08001078
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001079 setStickyTop();
1080 var hiding = false;
Dirk Dougherty29e93432015-05-05 18:17:13 -07001081 var $stickyEl = $('#sticky-header');
1082 var $menuEl = $('.menu-container');
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001083 // Exit if there's no sidenav
1084 if ($('#side-nav').length == 0) return;
1085 // Exit if the mouse target is a DIV, because that means the event is coming
1086 // from a scrollable div and so there's no need to make adjustments to our layout
1087 if ($(event.target).nodeName == "DIV") {
1088 return;
1089 }
1090
1091 var top = $(window).scrollTop();
1092 // we set the navbar fixed when the scroll position is beyond the height of the site header...
1093 var shouldBeSticky = top >= stickyTop;
1094 // ... except if the document content is shorter than the sidenav height.
1095 // (this is necessary to avoid crazy behavior on OSX Lion due to overscroll bouncing)
1096 if ($("#doc-col").height() < $("#side-nav").height()) {
1097 shouldBeSticky = false;
1098 }
1099 // Account for horizontal scroll
1100 var scrollLeft = $(window).scrollLeft();
1101 // When the sidenav is fixed and user scrolls horizontally, reposition the sidenav to match
1102 if (sticky && (scrollLeft != prevScrollLeft)) {
1103 updateSideNavPosition();
1104 prevScrollLeft = scrollLeft;
1105 }
1106
1107 // Don't continue if the header is sufficently far away
1108 // (to avoid intensive resizing that slows scrolling)
1109 if (sticky == shouldBeSticky) {
1110 return;
1111 }
1112
1113 // If sticky header visible and position is now near top, hide sticky
1114 if (sticky && !shouldBeSticky) {
1115 sticky = false;
1116 hiding = true;
1117 // make the sidenav static again
1118 $('#devdoc-nav')
1119 .removeClass('fixed')
1120 .css({'width':'auto','margin':''})
1121 .prependTo('#side-nav');
1122 // delay hide the sticky
Dirk Dougherty29e93432015-05-05 18:17:13 -07001123 $menuEl.removeClass('sticky-menu');
1124 $stickyEl.fadeOut(250);
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001125 hiding = false;
1126
1127 // update the sidenaav position for side scrolling
1128 updateSideNavPosition();
1129 } else if (!sticky && shouldBeSticky) {
1130 sticky = true;
Dirk Dougherty29e93432015-05-05 18:17:13 -07001131 $stickyEl.fadeIn(10);
1132 $menuEl.addClass('sticky-menu');
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001133
1134 // make the sidenav fixed
1135 var width = $('#devdoc-nav').width();
1136 $('#devdoc-nav')
1137 .addClass('fixed')
1138 .css({'width':width+'px'})
1139 .prependTo('#body-content');
1140
1141 // update the sidenaav position for side scrolling
1142 updateSideNavPosition();
1143
1144 } else if (hiding && top < 15) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07001145 $menuEl.removeClass('sticky-menu');
1146 $stickyEl.hide();
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001147 hiding = false;
1148 }
1149 resizeNav(250); // pass true in order to delay the scrollbar re-initialization for performance
1150});
1151
1152/*
1153 * Manages secion card states and nav resize to conclude loading
Dirk Dougherty08032402014-02-15 10:14:35 -08001154 */
Dirk Dougherty08032402014-02-15 10:14:35 -08001155(function() {
1156 $(document).ready(function() {
1157
Dirk Dougherty08032402014-02-15 10:14:35 -08001158 // Stack hover states
1159 $('.section-card-menu').each(function(index, el) {
1160 var height = $(el).height();
1161 $(el).css({height:height+'px', position:'relative'});
1162 var $cardInfo = $(el).find('.card-info');
1163
1164 $cardInfo.css({position: 'absolute', bottom:'0px', left:'0px', right:'0px', overflow:'visible'});
1165 });
1166
Dirk Dougherty08032402014-02-15 10:14:35 -08001167 });
1168
1169})();
Dirk Dougherty541b4942014-02-14 18:31:53 -08001170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184/* MISC LIBRARY FUNCTIONS */
1185
1186
1187
1188
1189
1190function toggle(obj, slide) {
1191 var ul = $("ul:first", obj);
1192 var li = ul.parent();
1193 if (li.hasClass("closed")) {
1194 if (slide) {
1195 ul.slideDown("fast");
1196 } else {
1197 ul.show();
1198 }
1199 li.removeClass("closed");
1200 li.addClass("open");
1201 $(".toggle-img", li).attr("title", "hide pages");
1202 } else {
1203 ul.slideUp("fast");
1204 li.removeClass("open");
1205 li.addClass("closed");
1206 $(".toggle-img", li).attr("title", "show pages");
1207 }
1208}
1209
1210
1211function buildToggleLists() {
1212 $(".toggle-list").each(
1213 function(i) {
1214 $("div:first", this).append("<a class='toggle-img' href='#' title='show pages' onClick='toggle(this.parentNode.parentNode, true); return false;'></a>");
1215 $(this).addClass("closed");
1216 });
1217}
1218
1219
1220
1221function hideNestedItems(list, toggle) {
1222 $list = $(list);
1223 // hide nested lists
1224 if($list.hasClass('showing')) {
1225 $("li ol", $list).hide('fast');
1226 $list.removeClass('showing');
1227 // show nested lists
1228 } else {
1229 $("li ol", $list).show('fast');
1230 $list.addClass('showing');
1231 }
1232 $(".more,.less",$(toggle)).toggle();
1233}
1234
1235
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001236/* Call this to add listeners to a <select> element for Studio/Eclipse/Other docs */
1237function setupIdeDocToggle() {
1238 $( "select.ide" ).change(function() {
1239 var selected = $(this).find("option:selected").attr("value");
1240 $(".select-ide").hide();
1241 $(".select-ide."+selected).show();
Dirk Dougherty541b4942014-02-14 18:31:53 -08001242
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001243 $("select.ide").val(selected);
1244 });
1245}
Dirk Dougherty541b4942014-02-14 18:31:53 -08001246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270/* REFERENCE NAV SWAP */
1271
1272
1273function getNavPref() {
1274 var v = readCookie('reference_nav');
1275 if (v != NAV_PREF_TREE) {
1276 v = NAV_PREF_PANELS;
1277 }
1278 return v;
1279}
1280
1281function chooseDefaultNav() {
1282 nav_pref = getNavPref();
1283 if (nav_pref == NAV_PREF_TREE) {
1284 $("#nav-panels").toggle();
1285 $("#panel-link").toggle();
1286 $("#nav-tree").toggle();
1287 $("#tree-link").toggle();
1288 }
1289}
1290
1291function swapNav() {
1292 if (nav_pref == NAV_PREF_TREE) {
1293 nav_pref = NAV_PREF_PANELS;
1294 } else {
1295 nav_pref = NAV_PREF_TREE;
1296 init_default_navtree(toRoot);
1297 }
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001298 writeCookie("nav", nav_pref, "reference");
Dirk Dougherty541b4942014-02-14 18:31:53 -08001299
1300 $("#nav-panels").toggle();
1301 $("#panel-link").toggle();
1302 $("#nav-tree").toggle();
1303 $("#tree-link").toggle();
1304
1305 resizeNav();
1306
1307 // Gross nasty hack to make tree view show up upon first swap by setting height manually
1308 $("#nav-tree .jspContainer:visible")
1309 .css({'height':$("#nav-tree .jspContainer .jspPane").height() +'px'});
1310 // Another nasty hack to make the scrollbar appear now that we have height
1311 resizeNav();
1312
1313 if ($("#nav-tree").is(':visible')) {
1314 scrollIntoView("nav-tree");
1315 } else {
1316 scrollIntoView("packages-nav");
1317 scrollIntoView("classes-nav");
1318 }
1319}
1320
1321
1322
1323/* ############################################ */
1324/* ########## LOCALIZATION ############ */
1325/* ############################################ */
1326
1327function getBaseUri(uri) {
1328 var intlUrl = (uri.substring(0,6) == "/intl/");
1329 if (intlUrl) {
1330 base = uri.substring(uri.indexOf('intl/')+5,uri.length);
1331 base = base.substring(base.indexOf('/')+1, base.length);
1332 //alert("intl, returning base url: /" + base);
1333 return ("/" + base);
1334 } else {
1335 //alert("not intl, returning uri as found.");
1336 return uri;
1337 }
1338}
1339
1340function requestAppendHL(uri) {
1341//append "?hl=<lang> to an outgoing request (such as to blog)
1342 var lang = getLangPref();
1343 if (lang) {
1344 var q = 'hl=' + lang;
1345 uri += '?' + q;
1346 window.location = uri;
1347 return false;
1348 } else {
1349 return true;
1350 }
1351}
1352
1353
1354function changeNavLang(lang) {
1355 var $links = $("#devdoc-nav,#header,#nav-x,.training-nav-top,.content-footer").find("a["+lang+"-lang]");
1356 $links.each(function(i){ // for each link with a translation
1357 var $link = $(this);
1358 if (lang != "en") { // No need to worry about English, because a language change invokes new request
1359 // put the desired language from the attribute as the text
1360 $link.text($link.attr(lang+"-lang"))
1361 }
1362 });
1363}
1364
1365function changeLangPref(lang, submit) {
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001366 writeCookie("pref_lang", lang, null);
Dirk Dougherty541b4942014-02-14 18:31:53 -08001367
1368 // ####### TODO: Remove this condition once we're stable on devsite #######
1369 // This condition is only needed if we still need to support legacy GAE server
1370 if (devsite) {
1371 // Switch language when on Devsite server
1372 if (submit) {
1373 $("#setlang").submit();
1374 }
1375 } else {
1376 // Switch language when on legacy GAE server
1377 if (submit) {
1378 window.location = getBaseUri(location.pathname);
1379 }
1380 }
1381}
1382
1383function loadLangPref() {
1384 var lang = readCookie("pref_lang");
1385 if (lang != 0) {
1386 $("#language").find("option[value='"+lang+"']").attr("selected",true);
1387 }
1388}
1389
1390function getLangPref() {
1391 var lang = $("#language").find(":selected").attr("value");
1392 if (!lang) {
1393 lang = readCookie("pref_lang");
1394 }
1395 return (lang != 0) ? lang : 'en';
1396}
1397
1398/* ########## END LOCALIZATION ############ */
1399
1400
1401
1402
1403
1404
1405/* Used to hide and reveal supplemental content, such as long code samples.
1406 See the companion CSS in android-developer-docs.css */
1407function toggleContent(obj) {
1408 var div = $(obj).closest(".toggle-content");
1409 var toggleMe = $(".toggle-content-toggleme:eq(0)",div);
1410 if (div.hasClass("closed")) { // if it's closed, open it
1411 toggleMe.slideDown();
1412 $(".toggle-content-text:eq(0)", obj).toggle();
1413 div.removeClass("closed").addClass("open");
1414 $(".toggle-content-img:eq(0)", div).attr("title", "hide").attr("src", toRoot
1415 + "assets/images/triangle-opened.png");
1416 } else { // if it's open, close it
1417 toggleMe.slideUp('fast', function() { // Wait until the animation is done before closing arrow
1418 $(".toggle-content-text:eq(0)", obj).toggle();
1419 div.removeClass("open").addClass("closed");
1420 div.find(".toggle-content").removeClass("open").addClass("closed")
1421 .find(".toggle-content-toggleme").hide();
1422 $(".toggle-content-img", div).attr("title", "show").attr("src", toRoot
1423 + "assets/images/triangle-closed.png");
1424 });
1425 }
1426 return false;
1427}
1428
1429
1430/* New version of expandable content */
1431function toggleExpandable(link,id) {
1432 if($(id).is(':visible')) {
1433 $(id).slideUp();
1434 $(link).removeClass('expanded');
1435 } else {
1436 $(id).slideDown();
1437 $(link).addClass('expanded');
1438 }
1439}
1440
1441function hideExpandable(ids) {
1442 $(ids).slideUp();
1443 $(ids).prev('h4').find('a.expandable').removeClass('expanded');
1444}
1445
1446
1447
1448
1449
1450/*
1451 * Slideshow 1.0
1452 * Used on /index.html and /develop/index.html for carousel
1453 *
1454 * Sample usage:
1455 * HTML -
1456 * <div class="slideshow-container">
1457 * <a href="" class="slideshow-prev">Prev</a>
1458 * <a href="" class="slideshow-next">Next</a>
1459 * <ul>
1460 * <li class="item"><img src="images/marquee1.jpg"></li>
1461 * <li class="item"><img src="images/marquee2.jpg"></li>
1462 * <li class="item"><img src="images/marquee3.jpg"></li>
1463 * <li class="item"><img src="images/marquee4.jpg"></li>
1464 * </ul>
1465 * </div>
1466 *
1467 * <script type="text/javascript">
1468 * $('.slideshow-container').dacSlideshow({
1469 * auto: true,
1470 * btnPrev: '.slideshow-prev',
1471 * btnNext: '.slideshow-next'
1472 * });
1473 * </script>
1474 *
1475 * Options:
1476 * btnPrev: optional identifier for previous button
1477 * btnNext: optional identifier for next button
1478 * btnPause: optional identifier for pause button
1479 * auto: whether or not to auto-proceed
1480 * speed: animation speed
1481 * autoTime: time between auto-rotation
1482 * easing: easing function for transition
1483 * start: item to select by default
1484 * scroll: direction to scroll in
1485 * pagination: whether or not to include dotted pagination
1486 *
1487 */
1488
1489 (function($) {
1490 $.fn.dacSlideshow = function(o) {
1491
1492 //Options - see above
1493 o = $.extend({
1494 btnPrev: null,
1495 btnNext: null,
1496 btnPause: null,
1497 auto: true,
1498 speed: 500,
1499 autoTime: 12000,
1500 easing: null,
1501 start: 0,
1502 scroll: 1,
1503 pagination: true
1504
1505 }, o || {});
1506
1507 //Set up a carousel for each
1508 return this.each(function() {
1509
1510 var running = false;
1511 var animCss = o.vertical ? "top" : "left";
1512 var sizeCss = o.vertical ? "height" : "width";
1513 var div = $(this);
1514 var ul = $("ul", div);
1515 var tLi = $("li", ul);
1516 var tl = tLi.size();
1517 var timer = null;
1518
1519 var li = $("li", ul);
1520 var itemLength = li.size();
1521 var curr = o.start;
1522
1523 li.css({float: o.vertical ? "none" : "left"});
1524 ul.css({margin: "0", padding: "0", position: "relative", "list-style-type": "none", "z-index": "1"});
1525 div.css({position: "relative", "z-index": "2", left: "0px"});
1526
1527 var liSize = o.vertical ? height(li) : width(li);
1528 var ulSize = liSize * itemLength;
1529 var divSize = liSize;
1530
1531 li.css({width: li.width(), height: li.height()});
1532 ul.css(sizeCss, ulSize+"px").css(animCss, -(curr*liSize));
1533
1534 div.css(sizeCss, divSize+"px");
1535
1536 //Pagination
1537 if (o.pagination) {
1538 var pagination = $("<div class='pagination'></div>");
1539 var pag_ul = $("<ul></ul>");
1540 if (tl > 1) {
1541 for (var i=0;i<tl;i++) {
1542 var li = $("<li>"+i+"</li>");
1543 pag_ul.append(li);
1544 if (i==o.start) li.addClass('active');
1545 li.click(function() {
1546 go(parseInt($(this).text()));
1547 })
1548 }
1549 pagination.append(pag_ul);
1550 div.append(pagination);
1551 }
1552 }
1553
1554 //Previous button
1555 if(o.btnPrev)
1556 $(o.btnPrev).click(function(e) {
1557 e.preventDefault();
1558 return go(curr-o.scroll);
1559 });
1560
1561 //Next button
1562 if(o.btnNext)
1563 $(o.btnNext).click(function(e) {
1564 e.preventDefault();
1565 return go(curr+o.scroll);
1566 });
1567
1568 //Pause button
1569 if(o.btnPause)
1570 $(o.btnPause).click(function(e) {
1571 e.preventDefault();
1572 if ($(this).hasClass('paused')) {
1573 startRotateTimer();
1574 } else {
1575 pauseRotateTimer();
1576 }
1577 });
1578
1579 //Auto rotation
1580 if(o.auto) startRotateTimer();
1581
1582 function startRotateTimer() {
1583 clearInterval(timer);
1584 timer = setInterval(function() {
1585 if (curr == tl-1) {
1586 go(0);
1587 } else {
1588 go(curr+o.scroll);
1589 }
1590 }, o.autoTime);
1591 $(o.btnPause).removeClass('paused');
1592 }
1593
1594 function pauseRotateTimer() {
1595 clearInterval(timer);
1596 $(o.btnPause).addClass('paused');
1597 }
1598
1599 //Go to an item
1600 function go(to) {
1601 if(!running) {
1602
1603 if(to<0) {
1604 to = itemLength-1;
1605 } else if (to>itemLength-1) {
1606 to = 0;
1607 }
1608 curr = to;
1609
1610 running = true;
1611
1612 ul.animate(
1613 animCss == "left" ? { left: -(curr*liSize) } : { top: -(curr*liSize) } , o.speed, o.easing,
1614 function() {
1615 running = false;
1616 }
1617 );
1618
1619 $(o.btnPrev + "," + o.btnNext).removeClass("disabled");
1620 $( (curr-o.scroll<0 && o.btnPrev)
1621 ||
1622 (curr+o.scroll > itemLength && o.btnNext)
1623 ||
1624 []
1625 ).addClass("disabled");
1626
1627
1628 var nav_items = $('li', pagination);
1629 nav_items.removeClass('active');
1630 nav_items.eq(to).addClass('active');
1631
1632
1633 }
1634 if(o.auto) startRotateTimer();
1635 return false;
1636 };
1637 });
1638 };
1639
1640 function css(el, prop) {
1641 return parseInt($.css(el[0], prop)) || 0;
1642 };
1643 function width(el) {
1644 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1645 };
1646 function height(el) {
1647 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1648 };
1649
1650 })(jQuery);
1651
1652
1653/*
1654 * dacSlideshow 1.0
1655 * Used on develop/index.html for side-sliding tabs
1656 *
1657 * Sample usage:
1658 * HTML -
1659 * <div class="slideshow-container">
1660 * <a href="" class="slideshow-prev">Prev</a>
1661 * <a href="" class="slideshow-next">Next</a>
1662 * <ul>
1663 * <li class="item"><img src="images/marquee1.jpg"></li>
1664 * <li class="item"><img src="images/marquee2.jpg"></li>
1665 * <li class="item"><img src="images/marquee3.jpg"></li>
1666 * <li class="item"><img src="images/marquee4.jpg"></li>
1667 * </ul>
1668 * </div>
1669 *
1670 * <script type="text/javascript">
1671 * $('.slideshow-container').dacSlideshow({
1672 * auto: true,
1673 * btnPrev: '.slideshow-prev',
1674 * btnNext: '.slideshow-next'
1675 * });
1676 * </script>
1677 *
1678 * Options:
1679 * btnPrev: optional identifier for previous button
1680 * btnNext: optional identifier for next button
1681 * auto: whether or not to auto-proceed
1682 * speed: animation speed
1683 * autoTime: time between auto-rotation
1684 * easing: easing function for transition
1685 * start: item to select by default
1686 * scroll: direction to scroll in
1687 * pagination: whether or not to include dotted pagination
1688 *
1689 */
1690 (function($) {
1691 $.fn.dacTabbedList = function(o) {
1692
1693 //Options - see above
1694 o = $.extend({
1695 speed : 250,
1696 easing: null,
1697 nav_id: null,
1698 frame_id: null
1699 }, o || {});
1700
1701 //Set up a carousel for each
1702 return this.each(function() {
1703
1704 var curr = 0;
1705 var running = false;
1706 var animCss = "margin-left";
1707 var sizeCss = "width";
1708 var div = $(this);
1709
1710 var nav = $(o.nav_id, div);
1711 var nav_li = $("li", nav);
1712 var nav_size = nav_li.size();
1713 var frame = div.find(o.frame_id);
1714 var content_width = $(frame).find('ul').width();
1715 //Buttons
1716 $(nav_li).click(function(e) {
1717 go($(nav_li).index($(this)));
1718 })
1719
1720 //Go to an item
1721 function go(to) {
1722 if(!running) {
1723 curr = to;
1724 running = true;
1725
1726 frame.animate({ 'margin-left' : -(curr*content_width) }, o.speed, o.easing,
1727 function() {
1728 running = false;
1729 }
1730 );
1731
1732
1733 nav_li.removeClass('active');
1734 nav_li.eq(to).addClass('active');
1735
1736
1737 }
1738 return false;
1739 };
1740 });
1741 };
1742
1743 function css(el, prop) {
1744 return parseInt($.css(el[0], prop)) || 0;
1745 };
1746 function width(el) {
1747 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1748 };
1749 function height(el) {
1750 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1751 };
1752
1753 })(jQuery);
1754
1755
1756
1757
1758
1759/* ######################################################## */
1760/* ################ SEARCH SUGGESTIONS ################## */
1761/* ######################################################## */
1762
1763
1764
1765var gSelectedIndex = -1; // the index position of currently highlighted suggestion
1766var gSelectedColumn = -1; // which column of suggestion lists is currently focused
1767
1768var gMatches = new Array();
1769var gLastText = "";
1770var gInitialized = false;
1771var ROW_COUNT_FRAMEWORK = 20; // max number of results in list
1772var gListLength = 0;
1773
1774
1775var gGoogleMatches = new Array();
1776var ROW_COUNT_GOOGLE = 15; // max number of results in list
1777var gGoogleListLength = 0;
1778
1779var gDocsMatches = new Array();
1780var ROW_COUNT_DOCS = 100; // max number of results in list
1781var gDocsListLength = 0;
1782
1783function onSuggestionClick(link) {
1784 // When user clicks a suggested document, track it
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001785 ga('send', 'event', 'Suggestion Click', 'clicked: ' + $(link).attr('href'),
1786 'query: ' + $("#search_autocomplete").val().toLowerCase());
Dirk Dougherty541b4942014-02-14 18:31:53 -08001787}
1788
1789function set_item_selected($li, selected)
1790{
1791 if (selected) {
1792 $li.attr('class','jd-autocomplete jd-selected');
1793 } else {
1794 $li.attr('class','jd-autocomplete');
1795 }
1796}
1797
1798function set_item_values(toroot, $li, match)
1799{
1800 var $link = $('a',$li);
1801 $link.html(match.__hilabel || match.label);
1802 $link.attr('href',toroot + match.link);
1803}
1804
1805function set_item_values_jd(toroot, $li, match)
1806{
1807 var $link = $('a',$li);
1808 $link.html(match.title);
1809 $link.attr('href',toroot + match.url);
1810}
1811
1812function new_suggestion($list) {
1813 var $li = $("<li class='jd-autocomplete'></li>");
1814 $list.append($li);
1815
1816 $li.mousedown(function() {
1817 window.location = this.firstChild.getAttribute("href");
1818 });
1819 $li.mouseover(function() {
1820 $('.search_filtered_wrapper li').removeClass('jd-selected');
1821 $(this).addClass('jd-selected');
1822 gSelectedColumn = $(".search_filtered:visible").index($(this).closest('.search_filtered'));
1823 gSelectedIndex = $("li", $(".search_filtered:visible")[gSelectedColumn]).index(this);
1824 });
1825 $li.append("<a onclick='onSuggestionClick(this)'></a>");
1826 $li.attr('class','show-item');
1827 return $li;
1828}
1829
1830function sync_selection_table(toroot)
1831{
1832 var $li; //list item jquery object
1833 var i; //list item iterator
1834
1835 // if there are NO results at all, hide all columns
1836 if (!(gMatches.length > 0) && !(gGoogleMatches.length > 0) && !(gDocsMatches.length > 0)) {
1837 $('.suggest-card').hide(300);
1838 return;
1839 }
1840
1841 // if there are api results
1842 if ((gMatches.length > 0) || (gGoogleMatches.length > 0)) {
1843 // reveal suggestion list
Dirk Dougherty29e93432015-05-05 18:17:13 -07001844 $('.suggest-card.dummy').show();
Dirk Dougherty541b4942014-02-14 18:31:53 -08001845 $('.suggest-card.reference').show();
1846 var listIndex = 0; // list index position
1847
1848 // reset the lists
Dirk Dougherty29e93432015-05-05 18:17:13 -07001849 $(".search_filtered_wrapper.reference li").remove();
Dirk Dougherty541b4942014-02-14 18:31:53 -08001850
1851 // ########### ANDROID RESULTS #############
1852 if (gMatches.length > 0) {
1853
1854 // determine android results to show
1855 gListLength = gMatches.length < ROW_COUNT_FRAMEWORK ?
1856 gMatches.length : ROW_COUNT_FRAMEWORK;
1857 for (i=0; i<gListLength; i++) {
1858 var $li = new_suggestion($(".suggest-card.reference ul"));
1859 set_item_values(toroot, $li, gMatches[i]);
1860 set_item_selected($li, i == gSelectedIndex);
1861 }
1862 }
1863
1864 // ########### GOOGLE RESULTS #############
1865 if (gGoogleMatches.length > 0) {
1866 // show header for list
1867 $(".suggest-card.reference ul").append("<li class='header'>in Google Services:</li>");
1868
1869 // determine google results to show
1870 gGoogleListLength = gGoogleMatches.length < ROW_COUNT_GOOGLE ? gGoogleMatches.length : ROW_COUNT_GOOGLE;
1871 for (i=0; i<gGoogleListLength; i++) {
1872 var $li = new_suggestion($(".suggest-card.reference ul"));
1873 set_item_values(toroot, $li, gGoogleMatches[i]);
1874 set_item_selected($li, i == gSelectedIndex);
1875 }
1876 }
1877 } else {
1878 $('.suggest-card.reference').hide();
Dirk Dougherty29e93432015-05-05 18:17:13 -07001879 $('.suggest-card.dummy').hide();
Dirk Dougherty541b4942014-02-14 18:31:53 -08001880 }
1881
1882 // ########### JD DOC RESULTS #############
1883 if (gDocsMatches.length > 0) {
1884 // reset the lists
Dirk Dougherty29e93432015-05-05 18:17:13 -07001885 $(".search_filtered_wrapper.docs li").remove();
Dirk Dougherty541b4942014-02-14 18:31:53 -08001886
1887 // determine google results to show
1888 // NOTE: The order of the conditions below for the sugg.type MUST BE SPECIFIC:
1889 // The order must match the reverse order that each section appears as a card in
1890 // the suggestion UI... this may be only for the "develop" grouped items though.
1891 gDocsListLength = gDocsMatches.length < ROW_COUNT_DOCS ? gDocsMatches.length : ROW_COUNT_DOCS;
1892 for (i=0; i<gDocsListLength; i++) {
1893 var sugg = gDocsMatches[i];
1894 var $li;
1895 if (sugg.type == "design") {
1896 $li = new_suggestion($(".suggest-card.design ul"));
1897 } else
1898 if (sugg.type == "distribute") {
1899 $li = new_suggestion($(".suggest-card.distribute ul"));
1900 } else
1901 if (sugg.type == "samples") {
1902 $li = new_suggestion($(".suggest-card.develop .child-card.samples"));
1903 } else
1904 if (sugg.type == "training") {
1905 $li = new_suggestion($(".suggest-card.develop .child-card.training"));
1906 } else
1907 if (sugg.type == "about"||"guide"||"tools"||"google") {
1908 $li = new_suggestion($(".suggest-card.develop .child-card.guides"));
1909 } else {
1910 continue;
1911 }
1912
1913 set_item_values_jd(toroot, $li, sugg);
1914 set_item_selected($li, i == gSelectedIndex);
1915 }
1916
1917 // add heading and show or hide card
1918 if ($(".suggest-card.design li").length > 0) {
1919 $(".suggest-card.design ul").prepend("<li class='header'>Design:</li>");
1920 $(".suggest-card.design").show(300);
1921 } else {
1922 $('.suggest-card.design').hide(300);
1923 }
1924 if ($(".suggest-card.distribute li").length > 0) {
1925 $(".suggest-card.distribute ul").prepend("<li class='header'>Distribute:</li>");
1926 $(".suggest-card.distribute").show(300);
1927 } else {
1928 $('.suggest-card.distribute').hide(300);
1929 }
1930 if ($(".child-card.guides li").length > 0) {
1931 $(".child-card.guides").prepend("<li class='header'>Guides:</li>");
1932 $(".child-card.guides li").appendTo(".suggest-card.develop ul");
1933 }
1934 if ($(".child-card.training li").length > 0) {
1935 $(".child-card.training").prepend("<li class='header'>Training:</li>");
1936 $(".child-card.training li").appendTo(".suggest-card.develop ul");
1937 }
1938 if ($(".child-card.samples li").length > 0) {
1939 $(".child-card.samples").prepend("<li class='header'>Samples:</li>");
1940 $(".child-card.samples li").appendTo(".suggest-card.develop ul");
1941 }
1942
1943 if ($(".suggest-card.develop li").length > 0) {
1944 $(".suggest-card.develop").show(300);
1945 } else {
1946 $('.suggest-card.develop').hide(300);
1947 }
1948
1949 } else {
Dirk Dougherty29e93432015-05-05 18:17:13 -07001950 $('.search_filtered_wrapper.docs .suggest-card:not(.dummy)').hide(300);
Dirk Dougherty541b4942014-02-14 18:31:53 -08001951 }
1952}
1953
1954/** Called by the search input's onkeydown and onkeyup events.
1955 * Handles navigation with keyboard arrows, Enter key to invoke search,
1956 * otherwise invokes search suggestions on key-up event.
1957 * @param e The JS event
1958 * @param kd True if the event is key-down
1959 * @param toroot A string for the site's root path
1960 * @returns True if the event should bubble up
1961 */
1962function search_changed(e, kd, toroot)
1963{
1964 var currentLang = getLangPref();
1965 var search = document.getElementById("search_autocomplete");
1966 var text = search.value.replace(/(^ +)|( +$)/g, '');
1967 // get the ul hosting the currently selected item
1968 gSelectedColumn = gSelectedColumn >= 0 ? gSelectedColumn : 0;
1969 var $columns = $(".search_filtered_wrapper").find(".search_filtered:visible");
1970 var $selectedUl = $columns[gSelectedColumn];
1971
1972 // show/hide the close button
1973 if (text != '') {
Dirk Dougherty29e93432015-05-05 18:17:13 -07001974 $(".search .close").removeClass("hide");
Dirk Dougherty541b4942014-02-14 18:31:53 -08001975 } else {
Dirk Dougherty29e93432015-05-05 18:17:13 -07001976 $(".search .close").addClass("hide");
Dirk Dougherty541b4942014-02-14 18:31:53 -08001977 }
1978 // 27 = esc
1979 if (e.keyCode == 27) {
1980 // close all search results
Dirk Dougherty29e93432015-05-05 18:17:13 -07001981 if (kd) $('.search .close').trigger('click');
Dirk Dougherty541b4942014-02-14 18:31:53 -08001982 return true;
1983 }
1984 // 13 = enter
1985 else if (e.keyCode == 13) {
1986 if (gSelectedIndex < 0) {
1987 $('.suggest-card').hide();
1988 if ($("#searchResults").is(":hidden") && (search.value != "")) {
1989 // if results aren't showing (and text not empty), return true to allow search to execute
Scott Main4868e9b2014-04-14 19:00:12 -07001990 $('body,html').animate({scrollTop:0}, '500', 'swing');
Dirk Dougherty541b4942014-02-14 18:31:53 -08001991 return true;
1992 } else {
1993 // otherwise, results are already showing, so allow ajax to auto refresh the results
1994 // and ignore this Enter press to avoid the reload.
1995 return false;
1996 }
1997 } else if (kd && gSelectedIndex >= 0) {
1998 // click the link corresponding to selected item
1999 $("a",$("li",$selectedUl)[gSelectedIndex]).get()[0].click();
2000 return false;
2001 }
2002 }
Dirk Doughertyff233cc2015-05-04 14:37:05 -07002003 // If Google results are showing, return true to allow ajax search to execute
Dirk Dougherty541b4942014-02-14 18:31:53 -08002004 else if ($("#searchResults").is(":visible")) {
Dirk Doughertyff233cc2015-05-04 14:37:05 -07002005 // Also, if search_results is scrolled out of view, scroll to top to make results visible
2006 if ((sticky ) && (search.value != "")) {
2007 $('body,html').animate({scrollTop:0}, '500', 'swing');
2008 }
Dirk Dougherty541b4942014-02-14 18:31:53 -08002009 return true;
2010 }
2011 // 38 UP ARROW
2012 else if (kd && (e.keyCode == 38)) {
2013 // if the next item is a header, skip it
2014 if ($($("li", $selectedUl)[gSelectedIndex-1]).hasClass("header")) {
2015 gSelectedIndex--;
2016 }
2017 if (gSelectedIndex >= 0) {
2018 $('li', $selectedUl).removeClass('jd-selected');
2019 gSelectedIndex--;
2020 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
2021 // If user reaches top, reset selected column
2022 if (gSelectedIndex < 0) {
2023 gSelectedColumn = -1;
2024 }
2025 }
2026 return false;
2027 }
2028 // 40 DOWN ARROW
2029 else if (kd && (e.keyCode == 40)) {
2030 // if the next item is a header, skip it
2031 if ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header")) {
2032 gSelectedIndex++;
2033 }
2034 if ((gSelectedIndex < $("li", $selectedUl).length-1) ||
2035 ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header"))) {
2036 $('li', $selectedUl).removeClass('jd-selected');
2037 gSelectedIndex++;
2038 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
2039 }
2040 return false;
2041 }
2042 // Consider left/right arrow navigation
2043 // NOTE: Order of suggest columns are reverse order (index position 0 is on right)
2044 else if (kd && $columns.length > 1 && gSelectedColumn >= 0) {
2045 // 37 LEFT ARROW
2046 // go left only if current column is not left-most column (last column)
2047 if (e.keyCode == 37 && gSelectedColumn < $columns.length - 1) {
2048 $('li', $selectedUl).removeClass('jd-selected');
2049 gSelectedColumn++;
2050 $selectedUl = $columns[gSelectedColumn];
2051 // keep or reset the selected item to last item as appropriate
2052 gSelectedIndex = gSelectedIndex >
2053 $("li", $selectedUl).length-1 ?
2054 $("li", $selectedUl).length-1 : gSelectedIndex;
2055 // if the corresponding item is a header, move down
2056 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
2057 gSelectedIndex++;
2058 }
2059 // set item selected
2060 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
2061 return false;
2062 }
2063 // 39 RIGHT ARROW
2064 // go right only if current column is not the right-most column (first column)
2065 else if (e.keyCode == 39 && gSelectedColumn > 0) {
2066 $('li', $selectedUl).removeClass('jd-selected');
2067 gSelectedColumn--;
2068 $selectedUl = $columns[gSelectedColumn];
2069 // keep or reset the selected item to last item as appropriate
2070 gSelectedIndex = gSelectedIndex >
2071 $("li", $selectedUl).length-1 ?
2072 $("li", $selectedUl).length-1 : gSelectedIndex;
2073 // if the corresponding item is a header, move down
2074 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
2075 gSelectedIndex++;
2076 }
2077 // set item selected
2078 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
2079 return false;
2080 }
2081 }
2082
2083 // if key-up event and not arrow down/up/left/right,
2084 // read the search query and add suggestions to gMatches
2085 else if (!kd && (e.keyCode != 40)
2086 && (e.keyCode != 38)
2087 && (e.keyCode != 37)
2088 && (e.keyCode != 39)) {
2089 gSelectedIndex = -1;
2090 gMatches = new Array();
2091 matchedCount = 0;
2092 gGoogleMatches = new Array();
2093 matchedCountGoogle = 0;
2094 gDocsMatches = new Array();
2095 matchedCountDocs = 0;
2096
2097 // Search for Android matches
2098 for (var i=0; i<DATA.length; i++) {
2099 var s = DATA[i];
2100 if (text.length != 0 &&
2101 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
2102 gMatches[matchedCount] = s;
2103 matchedCount++;
2104 }
2105 }
2106 rank_autocomplete_api_results(text, gMatches);
2107 for (var i=0; i<gMatches.length; i++) {
2108 var s = gMatches[i];
2109 }
2110
2111
2112 // Search for Google matches
2113 for (var i=0; i<GOOGLE_DATA.length; i++) {
2114 var s = GOOGLE_DATA[i];
2115 if (text.length != 0 &&
2116 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
2117 gGoogleMatches[matchedCountGoogle] = s;
2118 matchedCountGoogle++;
2119 }
2120 }
2121 rank_autocomplete_api_results(text, gGoogleMatches);
2122 for (var i=0; i<gGoogleMatches.length; i++) {
2123 var s = gGoogleMatches[i];
2124 }
2125
2126 highlight_autocomplete_result_labels(text);
2127
2128
2129
2130 // Search for matching JD docs
Dirk Doughertyff233cc2015-05-04 14:37:05 -07002131 if (text.length >= 2) {
Dirk Dougherty541b4942014-02-14 18:31:53 -08002132 // Regex to match only the beginning of a word
2133 var textRegex = new RegExp("\\b" + text.toLowerCase(), "g");
2134
2135
2136 // Search for Training classes
2137 for (var i=0; i<TRAINING_RESOURCES.length; i++) {
2138 // current search comparison, with counters for tag and title,
2139 // used later to improve ranking
2140 var s = TRAINING_RESOURCES[i];
2141 s.matched_tag = 0;
2142 s.matched_title = 0;
2143 var matched = false;
2144
2145 // Check if query matches any tags; work backwards toward 1 to assist ranking
2146 for (var j = s.keywords.length - 1; j >= 0; j--) {
2147 // it matches a tag
2148 if (s.keywords[j].toLowerCase().match(textRegex)) {
2149 matched = true;
2150 s.matched_tag = j + 1; // add 1 to index position
2151 }
2152 }
2153 // Don't consider doc title for lessons (only for class landing pages),
2154 // unless the lesson has a tag that already matches
2155 if ((s.lang == currentLang) &&
2156 (!(s.type == "training" && s.url.indexOf("index.html") == -1) || matched)) {
2157 // it matches the doc title
2158 if (s.title.toLowerCase().match(textRegex)) {
2159 matched = true;
2160 s.matched_title = 1;
2161 }
2162 }
2163 if (matched) {
2164 gDocsMatches[matchedCountDocs] = s;
2165 matchedCountDocs++;
2166 }
2167 }
2168
2169
2170 // Search for API Guides
2171 for (var i=0; i<GUIDE_RESOURCES.length; i++) {
2172 // current search comparison, with counters for tag and title,
2173 // used later to improve ranking
2174 var s = GUIDE_RESOURCES[i];
2175 s.matched_tag = 0;
2176 s.matched_title = 0;
2177 var matched = false;
2178
2179 // Check if query matches any tags; work backwards toward 1 to assist ranking
2180 for (var j = s.keywords.length - 1; j >= 0; j--) {
2181 // it matches a tag
2182 if (s.keywords[j].toLowerCase().match(textRegex)) {
2183 matched = true;
2184 s.matched_tag = j + 1; // add 1 to index position
2185 }
2186 }
2187 // Check if query matches the doc title, but only for current language
2188 if (s.lang == currentLang) {
2189 // if query matches the doc title
2190 if (s.title.toLowerCase().match(textRegex)) {
2191 matched = true;
2192 s.matched_title = 1;
2193 }
2194 }
2195 if (matched) {
2196 gDocsMatches[matchedCountDocs] = s;
2197 matchedCountDocs++;
2198 }
2199 }
2200
2201
2202 // Search for Tools Guides
2203 for (var i=0; i<TOOLS_RESOURCES.length; i++) {
2204 // current search comparison, with counters for tag and title,
2205 // used later to improve ranking
2206 var s = TOOLS_RESOURCES[i];
2207 s.matched_tag = 0;
2208 s.matched_title = 0;
2209 var matched = false;
2210
2211 // Check if query matches any tags; work backwards toward 1 to assist ranking
2212 for (var j = s.keywords.length - 1; j >= 0; j--) {
2213 // it matches a tag
2214 if (s.keywords[j].toLowerCase().match(textRegex)) {
2215 matched = true;
2216 s.matched_tag = j + 1; // add 1 to index position
2217 }
2218 }
2219 // Check if query matches the doc title, but only for current language
2220 if (s.lang == currentLang) {
2221 // if query matches the doc title
2222 if (s.title.toLowerCase().match(textRegex)) {
2223 matched = true;
2224 s.matched_title = 1;
2225 }
2226 }
2227 if (matched) {
2228 gDocsMatches[matchedCountDocs] = s;
2229 matchedCountDocs++;
2230 }
2231 }
2232
2233
2234 // Search for About docs
2235 for (var i=0; i<ABOUT_RESOURCES.length; i++) {
2236 // current search comparison, with counters for tag and title,
2237 // used later to improve ranking
2238 var s = ABOUT_RESOURCES[i];
2239 s.matched_tag = 0;
2240 s.matched_title = 0;
2241 var matched = false;
2242
2243 // Check if query matches any tags; work backwards toward 1 to assist ranking
2244 for (var j = s.keywords.length - 1; j >= 0; j--) {
2245 // it matches a tag
2246 if (s.keywords[j].toLowerCase().match(textRegex)) {
2247 matched = true;
2248 s.matched_tag = j + 1; // add 1 to index position
2249 }
2250 }
2251 // Check if query matches the doc title, but only for current language
2252 if (s.lang == currentLang) {
2253 // if query matches the doc title
2254 if (s.title.toLowerCase().match(textRegex)) {
2255 matched = true;
2256 s.matched_title = 1;
2257 }
2258 }
2259 if (matched) {
2260 gDocsMatches[matchedCountDocs] = s;
2261 matchedCountDocs++;
2262 }
2263 }
2264
2265
2266 // Search for Design guides
2267 for (var i=0; i<DESIGN_RESOURCES.length; i++) {
2268 // current search comparison, with counters for tag and title,
2269 // used later to improve ranking
2270 var s = DESIGN_RESOURCES[i];
2271 s.matched_tag = 0;
2272 s.matched_title = 0;
2273 var matched = false;
2274
2275 // Check if query matches any tags; work backwards toward 1 to assist ranking
2276 for (var j = s.keywords.length - 1; j >= 0; j--) {
2277 // it matches a tag
2278 if (s.keywords[j].toLowerCase().match(textRegex)) {
2279 matched = true;
2280 s.matched_tag = j + 1; // add 1 to index position
2281 }
2282 }
2283 // Check if query matches the doc title, but only for current language
2284 if (s.lang == currentLang) {
2285 // if query matches the doc title
2286 if (s.title.toLowerCase().match(textRegex)) {
2287 matched = true;
2288 s.matched_title = 1;
2289 }
2290 }
2291 if (matched) {
2292 gDocsMatches[matchedCountDocs] = s;
2293 matchedCountDocs++;
2294 }
2295 }
2296
2297
2298 // Search for Distribute guides
2299 for (var i=0; i<DISTRIBUTE_RESOURCES.length; i++) {
2300 // current search comparison, with counters for tag and title,
2301 // used later to improve ranking
2302 var s = DISTRIBUTE_RESOURCES[i];
2303 s.matched_tag = 0;
2304 s.matched_title = 0;
2305 var matched = false;
2306
2307 // Check if query matches any tags; work backwards toward 1 to assist ranking
2308 for (var j = s.keywords.length - 1; j >= 0; j--) {
2309 // it matches a tag
2310 if (s.keywords[j].toLowerCase().match(textRegex)) {
2311 matched = true;
2312 s.matched_tag = j + 1; // add 1 to index position
2313 }
2314 }
2315 // Check if query matches the doc title, but only for current language
2316 if (s.lang == currentLang) {
2317 // if query matches the doc title
2318 if (s.title.toLowerCase().match(textRegex)) {
2319 matched = true;
2320 s.matched_title = 1;
2321 }
2322 }
2323 if (matched) {
2324 gDocsMatches[matchedCountDocs] = s;
2325 matchedCountDocs++;
2326 }
2327 }
2328
2329
2330 // Search for Google guides
2331 for (var i=0; i<GOOGLE_RESOURCES.length; i++) {
2332 // current search comparison, with counters for tag and title,
2333 // used later to improve ranking
2334 var s = GOOGLE_RESOURCES[i];
2335 s.matched_tag = 0;
2336 s.matched_title = 0;
2337 var matched = false;
2338
2339 // Check if query matches any tags; work backwards toward 1 to assist ranking
2340 for (var j = s.keywords.length - 1; j >= 0; j--) {
2341 // it matches a tag
2342 if (s.keywords[j].toLowerCase().match(textRegex)) {
2343 matched = true;
2344 s.matched_tag = j + 1; // add 1 to index position
2345 }
2346 }
2347 // Check if query matches the doc title, but only for current language
2348 if (s.lang == currentLang) {
2349 // if query matches the doc title
2350 if (s.title.toLowerCase().match(textRegex)) {
2351 matched = true;
2352 s.matched_title = 1;
2353 }
2354 }
2355 if (matched) {
2356 gDocsMatches[matchedCountDocs] = s;
2357 matchedCountDocs++;
2358 }
2359 }
2360
2361
2362 // Search for Samples
2363 for (var i=0; i<SAMPLES_RESOURCES.length; i++) {
2364 // current search comparison, with counters for tag and title,
2365 // used later to improve ranking
2366 var s = SAMPLES_RESOURCES[i];
2367 s.matched_tag = 0;
2368 s.matched_title = 0;
2369 var matched = false;
2370 // Check if query matches any tags; work backwards toward 1 to assist ranking
2371 for (var j = s.keywords.length - 1; j >= 0; j--) {
2372 // it matches a tag
2373 if (s.keywords[j].toLowerCase().match(textRegex)) {
2374 matched = true;
2375 s.matched_tag = j + 1; // add 1 to index position
2376 }
2377 }
2378 // Check if query matches the doc title, but only for current language
2379 if (s.lang == currentLang) {
2380 // if query matches the doc title.t
2381 if (s.title.toLowerCase().match(textRegex)) {
2382 matched = true;
2383 s.matched_title = 1;
2384 }
2385 }
2386 if (matched) {
2387 gDocsMatches[matchedCountDocs] = s;
2388 matchedCountDocs++;
2389 }
2390 }
2391
2392 // Rank/sort all the matched pages
2393 rank_autocomplete_doc_results(text, gDocsMatches);
2394 }
2395
2396 // draw the suggestions
2397 sync_selection_table(toroot);
2398 return true; // allow the event to bubble up to the search api
2399 }
2400}
2401
2402/* Order the jd doc result list based on match quality */
2403function rank_autocomplete_doc_results(query, matches) {
2404 query = query || '';
2405 if (!matches || !matches.length)
2406 return;
2407
2408 var _resultScoreFn = function(match) {
2409 var score = 1.0;
2410
2411 // if the query matched a tag
2412 if (match.matched_tag > 0) {
2413 // multiply score by factor relative to position in tags list (max of 3)
2414 score *= 3 / match.matched_tag;
2415
2416 // if it also matched the title
2417 if (match.matched_title > 0) {
2418 score *= 2;
2419 }
2420 } else if (match.matched_title > 0) {
2421 score *= 3;
2422 }
2423
2424 return score;
2425 };
2426
2427 for (var i=0; i<matches.length; i++) {
2428 matches[i].__resultScore = _resultScoreFn(matches[i]);
2429 }
2430
2431 matches.sort(function(a,b){
2432 var n = b.__resultScore - a.__resultScore;
2433 if (n == 0) // lexicographical sort if scores are the same
2434 n = (a.label < b.label) ? -1 : 1;
2435 return n;
2436 });
2437}
2438
2439/* Order the result list based on match quality */
2440function rank_autocomplete_api_results(query, matches) {
2441 query = query || '';
2442 if (!matches || !matches.length)
2443 return;
2444
2445 // helper function that gets the last occurence index of the given regex
2446 // in the given string, or -1 if not found
2447 var _lastSearch = function(s, re) {
2448 if (s == '')
2449 return -1;
2450 var l = -1;
2451 var tmp;
2452 while ((tmp = s.search(re)) >= 0) {
2453 if (l < 0) l = 0;
2454 l += tmp;
2455 s = s.substr(tmp + 1);
2456 }
2457 return l;
2458 };
2459
2460 // helper function that counts the occurrences of a given character in
2461 // a given string
2462 var _countChar = function(s, c) {
2463 var n = 0;
2464 for (var i=0; i<s.length; i++)
2465 if (s.charAt(i) == c) ++n;
2466 return n;
2467 };
2468
2469 var queryLower = query.toLowerCase();
2470 var queryAlnum = (queryLower.match(/\w+/) || [''])[0];
2471 var partPrefixAlnumRE = new RegExp('\\b' + queryAlnum);
2472 var partExactAlnumRE = new RegExp('\\b' + queryAlnum + '\\b');
2473
2474 var _resultScoreFn = function(result) {
2475 // scores are calculated based on exact and prefix matches,
2476 // and then number of path separators (dots) from the last
2477 // match (i.e. favoring classes and deep package names)
2478 var score = 1.0;
2479 var labelLower = result.label.toLowerCase();
2480 var t;
2481 t = _lastSearch(labelLower, partExactAlnumRE);
2482 if (t >= 0) {
2483 // exact part match
2484 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2485 score *= 200 / (partsAfter + 1);
2486 } else {
2487 t = _lastSearch(labelLower, partPrefixAlnumRE);
2488 if (t >= 0) {
2489 // part prefix match
2490 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2491 score *= 20 / (partsAfter + 1);
2492 }
2493 }
2494
2495 return score;
2496 };
2497
2498 for (var i=0; i<matches.length; i++) {
2499 // if the API is deprecated, default score is 0; otherwise, perform scoring
2500 if (matches[i].deprecated == "true") {
2501 matches[i].__resultScore = 0;
2502 } else {
2503 matches[i].__resultScore = _resultScoreFn(matches[i]);
2504 }
2505 }
2506
2507 matches.sort(function(a,b){
2508 var n = b.__resultScore - a.__resultScore;
2509 if (n == 0) // lexicographical sort if scores are the same
2510 n = (a.label < b.label) ? -1 : 1;
2511 return n;
2512 });
2513}
2514
2515/* Add emphasis to part of string that matches query */
2516function highlight_autocomplete_result_labels(query) {
2517 query = query || '';
2518 if ((!gMatches || !gMatches.length) && (!gGoogleMatches || !gGoogleMatches.length))
2519 return;
2520
2521 var queryLower = query.toLowerCase();
2522 var queryAlnumDot = (queryLower.match(/[\w\.]+/) || [''])[0];
2523 var queryRE = new RegExp(
2524 '(' + queryAlnumDot.replace(/\./g, '\\.') + ')', 'ig');
2525 for (var i=0; i<gMatches.length; i++) {
2526 gMatches[i].__hilabel = gMatches[i].label.replace(
2527 queryRE, '<b>$1</b>');
2528 }
2529 for (var i=0; i<gGoogleMatches.length; i++) {
2530 gGoogleMatches[i].__hilabel = gGoogleMatches[i].label.replace(
2531 queryRE, '<b>$1</b>');
2532 }
2533}
2534
2535function search_focus_changed(obj, focused)
2536{
2537 if (!focused) {
2538 if(obj.value == ""){
Dirk Dougherty29e93432015-05-05 18:17:13 -07002539 $(".search .close").addClass("hide");
Dirk Dougherty541b4942014-02-14 18:31:53 -08002540 }
2541 $(".suggest-card").hide();
2542 }
2543}
2544
2545function submit_search() {
2546 var query = document.getElementById('search_autocomplete').value;
2547 location.hash = 'q=' + query;
2548 loadSearchResults();
Scott Maind6a8e662014-04-12 16:40:48 -07002549 $("#searchResults").slideDown('slow', setStickyTop);
Dirk Dougherty541b4942014-02-14 18:31:53 -08002550 return false;
2551}
2552
2553
2554function hideResults() {
Scott Maind6a8e662014-04-12 16:40:48 -07002555 $("#searchResults").slideUp('fast', setStickyTop);
Dirk Dougherty29e93432015-05-05 18:17:13 -07002556 $(".search .close").addClass("hide");
Dirk Dougherty541b4942014-02-14 18:31:53 -08002557 location.hash = '';
2558
2559 $("#search_autocomplete").val("").blur();
2560
2561 // reset the ajax search callback to nothing, so results don't appear unless ENTER
2562 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {});
2563
2564 // forcefully regain key-up event control (previously jacked by search api)
2565 $("#search_autocomplete").keyup(function(event) {
2566 return search_changed(event, false, toRoot);
2567 });
2568
2569 return false;
2570}
2571
2572
2573
2574/* ########################################################## */
2575/* ################ CUSTOM SEARCH ENGINE ################## */
2576/* ########################################################## */
2577
2578var searchControl;
2579google.load('search', '1', {"callback" : function() {
2580 searchControl = new google.search.SearchControl();
2581 } });
2582
2583function loadSearchResults() {
2584 document.getElementById("search_autocomplete").style.color = "#000";
2585
2586 searchControl = new google.search.SearchControl();
2587
2588 // use our existing search form and use tabs when multiple searchers are used
2589 drawOptions = new google.search.DrawOptions();
2590 drawOptions.setDrawMode(google.search.SearchControl.DRAW_MODE_TABBED);
2591 drawOptions.setInput(document.getElementById("search_autocomplete"));
2592
2593 // configure search result options
2594 searchOptions = new google.search.SearcherOptions();
2595 searchOptions.setExpandMode(GSearchControl.EXPAND_MODE_OPEN);
2596
2597 // configure each of the searchers, for each tab
2598 devSiteSearcher = new google.search.WebSearch();
2599 devSiteSearcher.setUserDefinedLabel("All");
2600 devSiteSearcher.setSiteRestriction("001482626316274216503:zu90b7s047u");
2601
2602 designSearcher = new google.search.WebSearch();
2603 designSearcher.setUserDefinedLabel("Design");
2604 designSearcher.setSiteRestriction("http://developer.android.com/design/");
2605
2606 trainingSearcher = new google.search.WebSearch();
2607 trainingSearcher.setUserDefinedLabel("Training");
2608 trainingSearcher.setSiteRestriction("http://developer.android.com/training/");
2609
2610 guidesSearcher = new google.search.WebSearch();
2611 guidesSearcher.setUserDefinedLabel("Guides");
2612 guidesSearcher.setSiteRestriction("http://developer.android.com/guide/");
2613
2614 referenceSearcher = new google.search.WebSearch();
2615 referenceSearcher.setUserDefinedLabel("Reference");
2616 referenceSearcher.setSiteRestriction("http://developer.android.com/reference/");
2617
2618 googleSearcher = new google.search.WebSearch();
2619 googleSearcher.setUserDefinedLabel("Google Services");
2620 googleSearcher.setSiteRestriction("http://developer.android.com/google/");
2621
2622 blogSearcher = new google.search.WebSearch();
2623 blogSearcher.setUserDefinedLabel("Blog");
2624 blogSearcher.setSiteRestriction("http://android-developers.blogspot.com");
2625
2626 // add each searcher to the search control
2627 searchControl.addSearcher(devSiteSearcher, searchOptions);
2628 searchControl.addSearcher(designSearcher, searchOptions);
2629 searchControl.addSearcher(trainingSearcher, searchOptions);
2630 searchControl.addSearcher(guidesSearcher, searchOptions);
2631 searchControl.addSearcher(referenceSearcher, searchOptions);
2632 searchControl.addSearcher(googleSearcher, searchOptions);
2633 searchControl.addSearcher(blogSearcher, searchOptions);
2634
2635 // configure result options
2636 searchControl.setResultSetSize(google.search.Search.LARGE_RESULTSET);
2637 searchControl.setLinkTarget(google.search.Search.LINK_TARGET_SELF);
2638 searchControl.setTimeoutInterval(google.search.SearchControl.TIMEOUT_SHORT);
2639 searchControl.setNoResultsString(google.search.SearchControl.NO_RESULTS_DEFAULT_STRING);
2640
2641 // upon ajax search, refresh the url and search title
2642 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {
2643 updateResultTitle(query);
2644 var query = document.getElementById('search_autocomplete').value;
2645 location.hash = 'q=' + query;
2646 });
2647
2648 // once search results load, set up click listeners
2649 searchControl.setSearchCompleteCallback(this, function(control, searcher, query) {
2650 addResultClickListeners();
2651 });
2652
2653 // draw the search results box
2654 searchControl.draw(document.getElementById("leftSearchControl"), drawOptions);
2655
2656 // get query and execute the search
2657 searchControl.execute(decodeURI(getQuery(location.hash)));
2658
2659 document.getElementById("search_autocomplete").focus();
2660 addTabListeners();
2661}
2662// End of loadSearchResults
2663
2664
2665google.setOnLoadCallback(function(){
2666 if (location.hash.indexOf("q=") == -1) {
2667 // if there's no query in the url, don't search and make sure results are hidden
2668 $('#searchResults').hide();
2669 return;
2670 } else {
2671 // first time loading search results for this page
Scott Maind6a8e662014-04-12 16:40:48 -07002672 $('#searchResults').slideDown('slow', setStickyTop);
Dirk Dougherty29e93432015-05-05 18:17:13 -07002673 $(".search .close").removeClass("hide");
Dirk Dougherty541b4942014-02-14 18:31:53 -08002674 loadSearchResults();
2675 }
2676}, true);
2677
Dirk Doughertyff233cc2015-05-04 14:37:05 -07002678/* Adjust the scroll position to account for sticky header, only if the hash matches an id.
2679 This does not handle <a name=""> tags. Some CSS fixes those, but only for reference docs. */
2680function offsetScrollForSticky() {
2681 // Ignore if there's no search bar (some special pages have no header)
2682 if ($("#search-container").length < 1) return;
2683
2684 var hash = escape(location.hash.substr(1));
2685 var $matchingElement = $("#"+hash);
2686 // Sanity check that there's an element with that ID on the page
2687 if ($matchingElement.length) {
2688 // If the position of the target element is near the top of the page (<20px, where we expect it
2689 // to be because we need to move it down 60px to become in view), then move it down 60px
2690 if (Math.abs($matchingElement.offset().top - $(window).scrollTop()) < 20) {
2691 $(window).scrollTop($(window).scrollTop() - 60);
2692 }
2693 }
2694}
2695
Dirk Dougherty541b4942014-02-14 18:31:53 -08002696// when an event on the browser history occurs (back, forward, load) requery hash and do search
2697$(window).hashchange( function(){
Dirk Doughertyff233cc2015-05-04 14:37:05 -07002698 // Ignore if there's no search bar (some special pages have no header)
2699 if ($("#search-container").length < 1) return;
2700
Scott Main4868e9b2014-04-14 19:00:12 -07002701 // If the hash isn't a search query or there's an error in the query,
2702 // then adjust the scroll position to account for sticky header, then exit.
Dirk Dougherty541b4942014-02-14 18:31:53 -08002703 if ((location.hash.indexOf("q=") == -1) || (query == "undefined")) {
2704 // If the results pane is open, close it.
2705 if (!$("#searchResults").is(":hidden")) {
2706 hideResults();
2707 }
Dirk Doughertyff233cc2015-05-04 14:37:05 -07002708 offsetScrollForSticky();
Dirk Dougherty541b4942014-02-14 18:31:53 -08002709 return;
2710 }
2711
2712 // Otherwise, we have a search to do
2713 var query = decodeURI(getQuery(location.hash));
2714 searchControl.execute(query);
Scott Maind6a8e662014-04-12 16:40:48 -07002715 $('#searchResults').slideDown('slow', setStickyTop);
Dirk Dougherty541b4942014-02-14 18:31:53 -08002716 $("#search_autocomplete").focus();
Dirk Dougherty29e93432015-05-05 18:17:13 -07002717 $(".search .close").removeClass("hide");
Dirk Dougherty541b4942014-02-14 18:31:53 -08002718
2719 updateResultTitle(query);
2720});
2721
2722function updateResultTitle(query) {
2723 $("#searchTitle").html("Results for <em>" + escapeHTML(query) + "</em>");
2724}
2725
2726// forcefully regain key-up event control (previously jacked by search api)
2727$("#search_autocomplete").keyup(function(event) {
2728 return search_changed(event, false, toRoot);
2729});
2730
2731// add event listeners to each tab so we can track the browser history
2732function addTabListeners() {
2733 var tabHeaders = $(".gsc-tabHeader");
2734 for (var i = 0; i < tabHeaders.length; i++) {
2735 $(tabHeaders[i]).attr("id",i).click(function() {
2736 /*
2737 // make a copy of the page numbers for the search left pane
2738 setTimeout(function() {
2739 // remove any residual page numbers
2740 $('#searchResults .gsc-tabsArea .gsc-cursor-box.gs-bidi-start-align').remove();
2741 // move the page numbers to the left position; make a clone,
2742 // because the element is drawn to the DOM only once
2743 // and because we're going to remove it (previous line),
2744 // we need it to be available to move again as the user navigates
2745 $('#searchResults .gsc-webResult .gsc-cursor-box.gs-bidi-start-align:visible')
2746 .clone().appendTo('#searchResults .gsc-tabsArea');
2747 }, 200);
2748 */
2749 });
2750 }
2751 setTimeout(function(){$(tabHeaders[0]).click()},200);
2752}
2753
2754// add analytics tracking events to each result link
2755function addResultClickListeners() {
2756 $("#searchResults a.gs-title").each(function(index, link) {
2757 // When user clicks enter for Google search results, track it
2758 $(link).click(function() {
Dirk Doughertyff233cc2015-05-04 14:37:05 -07002759 ga('send', 'event', 'Google Click', 'clicked: ' + $(this).attr('href'),
2760 'query: ' + $("#search_autocomplete").val().toLowerCase());
Dirk Dougherty541b4942014-02-14 18:31:53 -08002761 });
2762 });
2763}
2764
2765
2766function getQuery(hash) {
2767 var queryParts = hash.split('=');
2768 return queryParts[1];
2769}
2770
2771/* returns the given string with all HTML brackets converted to entities
2772 TODO: move this to the site's JS library */
2773function escapeHTML(string) {
2774 return string.replace(/</g,"&lt;")
2775 .replace(/>/g,"&gt;");
2776}
2777
2778
2779
2780
2781
2782
2783
2784/* ######################################################## */
2785/* ################# JAVADOC REFERENCE ################### */
2786/* ######################################################## */
2787
2788/* Initialize some droiddoc stuff, but only if we're in the reference */
2789if (location.pathname.indexOf("/reference") == 0) {
2790 if(!(location.pathname.indexOf("/reference-gms/packages.html") == 0)
2791 && !(location.pathname.indexOf("/reference-gcm/packages.html") == 0)
2792 && !(location.pathname.indexOf("/reference/com/google") == 0)) {
2793 $(document).ready(function() {
2794 // init available apis based on user pref
2795 changeApiLevel();
2796 initSidenavHeightResize()
2797 });
2798 }
2799}
2800
2801var API_LEVEL_COOKIE = "api_level";
2802var minLevel = 1;
2803var maxLevel = 1;
2804
2805/******* SIDENAV DIMENSIONS ************/
2806
2807 function initSidenavHeightResize() {
2808 // Change the drag bar size to nicely fit the scrollbar positions
2809 var $dragBar = $(".ui-resizable-s");
2810 $dragBar.css({'width': $dragBar.parent().width() - 5 + "px"});
2811
2812 $( "#resize-packages-nav" ).resizable({
2813 containment: "#nav-panels",
2814 handles: "s",
2815 alsoResize: "#packages-nav",
2816 resize: function(event, ui) { resizeNav(); }, /* resize the nav while dragging */
2817 stop: function(event, ui) { saveNavPanels(); } /* once stopped, save the sizes to cookie */
2818 });
2819
2820 }
2821
2822function updateSidenavFixedWidth() {
Dirk Doughertyff233cc2015-05-04 14:37:05 -07002823 if (!sticky) return;
Dirk Dougherty541b4942014-02-14 18:31:53 -08002824 $('#devdoc-nav').css({
2825 'width' : $('#side-nav').css('width'),
2826 'margin' : $('#side-nav').css('margin')
2827 });
2828 $('#devdoc-nav a.totop').css({'display':'block','width':$("#nav").innerWidth()+'px'});
2829
2830 initSidenavHeightResize();
2831}
2832
2833function updateSidenavFullscreenWidth() {
Dirk Doughertyff233cc2015-05-04 14:37:05 -07002834 if (!sticky) return;
Dirk Dougherty541b4942014-02-14 18:31:53 -08002835 $('#devdoc-nav').css({
2836 'width' : $('#side-nav').css('width'),
2837 'margin' : $('#side-nav').css('margin')
2838 });
2839 $('#devdoc-nav .totop').css({'left': 'inherit'});
2840
2841 initSidenavHeightResize();
2842}
2843
2844function buildApiLevelSelector() {
2845 maxLevel = SINCE_DATA.length;
2846 var userApiLevel = parseInt(readCookie(API_LEVEL_COOKIE));
2847 userApiLevel = userApiLevel == 0 ? maxLevel : userApiLevel; // If there's no cookie (zero), use the max by default
2848
2849 minLevel = parseInt($("#doc-api-level").attr("class"));
2850 // Handle provisional api levels; the provisional level will always be the highest possible level
2851 // Provisional api levels will also have a length; other stuff that's just missing a level won't,
2852 // so leave those kinds of entities at the default level of 1 (for example, the R.styleable class)
2853 if (isNaN(minLevel) && minLevel.length) {
2854 minLevel = maxLevel;
2855 }
2856 var select = $("#apiLevelSelector").html("").change(changeApiLevel);
2857 for (var i = maxLevel-1; i >= 0; i--) {
2858 var option = $("<option />").attr("value",""+SINCE_DATA[i]).append(""+SINCE_DATA[i]);
2859 // if (SINCE_DATA[i] < minLevel) option.addClass("absent"); // always false for strings (codenames)
2860 select.append(option);
2861 }
2862
2863 // get the DOM element and use setAttribute cuz IE6 fails when using jquery .attr('selected',true)
2864 var selectedLevelItem = $("#apiLevelSelector option[value='"+userApiLevel+"']").get(0);
2865 selectedLevelItem.setAttribute('selected',true);
2866}
2867
2868function changeApiLevel() {
2869 maxLevel = SINCE_DATA.length;
2870 var selectedLevel = maxLevel;
2871
2872 selectedLevel = parseInt($("#apiLevelSelector option:selected").val());
2873 toggleVisisbleApis(selectedLevel, "body");
2874
Dirk Doughertyff233cc2015-05-04 14:37:05 -07002875 writeCookie(API_LEVEL_COOKIE, selectedLevel, null);
Dirk Dougherty541b4942014-02-14 18:31:53 -08002876
2877 if (selectedLevel < minLevel) {
2878 var thing = ($("#jd-header").html().indexOf("package") != -1) ? "package" : "class";
2879 $("#naMessage").show().html("<div><p><strong>This " + thing
2880 + " requires API level " + minLevel + " or higher.</strong></p>"
2881 + "<p>This document is hidden because your selected API level for the documentation is "
2882 + selectedLevel + ". You can change the documentation API level with the selector "
2883 + "above the left navigation.</p>"
2884 + "<p>For more information about specifying the API level your app requires, "
2885 + "read <a href='" + toRoot + "training/basics/supporting-devices/platforms.html'"
2886 + ">Supporting Different Platform Versions</a>.</p>"
2887 + "<input type='button' value='OK, make this page visible' "
2888 + "title='Change the API level to " + minLevel + "' "
2889 + "onclick='$(\"#apiLevelSelector\").val(\"" + minLevel + "\");changeApiLevel();' />"
2890 + "</div>");
2891 } else {
2892 $("#naMessage").hide();
2893 }
2894}
2895
2896function toggleVisisbleApis(selectedLevel, context) {
2897 var apis = $(".api",context);
2898 apis.each(function(i) {
2899 var obj = $(this);
2900 var className = obj.attr("class");
2901 var apiLevelIndex = className.lastIndexOf("-")+1;
2902 var apiLevelEndIndex = className.indexOf(" ", apiLevelIndex);
2903 apiLevelEndIndex = apiLevelEndIndex != -1 ? apiLevelEndIndex : className.length;
2904 var apiLevel = className.substring(apiLevelIndex, apiLevelEndIndex);
2905 if (apiLevel.length == 0) { // for odd cases when the since data is actually missing, just bail
2906 return;
2907 }
2908 apiLevel = parseInt(apiLevel);
2909
2910 // Handle provisional api levels; if this item's level is the provisional one, set it to the max
2911 var selectedLevelNum = parseInt(selectedLevel)
2912 var apiLevelNum = parseInt(apiLevel);
2913 if (isNaN(apiLevelNum)) {
2914 apiLevelNum = maxLevel;
2915 }
2916
2917 // Grey things out that aren't available and give a tooltip title
2918 if (apiLevelNum > selectedLevelNum) {
2919 obj.addClass("absent").attr("title","Requires API Level \""
2920 + apiLevel + "\" or higher. To reveal, change the target API level "
2921 + "above the left navigation.");
2922 }
2923 else obj.removeClass("absent").removeAttr("title");
2924 });
2925}
2926
2927
2928
2929
2930/* ################# SIDENAV TREE VIEW ################### */
2931
2932function new_node(me, mom, text, link, children_data, api_level)
2933{
2934 var node = new Object();
2935 node.children = Array();
2936 node.children_data = children_data;
2937 node.depth = mom.depth + 1;
2938
2939 node.li = document.createElement("li");
2940 mom.get_children_ul().appendChild(node.li);
2941
2942 node.label_div = document.createElement("div");
2943 node.label_div.className = "label";
2944 if (api_level != null) {
2945 $(node.label_div).addClass("api");
2946 $(node.label_div).addClass("api-level-"+api_level);
2947 }
2948 node.li.appendChild(node.label_div);
2949
2950 if (children_data != null) {
2951 node.expand_toggle = document.createElement("a");
2952 node.expand_toggle.href = "javascript:void(0)";
2953 node.expand_toggle.onclick = function() {
2954 if (node.expanded) {
2955 $(node.get_children_ul()).slideUp("fast");
2956 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2957 node.expanded = false;
2958 } else {
2959 expand_node(me, node);
2960 }
2961 };
2962 node.label_div.appendChild(node.expand_toggle);
2963
2964 node.plus_img = document.createElement("img");
2965 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2966 node.plus_img.className = "plus";
2967 node.plus_img.width = "8";
2968 node.plus_img.border = "0";
2969 node.expand_toggle.appendChild(node.plus_img);
2970
2971 node.expanded = false;
2972 }
2973
2974 var a = document.createElement("a");
2975 node.label_div.appendChild(a);
2976 node.label = document.createTextNode(text);
2977 a.appendChild(node.label);
2978 if (link) {
2979 a.href = me.toroot + link;
2980 } else {
2981 if (children_data != null) {
2982 a.className = "nolink";
2983 a.href = "javascript:void(0)";
2984 a.onclick = node.expand_toggle.onclick;
2985 // This next line shouldn't be necessary. I'll buy a beer for the first
2986 // person who figures out how to remove this line and have the link
2987 // toggle shut on the first try. --joeo@android.com
2988 node.expanded = false;
2989 }
2990 }
2991
2992
2993 node.children_ul = null;
2994 node.get_children_ul = function() {
2995 if (!node.children_ul) {
2996 node.children_ul = document.createElement("ul");
2997 node.children_ul.className = "children_ul";
2998 node.children_ul.style.display = "none";
2999 node.li.appendChild(node.children_ul);
3000 }
3001 return node.children_ul;
3002 };
3003
3004 return node;
3005}
3006
3007
3008
3009
3010function expand_node(me, node)
3011{
3012 if (node.children_data && !node.expanded) {
3013 if (node.children_visited) {
3014 $(node.get_children_ul()).slideDown("fast");
3015 } else {
3016 get_node(me, node);
3017 if ($(node.label_div).hasClass("absent")) {
3018 $(node.get_children_ul()).addClass("absent");
3019 }
3020 $(node.get_children_ul()).slideDown("fast");
3021 }
3022 node.plus_img.src = me.toroot + "assets/images/triangle-opened-small.png";
3023 node.expanded = true;
3024
3025 // perform api level toggling because new nodes are new to the DOM
3026 var selectedLevel = $("#apiLevelSelector option:selected").val();
3027 toggleVisisbleApis(selectedLevel, "#side-nav");
3028 }
3029}
3030
3031function get_node(me, mom)
3032{
3033 mom.children_visited = true;
3034 for (var i in mom.children_data) {
3035 var node_data = mom.children_data[i];
3036 mom.children[i] = new_node(me, mom, node_data[0], node_data[1],
3037 node_data[2], node_data[3]);
3038 }
3039}
3040
3041function this_page_relative(toroot)
3042{
3043 var full = document.location.pathname;
3044 var file = "";
3045 if (toroot.substr(0, 1) == "/") {
3046 if (full.substr(0, toroot.length) == toroot) {
3047 return full.substr(toroot.length);
3048 } else {
3049 // the file isn't under toroot. Fail.
3050 return null;
3051 }
3052 } else {
3053 if (toroot != "./") {
3054 toroot = "./" + toroot;
3055 }
3056 do {
3057 if (toroot.substr(toroot.length-3, 3) == "../" || toroot == "./") {
3058 var pos = full.lastIndexOf("/");
3059 file = full.substr(pos) + file;
3060 full = full.substr(0, pos);
3061 toroot = toroot.substr(0, toroot.length-3);
3062 }
3063 } while (toroot != "" && toroot != "/");
3064 return file.substr(1);
3065 }
3066}
3067
3068function find_page(url, data)
3069{
3070 var nodes = data;
3071 var result = null;
3072 for (var i in nodes) {
3073 var d = nodes[i];
3074 if (d[1] == url) {
3075 return new Array(i);
3076 }
3077 else if (d[2] != null) {
3078 result = find_page(url, d[2]);
3079 if (result != null) {
3080 return (new Array(i).concat(result));
3081 }
3082 }
3083 }
3084 return null;
3085}
3086
3087function init_default_navtree(toroot) {
3088 // load json file for navtree data
3089 $.getScript(toRoot + 'navtree_data.js', function(data, textStatus, jqxhr) {
3090 // when the file is loaded, initialize the tree
3091 if(jqxhr.status === 200) {
3092 init_navtree("tree-list", toroot, NAVTREE_DATA);
3093 }
3094 });
3095
3096 // perform api level toggling because because the whole tree is new to the DOM
3097 var selectedLevel = $("#apiLevelSelector option:selected").val();
3098 toggleVisisbleApis(selectedLevel, "#side-nav");
3099}
3100
3101function init_navtree(navtree_id, toroot, root_nodes)
3102{
3103 var me = new Object();
3104 me.toroot = toroot;
3105 me.node = new Object();
3106
3107 me.node.li = document.getElementById(navtree_id);
3108 me.node.children_data = root_nodes;
3109 me.node.children = new Array();
3110 me.node.children_ul = document.createElement("ul");
3111 me.node.get_children_ul = function() { return me.node.children_ul; };
3112 //me.node.children_ul.className = "children_ul";
3113 me.node.li.appendChild(me.node.children_ul);
3114 me.node.depth = 0;
3115
3116 get_node(me, me.node);
3117
3118 me.this_page = this_page_relative(toroot);
3119 me.breadcrumbs = find_page(me.this_page, root_nodes);
3120 if (me.breadcrumbs != null && me.breadcrumbs.length != 0) {
3121 var mom = me.node;
3122 for (var i in me.breadcrumbs) {
3123 var j = me.breadcrumbs[i];
3124 mom = mom.children[j];
3125 expand_node(me, mom);
3126 }
3127 mom.label_div.className = mom.label_div.className + " selected";
3128 addLoadEvent(function() {
3129 scrollIntoView("nav-tree");
3130 });
3131 }
3132}
3133
3134
3135
3136
3137
3138
3139
3140
3141/* TODO: eliminate redundancy with non-google functions */
3142function init_google_navtree(navtree_id, toroot, root_nodes)
3143{
3144 var me = new Object();
3145 me.toroot = toroot;
3146 me.node = new Object();
3147
3148 me.node.li = document.getElementById(navtree_id);
3149 me.node.children_data = root_nodes;
3150 me.node.children = new Array();
3151 me.node.children_ul = document.createElement("ul");
3152 me.node.get_children_ul = function() { return me.node.children_ul; };
3153 //me.node.children_ul.className = "children_ul";
3154 me.node.li.appendChild(me.node.children_ul);
3155 me.node.depth = 0;
3156
3157 get_google_node(me, me.node);
3158}
3159
3160function new_google_node(me, mom, text, link, children_data, api_level)
3161{
3162 var node = new Object();
3163 var child;
3164 node.children = Array();
3165 node.children_data = children_data;
3166 node.depth = mom.depth + 1;
3167 node.get_children_ul = function() {
3168 if (!node.children_ul) {
3169 node.children_ul = document.createElement("ul");
3170 node.children_ul.className = "tree-list-children";
3171 node.li.appendChild(node.children_ul);
3172 }
3173 return node.children_ul;
3174 };
3175 node.li = document.createElement("li");
3176
3177 mom.get_children_ul().appendChild(node.li);
3178
3179
3180 if(link) {
3181 child = document.createElement("a");
3182
3183 }
3184 else {
3185 child = document.createElement("span");
3186 child.className = "tree-list-subtitle";
3187
3188 }
3189 if (children_data != null) {
3190 node.li.className="nav-section";
3191 node.label_div = document.createElement("div");
3192 node.label_div.className = "nav-section-header-ref";
3193 node.li.appendChild(node.label_div);
3194 get_google_node(me, node);
3195 node.label_div.appendChild(child);
3196 }
3197 else {
3198 node.li.appendChild(child);
3199 }
3200 if(link) {
3201 child.href = me.toroot + link;
3202 }
3203 node.label = document.createTextNode(text);
3204 child.appendChild(node.label);
3205
3206 node.children_ul = null;
3207
3208 return node;
3209}
3210
3211function get_google_node(me, mom)
3212{
3213 mom.children_visited = true;
3214 var linkText;
3215 for (var i in mom.children_data) {
3216 var node_data = mom.children_data[i];
3217 linkText = node_data[0];
3218
3219 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3220 linkText = linkText.substr(19, linkText.length);
3221 }
3222 mom.children[i] = new_google_node(me, mom, linkText, node_data[1],
3223 node_data[2], node_data[3]);
3224 }
3225}
3226
3227
3228
3229
3230
3231
3232/****** NEW version of script to build google and sample navs dynamically ******/
3233// TODO: update Google reference docs to tolerate this new implementation
3234
3235var NODE_NAME = 0;
3236var NODE_HREF = 1;
3237var NODE_GROUP = 2;
3238var NODE_TAGS = 3;
3239var NODE_CHILDREN = 4;
3240
3241function init_google_navtree2(navtree_id, data)
3242{
3243 var $containerUl = $("#"+navtree_id);
3244 for (var i in data) {
3245 var node_data = data[i];
3246 $containerUl.append(new_google_node2(node_data));
3247 }
3248
3249 // Make all third-generation list items 'sticky' to prevent them from collapsing
3250 $containerUl.find('li li li.nav-section').addClass('sticky');
3251
3252 initExpandableNavItems("#"+navtree_id);
3253}
3254
3255function new_google_node2(node_data)
3256{
3257 var linkText = node_data[NODE_NAME];
3258 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3259 linkText = linkText.substr(19, linkText.length);
3260 }
3261 var $li = $('<li>');
3262 var $a;
3263 if (node_data[NODE_HREF] != null) {
3264 $a = $('<a href="' + toRoot + node_data[NODE_HREF] + '" title="' + linkText + '" >'
3265 + linkText + '</a>');
3266 } else {
3267 $a = $('<a href="#" onclick="return false;" title="' + linkText + '" >'
3268 + linkText + '/</a>');
3269 }
3270 var $childUl = $('<ul>');
3271 if (node_data[NODE_CHILDREN] != null) {
3272 $li.addClass("nav-section");
3273 $a = $('<div class="nav-section-header">').append($a);
3274 if (node_data[NODE_HREF] == null) $a.addClass('empty');
3275
3276 for (var i in node_data[NODE_CHILDREN]) {
3277 var child_node_data = node_data[NODE_CHILDREN][i];
3278 $childUl.append(new_google_node2(child_node_data));
3279 }
3280 $li.append($childUl);
3281 }
3282 $li.prepend($a);
3283
3284 return $li;
3285}
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297function showGoogleRefTree() {
3298 init_default_google_navtree(toRoot);
3299 init_default_gcm_navtree(toRoot);
3300}
3301
3302function init_default_google_navtree(toroot) {
3303 // load json file for navtree data
3304 $.getScript(toRoot + 'gms_navtree_data.js', function(data, textStatus, jqxhr) {
3305 // when the file is loaded, initialize the tree
3306 if(jqxhr.status === 200) {
3307 init_google_navtree("gms-tree-list", toroot, GMS_NAVTREE_DATA);
3308 highlightSidenav();
3309 resizeNav();
3310 }
3311 });
3312}
3313
3314function init_default_gcm_navtree(toroot) {
3315 // load json file for navtree data
3316 $.getScript(toRoot + 'gcm_navtree_data.js', function(data, textStatus, jqxhr) {
3317 // when the file is loaded, initialize the tree
3318 if(jqxhr.status === 200) {
3319 init_google_navtree("gcm-tree-list", toroot, GCM_NAVTREE_DATA);
3320 highlightSidenav();
3321 resizeNav();
3322 }
3323 });
3324}
3325
3326function showSamplesRefTree() {
3327 init_default_samples_navtree(toRoot);
3328}
3329
3330function init_default_samples_navtree(toroot) {
3331 // load json file for navtree data
3332 $.getScript(toRoot + 'samples_navtree_data.js', function(data, textStatus, jqxhr) {
3333 // when the file is loaded, initialize the tree
3334 if(jqxhr.status === 200) {
3335 // hack to remove the "about the samples" link then put it back in
3336 // after we nuke the list to remove the dummy static list of samples
3337 var $firstLi = $("#nav.samples-nav > li:first-child").clone();
3338 $("#nav.samples-nav").empty();
3339 $("#nav.samples-nav").append($firstLi);
3340
3341 init_google_navtree2("nav.samples-nav", SAMPLES_NAVTREE_DATA);
3342 highlightSidenav();
3343 resizeNav();
3344 if ($("#jd-content #samples").length) {
3345 showSamples();
3346 }
3347 }
3348 });
3349}
3350
3351/* TOGGLE INHERITED MEMBERS */
3352
3353/* Toggle an inherited class (arrow toggle)
3354 * @param linkObj The link that was clicked.
3355 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3356 * 'null' to simply toggle.
3357 */
3358function toggleInherited(linkObj, expand) {
3359 var base = linkObj.getAttribute("id");
3360 var list = document.getElementById(base + "-list");
3361 var summary = document.getElementById(base + "-summary");
3362 var trigger = document.getElementById(base + "-trigger");
3363 var a = $(linkObj);
3364 if ( (expand == null && a.hasClass("closed")) || expand ) {
3365 list.style.display = "none";
3366 summary.style.display = "block";
3367 trigger.src = toRoot + "assets/images/triangle-opened.png";
3368 a.removeClass("closed");
3369 a.addClass("opened");
3370 } else if ( (expand == null && a.hasClass("opened")) || (expand == false) ) {
3371 list.style.display = "block";
3372 summary.style.display = "none";
3373 trigger.src = toRoot + "assets/images/triangle-closed.png";
3374 a.removeClass("opened");
3375 a.addClass("closed");
3376 }
3377 return false;
3378}
3379
3380/* Toggle all inherited classes in a single table (e.g. all inherited methods)
3381 * @param linkObj The link that was clicked.
3382 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3383 * 'null' to simply toggle.
3384 */
3385function toggleAllInherited(linkObj, expand) {
3386 var a = $(linkObj);
3387 var table = $(a.parent().parent().parent()); // ugly way to get table/tbody
3388 var expandos = $(".jd-expando-trigger", table);
3389 if ( (expand == null && a.text() == "[Expand]") || expand ) {
3390 expandos.each(function(i) {
3391 toggleInherited(this, true);
3392 });
3393 a.text("[Collapse]");
3394 } else if ( (expand == null && a.text() == "[Collapse]") || (expand == false) ) {
3395 expandos.each(function(i) {
3396 toggleInherited(this, false);
3397 });
3398 a.text("[Expand]");
3399 }
3400 return false;
3401}
3402
3403/* Toggle all inherited members in the class (link in the class title)
3404 */
3405function toggleAllClassInherited() {
3406 var a = $("#toggleAllClassInherited"); // get toggle link from class title
3407 var toggles = $(".toggle-all", $("#body-content"));
3408 if (a.text() == "[Expand All]") {
3409 toggles.each(function(i) {
3410 toggleAllInherited(this, true);
3411 });
3412 a.text("[Collapse All]");
3413 } else {
3414 toggles.each(function(i) {
3415 toggleAllInherited(this, false);
3416 });
3417 a.text("[Expand All]");
3418 }
3419 return false;
3420}
3421
3422/* Expand all inherited members in the class. Used when initiating page search */
3423function ensureAllInheritedExpanded() {
3424 var toggles = $(".toggle-all", $("#body-content"));
3425 toggles.each(function(i) {
3426 toggleAllInherited(this, true);
3427 });
3428 $("#toggleAllClassInherited").text("[Collapse All]");
3429}
3430
3431
3432/* HANDLE KEY EVENTS
3433 * - Listen for Ctrl+F (Cmd on Mac) and expand all inherited members (to aid page search)
3434 */
3435var agent = navigator['userAgent'].toLowerCase();
3436var mac = agent.indexOf("macintosh") != -1;
3437
3438$(document).keydown( function(e) {
3439var control = mac ? e.metaKey && !e.ctrlKey : e.ctrlKey; // get ctrl key
3440 if (control && e.which == 70) { // 70 is "F"
3441 ensureAllInheritedExpanded();
3442 }
3443});
3444
3445
3446
3447
3448
3449
3450/* On-demand functions */
3451
3452/** Move sample code line numbers out of PRE block and into non-copyable column */
3453function initCodeLineNumbers() {
3454 var numbers = $("#codesample-block a.number");
3455 if (numbers.length) {
3456 $("#codesample-line-numbers").removeClass("hidden").append(numbers);
3457 }
3458
3459 $(document).ready(function() {
3460 // select entire line when clicked
3461 $("span.code-line").click(function() {
3462 if (!shifted) {
3463 selectText(this);
3464 }
3465 });
3466 // invoke line link on double click
3467 $(".code-line").dblclick(function() {
3468 document.location.hash = $(this).attr('id');
3469 });
3470 // highlight the line when hovering on the number
3471 $("#codesample-line-numbers a.number").mouseover(function() {
3472 var id = $(this).attr('href');
3473 $(id).css('background','#e7e7e7');
3474 });
3475 $("#codesample-line-numbers a.number").mouseout(function() {
3476 var id = $(this).attr('href');
3477 $(id).css('background','none');
3478 });
3479 });
3480}
3481
3482// create SHIFT key binder to avoid the selectText method when selecting multiple lines
3483var shifted = false;
3484$(document).bind('keyup keydown', function(e){shifted = e.shiftKey; return true;} );
3485
3486// courtesy of jasonedelman.com
3487function selectText(element) {
3488 var doc = document
3489 , range, selection
3490 ;
3491 if (doc.body.createTextRange) { //ms
3492 range = doc.body.createTextRange();
3493 range.moveToElementText(element);
3494 range.select();
3495 } else if (window.getSelection) { //all others
3496 selection = window.getSelection();
3497 range = doc.createRange();
3498 range.selectNodeContents(element);
3499 selection.removeAllRanges();
3500 selection.addRange(range);
3501 }
3502}
3503
3504
3505
3506
3507/** Display links and other information about samples that match the
3508 group specified by the URL */
3509function showSamples() {
3510 var group = $("#samples").attr('class');
3511 $("#samples").html("<p>Here are some samples for <b>" + group + "</b> apps:</p>");
3512
3513 var $ul = $("<ul>");
3514 $selectedLi = $("#nav li.selected");
3515
3516 $selectedLi.children("ul").children("li").each(function() {
3517 var $li = $("<li>").append($(this).find("a").first().clone());
3518 $ul.append($li);
3519 });
3520
3521 $("#samples").append($ul);
3522
3523}
Dirk Dougherty08032402014-02-15 10:14:35 -08003524
3525
3526
3527/* ########################################################## */
3528/* ################### RESOURCE CARDS ##################### */
3529/* ########################################################## */
3530
3531/** Handle resource queries, collections, and grids (sections). Requires
3532 jd_tag_helpers.js and the *_unified_data.js to be loaded. */
3533
3534(function() {
3535 // Prevent the same resource from being loaded more than once per page.
3536 var addedPageResources = {};
3537
3538 $(document).ready(function() {
3539 $('.resource-widget').each(function() {
3540 initResourceWidget(this);
3541 });
3542
Dirk Dougherty318fb972014-04-08 18:46:53 -07003543 /* Pass the line height to ellipsisfade() to adjust the height of the
3544 text container to show the max number of lines possible, without
3545 showing lines that are cut off. This works with the css ellipsis
3546 classes to fade last text line and apply an ellipsis char. */
3547
Dirk Dougherty29e93432015-05-05 18:17:13 -07003548 //card text currently uses 15px line height.
3549 var lineHeight = 15;
Dirk Dougherty318fb972014-04-08 18:46:53 -07003550 $('.card-info .text').ellipsisfade(lineHeight);
Dirk Dougherty08032402014-02-15 10:14:35 -08003551 });
3552
3553 /*
3554 Three types of resource layouts:
3555 Flow - Uses a fixed row-height flow using float left style.
Dirk Dougherty318fb972014-04-08 18:46:53 -07003556 Carousel - Single card slideshow all same dimension absolute.
Dirk Dougherty08032402014-02-15 10:14:35 -08003557 Stack - Uses fixed columns and flexible element height.
3558 */
3559 function initResourceWidget(widget) {
3560 var $widget = $(widget);
3561 var isFlow = $widget.hasClass('resource-flow-layout'),
3562 isCarousel = $widget.hasClass('resource-carousel-layout'),
3563 isStack = $widget.hasClass('resource-stack-layout');
3564
Dirk Dougherty29e93432015-05-05 18:17:13 -07003565 // find size of widget by pulling out its class name
3566 var sizeCols = 1;
Dirk Dougherty08032402014-02-15 10:14:35 -08003567 var m = $widget.get(0).className.match(/\bcol-(\d+)\b/);
Dirk Dougherty29e93432015-05-05 18:17:13 -07003568 if (m) {
3569 sizeCols = parseInt(m[1], 10);
Dirk Dougherty08032402014-02-15 10:14:35 -08003570 }
3571
3572 var opts = {
3573 cardSizes: ($widget.data('cardsizes') || '').split(','),
3574 maxResults: parseInt($widget.data('maxresults') || '100', 10),
3575 itemsPerPage: $widget.data('itemsperpage'),
3576 sortOrder: $widget.data('sortorder'),
3577 query: $widget.data('query'),
3578 section: $widget.data('section'),
Dirk Dougherty29e93432015-05-05 18:17:13 -07003579 sizeCols: sizeCols,
Dirk Doughertyff233cc2015-05-04 14:37:05 -07003580 /* Added by LFL 6/6/14 */
3581 resourceStyle: $widget.data('resourcestyle') || 'card',
3582 stackSort: $widget.data('stacksort') || 'true'
Dirk Dougherty08032402014-02-15 10:14:35 -08003583 };
3584
3585 // run the search for the set of resources to show
3586
3587 var resources = buildResourceList(opts);
3588
3589 if (isFlow) {
3590 drawResourcesFlowWidget($widget, opts, resources);
3591 } else if (isCarousel) {
3592 drawResourcesCarouselWidget($widget, opts, resources);
3593 } else if (isStack) {
Dirk Doughertyff233cc2015-05-04 14:37:05 -07003594 /* Looks like this got removed and is not used, so repurposing for the
3595 homepage style layout.
3596 Modified by LFL 6/6/14
3597 */
3598 //var sections = buildSectionList(opts);
Dirk Dougherty08032402014-02-15 10:14:35 -08003599 opts['numStacks'] = $widget.data('numstacks');
Dirk Doughertyff233cc2015-05-04 14:37:05 -07003600 drawResourcesStackWidget($widget, opts, resources/*, sections*/);
Dirk Dougherty08032402014-02-15 10:14:35 -08003601 }
3602 }
3603
3604 /* Initializes a Resource Carousel Widget */
3605 function drawResourcesCarouselWidget($widget, opts, resources) {
3606 $widget.empty();
Dirk Dougherty29e93432015-05-05 18:17:13 -07003607 var plusone = true; //always show plusone on carousel
Dirk Dougherty08032402014-02-15 10:14:35 -08003608
3609 $widget.addClass('resource-card slideshow-container')
3610 .append($('<a>').addClass('slideshow-prev').text('Prev'))
3611 .append($('<a>').addClass('slideshow-next').text('Next'));
3612
3613 var css = { 'width': $widget.width() + 'px',
3614 'height': $widget.height() + 'px' };
3615
3616 var $ul = $('<ul>');
3617
3618 for (var i = 0; i < resources.length; ++i) {
Dirk Dougherty08032402014-02-15 10:14:35 -08003619 var $card = $('<a>')
Dirk Doughertyff233cc2015-05-04 14:37:05 -07003620 .attr('href', cleanUrl(resources[i].url))
Dirk Dougherty318fb972014-04-08 18:46:53 -07003621 .decorateResourceCard(resources[i],plusone);
Dirk Dougherty08032402014-02-15 10:14:35 -08003622
3623 $('<li>').css(css)
3624 .append($card)
3625 .appendTo($ul);
3626 }
3627
3628 $('<div>').addClass('frame')
3629 .append($ul)
3630 .appendTo($widget);
3631
3632 $widget.dacSlideshow({
3633 auto: true,
3634 btnPrev: '.slideshow-prev',
3635 btnNext: '.slideshow-next'
3636 });
3637 };
3638
Dirk Doughertyff233cc2015-05-04 14:37:05 -07003639 /* Initializes a Resource Card Stack Widget (column-based layout)
3640 Modified by LFL 6/6/14
3641 */
Dirk Dougherty08032402014-02-15 10:14:35 -08003642 function drawResourcesStackWidget($widget, opts, resources, sections) {
3643 // Don't empty widget, grab all items inside since they will be the first
3644 // items stacked, followed by the resource query
Dirk Dougherty29e93432015-05-05 18:17:13 -07003645 var plusone = true; //by default show plusone on section cards
Dirk Dougherty08032402014-02-15 10:14:35 -08003646 var cards = $widget.find('.resource-card').detach().toArray();
3647 var numStacks = opts.numStacks || 1;
3648 var $stacks = [];
3649 var urlString;
3650
3651 for (var i = 0; i < numStacks; ++i) {
3652 $stacks[i] = $('<div>').addClass('resource-card-stack')
3653 .appendTo($widget);
3654 }
3655
3656 var sectionResources = [];
3657
3658 // Extract any subsections that are actually resource cards
Dirk Doughertyff233cc2015-05-04 14:37:05 -07003659 if (sections) {
3660 for (var i = 0; i < sections.length; ++i) {
3661 if (!sections[i].sections || !sections[i].sections.length) {
3662 // Render it as a resource card
3663 sectionResources.push(
3664 $('<a>')
3665 .addClass('resource-card section-card')
3666 .attr('href', cleanUrl(sections[i].resource.url))
3667 .decorateResourceCard(sections[i].resource,plusone)[0]
3668 );
Dirk Dougherty08032402014-02-15 10:14:35 -08003669
Dirk Doughertyff233cc2015-05-04 14:37:05 -07003670 } else {
3671 cards.push(
3672 $('<div>')
3673 .addClass('resource-card section-card-menu')
3674 .decorateResourceSection(sections[i],plusone)[0]
3675 );
3676 }
Dirk Dougherty08032402014-02-15 10:14:35 -08003677 }
3678 }
3679
3680 cards = cards.concat(sectionResources);
3681
3682 for (var i = 0; i < resources.length; ++i) {
Dirk Doughertyff233cc2015-05-04 14:37:05 -07003683 var $card = createResourceElement(resources[i], opts);
3684
3685 if (opts.resourceStyle.indexOf('related') > -1) {
3686 $card.addClass('related-card');
3687 }
Dirk Dougherty08032402014-02-15 10:14:35 -08003688
3689 cards.push($card[0]);
3690 }
3691
Dirk Doughertyff233cc2015-05-04 14:37:05 -07003692 if (opts.stackSort != 'false') {
3693 for (var i = 0; i < cards.length; ++i) {
3694 // Find the stack with the shortest height, but give preference to
3695 // left to right order.
3696 var minHeight = $stacks[0].height();
3697 var minIndex = 0;
Dirk Dougherty08032402014-02-15 10:14:35 -08003698
Dirk Doughertyff233cc2015-05-04 14:37:05 -07003699 for (var j = 1; j < numStacks; ++j) {
3700 var height = $stacks[j].height();
3701 if (height < minHeight - 45) {
3702 minHeight = height;
3703 minIndex = j;
3704 }
Dirk Dougherty08032402014-02-15 10:14:35 -08003705 }
Dirk Dougherty08032402014-02-15 10:14:35 -08003706
Dirk Doughertyff233cc2015-05-04 14:37:05 -07003707 $stacks[minIndex].append($(cards[i]));
3708 }
Dirk Dougherty08032402014-02-15 10:14:35 -08003709 }
3710
3711 };
3712
Dirk Doughertyff233cc2015-05-04 14:37:05 -07003713 /*
3714 Create a resource card using the given resource object and a list of html
3715 configured options. Returns a jquery object containing the element.
3716 */
3717 function createResourceElement(resource, opts, plusone) {
3718 var $el;
3719
3720 // The difference here is that generic cards are not entirely clickable
3721 // so its a div instead of an a tag, also the generic one is not given
3722 // the resource-card class so it appears with a transparent background
3723 // and can be styled in whatever way the css setup.
3724 if (opts.resourceStyle == 'generic') {
3725 $el = $('<div>')
3726 .addClass('resource')
3727 .attr('href', cleanUrl(resource.url))
3728 .decorateResource(resource, opts);
3729 } else {
3730 var cls = 'resource resource-card';
3731
3732 $el = $('<a>')
3733 .addClass(cls)
3734 .attr('href', cleanUrl(resource.url))
3735 .decorateResourceCard(resource, plusone);
3736 }
3737
3738 return $el;
3739 }
3740
Dirk Dougherty08032402014-02-15 10:14:35 -08003741 /* Initializes a flow widget, see distribute.scss for generating accompanying css */
3742 function drawResourcesFlowWidget($widget, opts, resources) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003743 $widget.empty();
Dirk Dougherty08032402014-02-15 10:14:35 -08003744 var cardSizes = opts.cardSizes || ['6x6'];
3745 var i = 0, j = 0;
Dirk Dougherty29e93432015-05-05 18:17:13 -07003746 var plusone = true; // by default show plusone on resource cards
Dirk Dougherty08032402014-02-15 10:14:35 -08003747
3748 while (i < resources.length) {
3749 var cardSize = cardSizes[j++ % cardSizes.length];
3750 cardSize = cardSize.replace(/^\s+|\s+$/,'');
Dirk Dougherty29e93432015-05-05 18:17:13 -07003751 // Some card sizes do not get a plusone button, such as where space is constrained
3752 // or for cards commonly embedded in docs (to improve overall page speed).
3753 plusone = !((cardSize == "6x2") || (cardSize == "6x3") ||
3754 (cardSize == "9x2") || (cardSize == "9x3") ||
3755 (cardSize == "12x2") || (cardSize == "12x3"));
Dirk Dougherty08032402014-02-15 10:14:35 -08003756
3757 // A stack has a third dimension which is the number of stacked items
3758 var isStack = cardSize.match(/(\d+)x(\d+)x(\d+)/);
3759 var stackCount = 0;
3760 var $stackDiv = null;
3761
3762 if (isStack) {
3763 // Create a stack container which should have the dimensions defined
3764 // by the product of the items inside.
3765 $stackDiv = $('<div>').addClass('resource-card-stack resource-card-' + isStack[1]
Dirk Dougherty29e93432015-05-05 18:17:13 -07003766 + 'x' + isStack[2] * isStack[3]) .appendTo($widget);
Dirk Dougherty08032402014-02-15 10:14:35 -08003767 }
3768
3769 // Build each stack item or just a single item
3770 do {
3771 var resource = resources[i];
Dirk Doughertyff233cc2015-05-04 14:37:05 -07003772
3773 var $card = createResourceElement(resources[i], opts, plusone);
3774
3775 $card.addClass('resource-card-' + cardSize +
3776 ' resource-card-' + resource.type);
Dirk Dougherty08032402014-02-15 10:14:35 -08003777
3778 if (isStack) {
3779 $card.addClass('resource-card-' + isStack[1] + 'x' + isStack[2]);
3780 if (++stackCount == parseInt(isStack[3])) {
3781 $card.addClass('resource-card-row-stack-last');
3782 stackCount = 0;
3783 }
3784 } else {
3785 stackCount = 0;
3786 }
3787
Dirk Dougherty29e93432015-05-05 18:17:13 -07003788 $card.appendTo($stackDiv || $widget);
Dirk Dougherty08032402014-02-15 10:14:35 -08003789
3790 } while (++i < resources.length && stackCount > 0);
3791 }
3792 }
3793
3794 /* Build a site map of resources using a section as a root. */
3795 function buildSectionList(opts) {
3796 if (opts.section && SECTION_BY_ID[opts.section]) {
3797 return SECTION_BY_ID[opts.section].sections || [];
3798 }
3799 return [];
3800 }
3801
3802 function buildResourceList(opts) {
3803 var maxResults = opts.maxResults || 100;
3804
3805 var query = opts.query || '';
3806 var expressions = parseResourceQuery(query);
3807 var addedResourceIndices = {};
3808 var results = [];
3809
3810 for (var i = 0; i < expressions.length; i++) {
3811 var clauses = expressions[i];
3812
3813 // build initial set of resources from first clause
3814 var firstClause = clauses[0];
3815 var resources = [];
3816 switch (firstClause.attr) {
3817 case 'type':
3818 resources = ALL_RESOURCES_BY_TYPE[firstClause.value];
3819 break;
3820 case 'lang':
3821 resources = ALL_RESOURCES_BY_LANG[firstClause.value];
3822 break;
3823 case 'tag':
3824 resources = ALL_RESOURCES_BY_TAG[firstClause.value];
3825 break;
3826 case 'collection':
3827 var urls = RESOURCE_COLLECTIONS[firstClause.value].resources || [];
3828 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3829 break;
3830 case 'section':
3831 var urls = SITE_MAP[firstClause.value].sections || [];
3832 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3833 break;
3834 }
Scott Main20cf2a92014-04-02 21:57:20 -07003835 // console.log(firstClause.attr + ':' + firstClause.value);
Dirk Dougherty08032402014-02-15 10:14:35 -08003836 resources = resources || [];
3837
3838 // use additional clauses to filter corpus
3839 if (clauses.length > 1) {
3840 var otherClauses = clauses.slice(1);
3841 resources = resources.filter(getResourceMatchesClausesFilter(otherClauses));
3842 }
3843
3844 // filter out resources already added
3845 if (i > 1) {
3846 resources = resources.filter(getResourceNotAlreadyAddedFilter(addedResourceIndices));
3847 }
3848
3849 // add to list of already added indices
3850 for (var j = 0; j < resources.length; j++) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003851 // console.log(resources[j].title);
3852 addedResourceIndices[resources[j].index] = 1;
Dirk Dougherty08032402014-02-15 10:14:35 -08003853 }
3854
3855 // concat to final results list
3856 results = results.concat(resources);
3857 }
3858
3859 if (opts.sortOrder && results.length) {
3860 var attr = opts.sortOrder;
3861
3862 if (opts.sortOrder == 'random') {
3863 var i = results.length, j, temp;
3864 while (--i) {
3865 j = Math.floor(Math.random() * (i + 1));
3866 temp = results[i];
3867 results[i] = results[j];
3868 results[j] = temp;
3869 }
3870 } else {
3871 var desc = attr.charAt(0) == '-';
3872 if (desc) {
3873 attr = attr.substring(1);
3874 }
3875 results = results.sort(function(x,y) {
3876 return (desc ? -1 : 1) * (parseInt(x[attr], 10) - parseInt(y[attr], 10));
3877 });
3878 }
3879 }
3880
3881 results = results.filter(getResourceNotAlreadyAddedFilter(addedPageResources));
3882 results = results.slice(0, maxResults);
3883
3884 for (var j = 0; j < results.length; ++j) {
3885 addedPageResources[results[j].index] = 1;
3886 }
3887
3888 return results;
3889 }
3890
3891
3892 function getResourceNotAlreadyAddedFilter(addedResourceIndices) {
3893 return function(resource) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003894 return !addedResourceIndices[resource.index];
Dirk Dougherty08032402014-02-15 10:14:35 -08003895 };
3896 }
3897
3898
3899 function getResourceMatchesClausesFilter(clauses) {
3900 return function(resource) {
3901 return doesResourceMatchClauses(resource, clauses);
3902 };
3903 }
3904
3905
3906 function doesResourceMatchClauses(resource, clauses) {
3907 for (var i = 0; i < clauses.length; i++) {
3908 var map;
3909 switch (clauses[i].attr) {
3910 case 'type':
3911 map = IS_RESOURCE_OF_TYPE[clauses[i].value];
3912 break;
3913 case 'lang':
3914 map = IS_RESOURCE_IN_LANG[clauses[i].value];
3915 break;
3916 case 'tag':
3917 map = IS_RESOURCE_TAGGED[clauses[i].value];
3918 break;
3919 }
3920
3921 if (!map || (!!clauses[i].negative ? map[resource.index] : !map[resource.index])) {
3922 return clauses[i].negative;
3923 }
3924 }
3925 return true;
3926 }
3927
Dirk Doughertyff233cc2015-05-04 14:37:05 -07003928 function cleanUrl(url)
3929 {
3930 if (url && url.indexOf('//') === -1) {
3931 url = toRoot + url;
3932 }
3933
3934 return url;
3935 }
3936
Dirk Dougherty08032402014-02-15 10:14:35 -08003937
3938 function parseResourceQuery(query) {
3939 // Parse query into array of expressions (expression e.g. 'tag:foo + type:video')
3940 var expressions = [];
3941 var expressionStrs = query.split(',') || [];
3942 for (var i = 0; i < expressionStrs.length; i++) {
3943 var expr = expressionStrs[i] || '';
3944
3945 // Break expression into clauses (clause e.g. 'tag:foo')
3946 var clauses = [];
3947 var clauseStrs = expr.split(/(?=[\+\-])/);
3948 for (var j = 0; j < clauseStrs.length; j++) {
3949 var clauseStr = clauseStrs[j] || '';
3950
3951 // Get attribute and value from clause (e.g. attribute='tag', value='foo')
3952 var parts = clauseStr.split(':');
3953 var clause = {};
3954
3955 clause.attr = parts[0].replace(/^\s+|\s+$/g,'');
3956 if (clause.attr) {
3957 if (clause.attr.charAt(0) == '+') {
3958 clause.attr = clause.attr.substring(1);
3959 } else if (clause.attr.charAt(0) == '-') {
3960 clause.negative = true;
3961 clause.attr = clause.attr.substring(1);
3962 }
3963 }
3964
3965 if (parts.length > 1) {
3966 clause.value = parts[1].replace(/^\s+|\s+$/g,'');
3967 }
3968
3969 clauses.push(clause);
3970 }
3971
3972 if (!clauses.length) {
3973 continue;
3974 }
3975
3976 expressions.push(clauses);
3977 }
3978
3979 return expressions;
3980 }
3981})();
3982
3983(function($) {
Dirk Doughertyff233cc2015-05-04 14:37:05 -07003984
3985 /*
3986 Utility method for creating dom for the description area of a card.
3987 Used in decorateResourceCard and decorateResource.
3988 */
3989 function buildResourceCardDescription(resource, plusone) {
3990 var $description = $('<div>').addClass('description ellipsis');
3991
3992 $description.append($('<div>').addClass('text').html(resource.summary));
3993
3994 if (resource.cta) {
3995 $description.append($('<a>').addClass('cta').html(resource.cta));
3996 }
3997
3998 if (plusone) {
3999 var plusurl = resource.url.indexOf("//") > -1 ? resource.url :
4000 "//developer.android.com/" + resource.url;
4001
4002 $description.append($('<div>').addClass('util')
4003 .append($('<div>').addClass('g-plusone')
4004 .attr('data-size', 'small')
4005 .attr('data-align', 'right')
4006 .attr('data-href', plusurl)));
4007 }
4008
4009 return $description;
4010 }
4011
4012
Dirk Dougherty08032402014-02-15 10:14:35 -08004013 /* Simple jquery function to create dom for a standard resource card */
Dirk Dougherty318fb972014-04-08 18:46:53 -07004014 $.fn.decorateResourceCard = function(resource,plusone) {
Dirk Dougherty08032402014-02-15 10:14:35 -08004015 var section = resource.group || resource.type;
Dirk Doughertyff233cc2015-05-04 14:37:05 -07004016 var imgUrl = resource.image ||
4017 'assets/images/resource-card-default-android.jpg';
4018
4019 if (imgUrl.indexOf('//') === -1) {
4020 imgUrl = toRoot + imgUrl;
Dirk Dougherty08032402014-02-15 10:14:35 -08004021 }
Dirk Doughertyff233cc2015-05-04 14:37:05 -07004022
4023 $('<div>').addClass('card-bg')
4024 .css('background-image', 'url(' + (imgUrl || toRoot +
4025 'assets/images/resource-card-default-android.jpg') + ')')
Dirk Dougherty08032402014-02-15 10:14:35 -08004026 .appendTo(this);
Dirk Doughertyff233cc2015-05-04 14:37:05 -07004027
4028 $('<div>').addClass('card-info' + (!resource.summary ? ' empty-desc' : ''))
4029 .append($('<div>').addClass('section').text(section))
4030 .append($('<div>').addClass('title').html(resource.title))
4031 .append(buildResourceCardDescription(resource, plusone))
4032 .appendTo(this);
Dirk Dougherty08032402014-02-15 10:14:35 -08004033
4034 return this;
4035 };
4036
4037 /* Simple jquery function to create dom for a resource section card (menu) */
Dirk Dougherty318fb972014-04-08 18:46:53 -07004038 $.fn.decorateResourceSection = function(section,plusone) {
Dirk Dougherty08032402014-02-15 10:14:35 -08004039 var resource = section.resource;
4040 //keep url clean for matching and offline mode handling
4041 var urlPrefix = resource.image.indexOf("//") > -1 ? "" : toRoot;
4042 var $base = $('<a>')
4043 .addClass('card-bg')
4044 .attr('href', resource.url)
4045 .append($('<div>').addClass('card-section-icon')
4046 .append($('<div>').addClass('icon'))
4047 .append($('<div>').addClass('section').html(resource.title)))
4048 .appendTo(this);
4049
4050 var $cardInfo = $('<div>').addClass('card-info').appendTo(this);
4051
4052 if (section.sections && section.sections.length) {
4053 // Recurse the section sub-tree to find a resource image.
4054 var stack = [section];
4055
4056 while (stack.length) {
4057 if (stack[0].resource.image) {
4058 $base.css('background-image', 'url(' + urlPrefix + stack[0].resource.image + ')');
4059 break;
4060 }
4061
4062 if (stack[0].sections) {
4063 stack = stack.concat(stack[0].sections);
4064 }
4065
4066 stack.shift();
4067 }
4068
4069 var $ul = $('<ul>')
4070 .appendTo($cardInfo);
4071
4072 var max = section.sections.length > 3 ? 3 : section.sections.length;
4073
4074 for (var i = 0; i < max; ++i) {
4075
4076 var subResource = section.sections[i];
Dirk Dougherty318fb972014-04-08 18:46:53 -07004077 if (!plusone) {
4078 $('<li>')
4079 .append($('<a>').attr('href', subResource.url)
4080 .append($('<div>').addClass('title').html(subResource.title))
4081 .append($('<div>').addClass('description ellipsis')
4082 .append($('<div>').addClass('text').html(subResource.summary))
4083 .append($('<div>').addClass('util'))))
4084 .appendTo($ul);
4085 } else {
4086 $('<li>')
4087 .append($('<a>').attr('href', subResource.url)
4088 .append($('<div>').addClass('title').html(subResource.title))
4089 .append($('<div>').addClass('description ellipsis')
4090 .append($('<div>').addClass('text').html(subResource.summary))
4091 .append($('<div>').addClass('util')
4092 .append($('<div>').addClass('g-plusone')
4093 .attr('data-size', 'small')
4094 .attr('data-align', 'right')
4095 .attr('data-href', resource.url)))))
4096 .appendTo($ul);
4097 }
Dirk Dougherty08032402014-02-15 10:14:35 -08004098 }
4099
4100 // Add a more row
4101 if (max < section.sections.length) {
4102 $('<li>')
4103 .append($('<a>').attr('href', resource.url)
4104 .append($('<div>')
4105 .addClass('title')
4106 .text('More')))
4107 .appendTo($ul);
4108 }
4109 } else {
4110 // No sub-resources, just render description?
4111 }
4112
4113 return this;
4114 };
Dirk Doughertyff233cc2015-05-04 14:37:05 -07004115
4116
4117
4118
4119 /* Render other types of resource styles that are not cards. */
4120 $.fn.decorateResource = function(resource, opts) {
4121 var imgUrl = resource.image ||
4122 'assets/images/resource-card-default-android.jpg';
4123 var linkUrl = resource.url;
4124
4125 if (imgUrl.indexOf('//') === -1) {
4126 imgUrl = toRoot + imgUrl;
4127 }
4128
4129 if (linkUrl && linkUrl.indexOf('//') === -1) {
4130 linkUrl = toRoot + linkUrl;
4131 }
4132
4133 $(this).append(
4134 $('<div>').addClass('image')
4135 .css('background-image', 'url(' + imgUrl + ')'),
4136 $('<div>').addClass('info').append(
4137 $('<h4>').addClass('title').html(resource.title),
4138 $('<p>').addClass('summary').html(resource.summary),
4139 $('<a>').attr('href', linkUrl).addClass('cta').html('Learn More')
4140 )
4141 );
4142
4143 return this;
4144 };
Dirk Dougherty08032402014-02-15 10:14:35 -08004145})(jQuery);
Dirk Doughertyff233cc2015-05-04 14:37:05 -07004146
4147
Dirk Dougherty318fb972014-04-08 18:46:53 -07004148/* Calculate the vertical area remaining */
Dirk Dougherty08032402014-02-15 10:14:35 -08004149(function($) {
Dirk Dougherty318fb972014-04-08 18:46:53 -07004150 $.fn.ellipsisfade= function(lineHeight) {
Dirk Dougherty08032402014-02-15 10:14:35 -08004151 this.each(function() {
4152 // get element text
4153 var $this = $(this);
Dirk Dougherty318fb972014-04-08 18:46:53 -07004154 var remainingHeight = $this.parent().parent().height();
4155 $this.parent().siblings().each(function ()
4156 {
Dirk Doughertyff233cc2015-05-04 14:37:05 -07004157 if ($(this).is(":visible")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07004158 var h = $(this).height();
Dirk Doughertyff233cc2015-05-04 14:37:05 -07004159 remainingHeight = remainingHeight - h;
4160 }
Dirk Dougherty318fb972014-04-08 18:46:53 -07004161 });
Dirk Dougherty08032402014-02-15 10:14:35 -08004162
Dirk Dougherty318fb972014-04-08 18:46:53 -07004163 adjustedRemainingHeight = ((remainingHeight)/lineHeight>>0)*lineHeight
4164 $this.parent().css({'height': adjustedRemainingHeight});
4165 $this.css({'height': "auto"});
Dirk Dougherty08032402014-02-15 10:14:35 -08004166 });
4167
4168 return this;
4169 };
Scott Main20cf2a92014-04-02 21:57:20 -07004170}) (jQuery);
Dirk Doughertyff233cc2015-05-04 14:37:05 -07004171
4172/*
4173 Fullscreen Carousel
4174
4175 The following allows for an area at the top of the page that takes over the
4176 entire browser height except for its top offset and an optional bottom
4177 padding specified as a data attribute.
4178
4179 HTML:
4180
4181 <div class="fullscreen-carousel">
4182 <div class="fullscreen-carousel-content">
4183 <!-- content here -->
4184 </div>
4185 <div class="fullscreen-carousel-content">
4186 <!-- content here -->
4187 </div>
4188
4189 etc ...
4190
4191 </div>
4192
4193 Control over how the carousel takes over the screen can mostly be defined in
4194 a css file. Setting min-height on the .fullscreen-carousel-content elements
4195 will prevent them from shrinking to far vertically when the browser is very
4196 short, and setting max-height on the .fullscreen-carousel itself will prevent
4197 the area from becoming to long in the case that the browser is stretched very
4198 tall.
4199
4200 There is limited functionality for having multiple sections since that request
4201 was removed, but it is possible to add .next-arrow and .prev-arrow elements to
4202 scroll between multiple content areas.
4203*/
4204
4205(function() {
4206 $(document).ready(function() {
4207 $('.fullscreen-carousel').each(function() {
4208 initWidget(this);
4209 });
4210 });
4211
4212 function initWidget(widget) {
4213 var $widget = $(widget);
4214
4215 var topOffset = $widget.offset().top;
4216 var padBottom = parseInt($widget.data('paddingbottom')) || 0;
4217 var maxHeight = 0;
4218 var minHeight = 0;
4219 var $content = $widget.find('.fullscreen-carousel-content');
4220 var $nextArrow = $widget.find('.next-arrow');
4221 var $prevArrow = $widget.find('.prev-arrow');
4222 var $curSection = $($content[0]);
4223
4224 if ($content.length <= 1) {
4225 $nextArrow.hide();
4226 $prevArrow.hide();
4227 } else {
4228 $nextArrow.click(function() {
4229 var index = ($content.index($curSection) + 1);
4230 $curSection.hide();
4231 $curSection = $($content[index >= $content.length ? 0 : index]);
4232 $curSection.show();
4233 });
4234
4235 $prevArrow.click(function() {
4236 var index = ($content.index($curSection) - 1);
4237 $curSection.hide();
4238 $curSection = $($content[index < 0 ? $content.length - 1 : 0]);
4239 $curSection.show();
4240 });
4241 }
4242
4243 // Just hide all content sections except first.
4244 $content.each(function(index) {
4245 if ($(this).height() > minHeight) minHeight = $(this).height();
4246 $(this).css({position: 'absolute', display: index > 0 ? 'none' : ''});
4247 });
4248
4249 // Register for changes to window size, and trigger.
4250 $(window).resize(resizeWidget);
4251 resizeWidget();
4252
4253 function resizeWidget() {
4254 var height = $(window).height() - topOffset - padBottom;
4255 $widget.width($(window).width());
4256 $widget.height(height < minHeight ? minHeight :
4257 (maxHeight && height > maxHeight ? maxHeight : height));
4258 }
4259 }
4260})();
4261
4262
4263
4264
4265
4266/*
4267 Tab Carousel
4268
4269 The following allows tab widgets to be installed via the html below. Each
4270 tab content section should have a data-tab attribute matching one of the
4271 nav items'. Also each tab content section should have a width matching the
4272 tab carousel.
4273
4274 HTML:
4275
4276 <div class="tab-carousel">
4277 <ul class="tab-nav">
4278 <li><a href="#" data-tab="handsets">Handsets</a>
4279 <li><a href="#" data-tab="wearable">Wearable</a>
4280 <li><a href="#" data-tab="tv">TV</a>
4281 </ul>
4282
4283 <div class="tab-carousel-content">
4284 <div data-tab="handsets">
4285 <!--Full width content here-->
4286 </div>
4287
4288 <div data-tab="wearable">
4289 <!--Full width content here-->
4290 </div>
4291
4292 <div data-tab="tv">
4293 <!--Full width content here-->
4294 </div>
4295 </div>
4296 </div>
4297
4298*/
4299(function() {
4300 $(document).ready(function() {
4301 $('.tab-carousel').each(function() {
4302 initWidget(this);
4303 });
4304 });
4305
4306 function initWidget(widget) {
4307 var $widget = $(widget);
4308 var $nav = $widget.find('.tab-nav');
4309 var $anchors = $nav.find('[data-tab]');
4310 var $li = $nav.find('li');
4311 var $contentContainer = $widget.find('.tab-carousel-content');
4312 var $tabs = $contentContainer.find('[data-tab]');
4313 var $curTab = $($tabs[0]); // Current tab is first tab.
4314 var width = $widget.width();
4315
4316 // Setup nav interactivity.
4317 $anchors.click(function(evt) {
4318 evt.preventDefault();
4319 var query = '[data-tab=' + $(this).data('tab') + ']';
4320 transitionWidget($tabs.filter(query));
4321 });
4322
4323 // Add highlight for navigation on first item.
4324 var $highlight = $('<div>').addClass('highlight')
4325 .css({left:$li.position().left + 'px', width:$li.outerWidth() + 'px'})
4326 .appendTo($nav);
4327
4328 // Store height since we will change contents to absolute.
4329 $contentContainer.height($contentContainer.height());
4330
4331 // Absolutely position tabs so they're ready for transition.
4332 $tabs.each(function(index) {
4333 $(this).css({position: 'absolute', left: index > 0 ? width + 'px' : '0'});
4334 });
4335
4336 function transitionWidget($toTab) {
4337 if (!$curTab.is($toTab)) {
4338 var curIndex = $tabs.index($curTab[0]);
4339 var toIndex = $tabs.index($toTab[0]);
4340 var dir = toIndex > curIndex ? 1 : -1;
4341
4342 // Animate content sections.
4343 $toTab.css({left:(width * dir) + 'px'});
4344 $curTab.animate({left:(width * -dir) + 'px'});
4345 $toTab.animate({left:'0'});
4346
4347 // Animate navigation highlight.
4348 $highlight.animate({left:$($li[toIndex]).position().left + 'px',
4349 width:$($li[toIndex]).outerWidth() + 'px'})
4350
4351 // Store new current section.
4352 $curTab = $toTab;
4353 }
4354 }
4355 }
4356})();