blob: d88c0e62902dc60747c460e5a552d56a211b1e34 [file] [log] [blame]
var classesNav;
var devdocNav;
var sidenav;
var cookie_namespace = 'android_developer';
var NAV_PREF_TREE = "tree";
var NAV_PREF_PANELS = "panels";
var nav_pref;
var isMobile = false; // true if mobile, so we can adjust some layout
var mPagePath; // initialized in ready() function
var basePath = getBaseUri(location.pathname);
var SITE_ROOT = toRoot + basePath.substring(1,basePath.indexOf("/",1));
var GOOGLE_DATA; // combined data for google service apis, used for search suggest
// Ensure that all ajax getScript() requests allow caching
$.ajaxSetup({
cache: true
});
/****** ON LOAD SET UP STUFF *********/
$(document).ready(function() {
// show lang dialog if the URL includes /intl/
//if (location.pathname.substring(0,6) == "/intl/") {
// var lang = location.pathname.split('/')[2];
// if (lang != getLangPref()) {
// $("#langMessage a.yes").attr("onclick","changeLangPref('" + lang
// + "', true); $('#langMessage').hide(); return false;");
// $("#langMessage .lang." + lang).show();
// $("#langMessage").show();
// }
//}
// load json file for JD doc search suggestions
$.getScript(toRoot + 'jd_lists_unified.js');
// load json file for Android API search suggestions
$.getScript(toRoot + 'reference/lists.js');
// load json files for Google services API suggestions
$.getScript(toRoot + 'reference/gcm_lists.js', function(data, textStatus, jqxhr) {
// once the GCM json (GCM_DATA) is loaded, load the GMS json (GMS_DATA) and merge the data
if(jqxhr.status === 200) {
$.getScript(toRoot + 'reference/gms_lists.js', function(data, textStatus, jqxhr) {
if(jqxhr.status === 200) {
// combine GCM and GMS data
GOOGLE_DATA = GMS_DATA;
var start = GOOGLE_DATA.length;
for (var i=0; i<GCM_DATA.length; i++) {
GOOGLE_DATA.push({id:start+i, label:GCM_DATA[i].label,
link:GCM_DATA[i].link, type:GCM_DATA[i].type});
}
}
});
}
});
// setup keyboard listener for search shortcut
$('body').keyup(function(event) {
if (event.which == 191 && $(event.target).is(':not(:input)')) {
$('#search_autocomplete').focus();
}
});
// init the fullscreen toggle click event
$('#nav-swap .fullscreen').click(function(){
if ($(this).hasClass('disabled')) {
toggleFullscreen(true);
} else {
toggleFullscreen(false);
}
});
// initialize the divs with custom scrollbars
if (window.innerWidth >= 720) {
$('.scroll-pane').jScrollPane({verticalGutter: 0});
}
// set up the search close button
$('#search-close').on('click touchend', function() {
$searchInput = $('#search_autocomplete');
$searchInput.attr('value', '');
$(this).addClass("hide");
$("#search-container").removeClass('active');
$("#search_autocomplete").blur();
search_focus_changed($searchInput.get(), false);
hideResults();
});
//Set up search
$("#search_autocomplete").focus(function() {
$("#search-container").addClass('active');
})
$("#search-container").on('mouseover touchend', function(e) {
if ($(e.target).is('#search-close')) { return; }
$("#search-container").addClass('active');
$("#search_autocomplete").focus();
})
$("#search-container").mouseout(function() {
if ($("#search_autocomplete").is(":focus")) return;
if ($("#search_autocomplete").val() == '') {
setTimeout(function(){
$("#search-container").removeClass('active');
$("#search_autocomplete").blur();
},250);
}
})
$("#search_autocomplete").blur(function() {
if ($("#search_autocomplete").val() == '') {
$("#search-container").removeClass('active');
}
})
// prep nav expandos
var pagePath = document.location.pathname;
// account for intl docs by removing the intl/*/ path
if (pagePath.indexOf("/intl/") == 0) {
pagePath = pagePath.substr(pagePath.indexOf("/",6)); // start after intl/ to get last /
}
if (pagePath.indexOf(SITE_ROOT) == 0) {
if (pagePath == '' || pagePath.charAt(pagePath.length - 1) == '/') {
pagePath += 'index.html';
}
}
// Need a copy of the pagePath before it gets changed in the next block;
// it's needed to perform proper tab highlighting in offline docs (see rootDir below)
var pagePathOriginal = pagePath;
if (SITE_ROOT.match(/\.\.\//) || SITE_ROOT == '') {
// If running locally, SITE_ROOT will be a relative path, so account for that by
// finding the relative URL to this page. This will allow us to find links on the page
// leading back to this page.
var pathParts = pagePath.split('/');
var relativePagePathParts = [];
var upDirs = (SITE_ROOT.match(/(\.\.\/)+/) || [''])[0].length / 3;
for (var i = 0; i < upDirs; i++) {
relativePagePathParts.push('..');
}
for (var i = 0; i < upDirs; i++) {
relativePagePathParts.push(pathParts[pathParts.length - (upDirs - i) - 1]);
}
relativePagePathParts.push(pathParts[pathParts.length - 1]);
pagePath = relativePagePathParts.join('/');
} else {
// Otherwise the page path is already an absolute URL
}
// Highlight the header tabs...
// highlight Design tab
var urlSegments = pagePathOriginal.split('/');
var navEl = $(".dac-nav-list");
var subNavEl = navEl.find(".dac-nav-secondary");
var parentNavEl;
if ($("body").hasClass("design")) {
navEl.find("> li.design > a").addClass("selected");
// highlight About tabs
} else if ($("body").hasClass("about")) {
if (urlSegments[1] == "about" || urlSegments[1] == "wear" || urlSegments[1] == "tv" || urlSegments[1] == "auto") {
navEl.find("> li.home > a").addClass('has-subnav');
subNavEl.find("li." + urlSegments[1] + " > a").addClass("selected");
} else {
navEl.find("> li.home > a").addClass('selected');
}
// highlight NDK tabs
} else if ($("body").hasClass("ndk")) {
parentNavEl = navEl.find("> li.ndk > a");
parentNavEl.addClass('has-subnav');
if ($("body").hasClass("guide")) {
navEl.find("> li.guides > a").addClass("selected ndk");
} else if ($("body").hasClass("reference")) {
navEl.find("> li.reference > a").addClass("selected ndk");
} else if ($("body").hasClass("samples")) {
navEl.find("> li.samples > a").addClass("selected ndk");
} else if ($("body").hasClass("downloads")) {
navEl.find("> li.downloads > a").addClass("selected ndk");
}
// highlight Develop tab
} else if ($("body").hasClass("develop") || $("body").hasClass("google")) {
parentNavEl = navEl.find("> li.develop > a");
parentNavEl.addClass('has-subnav');
// In Develop docs, also highlight appropriate sub-tab
if (urlSegments[1] == "training") {
subNavEl.find("li.training > a").addClass("selected");
} else if (urlSegments[1] == "guide") {
subNavEl.find("li.guide > a").addClass("selected");
} else if (urlSegments[1] == "reference") {
// If the root is reference, but page is also part of Google Services, select Google
if ($("body").hasClass("google")) {
subNavEl.find("li.google > a").addClass("selected");
} else {
subNavEl.find("li.reference > a").addClass("selected");
}
} else if ((urlSegments[1] == "tools") || (urlSegments[1] == "sdk")) {
subNavEl.find("li.tools > a").addClass("selected");
} else if ($("body").hasClass("google")) {
subNavEl.find("li.google > a").addClass("selected");
} else if ($("body").hasClass("samples")) {
subNavEl.find("li.samples > a").addClass("selected");
} else if ($("body").hasClass("preview")) {
subNavEl.find("li.preview > a").addClass("selected");
} else {
parentNavEl.removeClass('has-subnav').addClass("selected");
}
// highlight Distribute tab
} else if ($("body").hasClass("distribute")) {
parentNavEl = navEl.find("> li.distribute > a");
parentNavEl.addClass('has-subnav');
if (urlSegments[2] == "users") {
subNavEl.find("li.users > a").addClass("selected");
} else if (urlSegments[2] == "engage") {
subNavEl.find("li.engage > a").addClass("selected");
} else if (urlSegments[2] == "monetize") {
subNavEl.find("li.monetize > a").addClass("selected");
} else if (urlSegments[2] == "analyze") {
subNavEl.find("li.analyze > a").addClass("selected");
} else if (urlSegments[2] == "tools") {
subNavEl.find("li.essentials > a").addClass("selected");
} else if (urlSegments[2] == "stories") {
subNavEl.find("li.stories > a").addClass("selected");
} else if (urlSegments[2] == "essentials") {
subNavEl.find("li.essentials > a").addClass("selected");
} else if (urlSegments[2] == "googleplay") {
subNavEl.find("li.googleplay > a").addClass("selected");
} else {
parentNavEl.removeClass('has-subnav').addClass("selected");
}
}
// set global variable so we can highlight the sidenav a bit later (such as for google reference)
// and highlight the sidenav
mPagePath = pagePath;
highlightSidenav();
buildBreadcrumbs();
// set up prev/next links if they exist
var $selNavLink = $('#nav').find('a[href="' + pagePath + '"]');
var $selListItem;
if ($selNavLink.length) {
$selListItem = $selNavLink.closest('li');
// set up prev links
var $prevLink = [];
var $prevListItem = $selListItem.prev('li');
var crossBoundaries = ($("body.design").length > 0) || ($("body.guide").length > 0) ? true :
false; // navigate across topic boundaries only in design docs
if ($prevListItem.length) {
if ($prevListItem.hasClass('nav-section') || crossBoundaries) {
// jump to last topic of previous section
$prevLink = $prevListItem.find('a:last');
} else if (!$selListItem.hasClass('nav-section')) {
// jump to previous topic in this section
$prevLink = $prevListItem.find('a:eq(0)');
}
} else {
// jump to this section's index page (if it exists)
var $parentListItem = $selListItem.parents('li');
$prevLink = $selListItem.parents('li').find('a');
// except if cross boundaries aren't allowed, and we're at the top of a section already
// (and there's another parent)
if (!crossBoundaries && $parentListItem.hasClass('nav-section')
&& $selListItem.hasClass('nav-section')) {
$prevLink = [];
}
}
// set up next links
var $nextLink = [];
var startClass = false;
var isCrossingBoundary = false;
if ($selListItem.hasClass('nav-section') && $selListItem.children('div.empty').length == 0) {
// we're on an index page, jump to the first topic
$nextLink = $selListItem.find('ul:eq(0)').find('a:eq(0)');
// if there aren't any children, go to the next section (required for About pages)
if($nextLink.length == 0) {
$nextLink = $selListItem.next('li').find('a');
} else if ($('.topic-start-link').length) {
// as long as there's a child link and there is a "topic start link" (we're on a landing)
// then set the landing page "start link" text to be the first doc title
$('.topic-start-link').text($nextLink.text().toUpperCase());
}
// If the selected page has a description, then it's a class or article homepage
if ($selListItem.find('a[description]').length) {
// this means we're on a class landing page
startClass = true;
}
} else {
// jump to the next topic in this section (if it exists)
$nextLink = $selListItem.next('li').find('a:eq(0)');
if ($nextLink.length == 0) {
isCrossingBoundary = true;
// no more topics in this section, jump to the first topic in the next section
$nextLink = $selListItem.parents('li:eq(0)').next('li').find('a:eq(0)');
if (!$nextLink.length) { // Go up another layer to look for next page (lesson > class > course)
$nextLink = $selListItem.parents('li:eq(1)').next('li.nav-section').find('a:eq(0)');
if ($nextLink.length == 0) {
// if that doesn't work, we're at the end of the list, so disable NEXT link
$('.next-page-link').attr('href','').addClass("disabled")
.click(function() { return false; });
// and completely hide the one in the footer
$('.content-footer .next-page-link').hide();
}
}
}
}
if (startClass) {
$('.start-class-link').attr('href', $nextLink.attr('href')).removeClass("hide");
// if there's no training bar (below the start button),
// then we need to add a bottom border to button
if (!$("#tb").length) {
$('.start-class-link').css({'border-bottom':'1px solid #DADADA'});
}
} else if (isCrossingBoundary && !$('body.design').length) { // Design always crosses boundaries
$('.content-footer.next-class').show();
$('.next-page-link').attr('href','')
.removeClass("hide").addClass("disabled")
.click(function() { return false; });
// and completely hide the one in the footer
$('.content-footer .next-page-link').hide();
if ($nextLink.length) {
$('.next-class-link').attr('href',$nextLink.attr('href'))
.removeClass("hide")
.append(": " + $nextLink.html());
$('.next-class-link').find('.new').empty();
}
} else {
$('.next-page-link').attr('href', $nextLink.attr('href'))
.removeClass("hide");
// for the footer link, also add the next page title
$('.content-footer .next-page-link').append(": " + $nextLink.html());
}
if (!startClass && $prevLink.length) {
var prevHref = $prevLink.attr('href');
if (prevHref == SITE_ROOT + 'index.html') {
// Don't show Previous when it leads to the homepage
} else {
$('.prev-page-link').attr('href', $prevLink.attr('href')).removeClass("hide");
}
}
}
// Set up the course landing pages for Training with class names and descriptions
if ($('body.trainingcourse').length) {
var $classLinks = $selListItem.find('ul li a').not('#nav .nav-section .nav-section ul a');
// create an array for all the class descriptions
var $classDescriptions = new Array($classLinks.length);
var lang = getLangPref();
$classLinks.each(function(index) {
var langDescr = $(this).attr(lang + "-description");
if (typeof langDescr !== 'undefined' && langDescr !== false) {
// if there's a class description in the selected language, use that
$classDescriptions[index] = langDescr;
} else {
// otherwise, use the default english description
$classDescriptions[index] = $(this).attr("description");
}
});
var $olClasses = $('<ol class="class-list"></ol>');
var $liClass;
var $h2Title;
var $pSummary;
var $olLessons;
var $liLesson;
$classLinks.each(function(index) {
$liClass = $('<li class="clearfix"></li>');
$h2Title = $('<a class="title" href="'+$(this).attr('href')+'"><h2 class="norule">' + $(this).html()+'</h2><span></span></a>');
$pSummary = $('<p class="description">' + $classDescriptions[index] + '</p>');
$olLessons = $('<ol class="lesson-list"></ol>');
$lessons = $(this).closest('li').find('ul li a');
if ($lessons.length) {
$lessons.each(function(index) {
$olLessons.append('<li><a href="'+$(this).attr('href')+'">' + $(this).html()+'</a></li>');
});
} else {
$pSummary.addClass('article');
}
$liClass.append($h2Title).append($pSummary).append($olLessons);
$olClasses.append($liClass);
});
$('.jd-descr').append($olClasses);
}
// Set up expand/collapse behavior
initExpandableNavItems("#nav");
$(".scroll-pane").scroll(function(event) {
event.preventDefault();
return false;
});
/* Resize nav height when window height changes */
$(window).resize(function() {
if ($('#side-nav').length == 0) return;
setNavBarDimensions(); // do this even if sidenav isn't fixed because it could become fixed
// make sidenav behave when resizing the window and side-scolling is a concern
updateSideNavDimensions();
checkSticky();
resizeNav(250);
});
if ($('#devdoc-nav').length) {
setNavBarDimensions();
}
// Set up play-on-hover <video> tags.
$('video.play-on-hover').bind('click', function(){
$(this).get(0).load(); // in case the video isn't seekable
$(this).get(0).play();
});
// Set up tooltips
var TOOLTIP_MARGIN = 10;
$('acronym,.tooltip-link').each(function() {
var $target = $(this);
var $tooltip = $('<div>')
.addClass('tooltip-box')
.append($target.attr('title'))
.hide()
.appendTo('body');
$target.removeAttr('title');
$target.hover(function() {
// in
var targetRect = $target.offset();
targetRect.width = $target.width();
targetRect.height = $target.height();
$tooltip.css({
left: targetRect.left,
top: targetRect.top + targetRect.height + TOOLTIP_MARGIN
});
$tooltip.addClass('below');
$tooltip.show();
}, function() {
// out
$tooltip.hide();
});
});
// Set up <h2> deeplinks
$('h2').click(function() {
var id = $(this).attr('id');
if (id) {
if (history && history.replaceState) {
// Change url without scrolling.
history.replaceState({}, '', '#' + id);
} else {
document.location.hash = id;
}
}
});
//Loads the +1 button
var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
po.src = 'https://apis.google.com/js/plusone.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
$(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
if ($(".scroll-pane").length > 1) {
// Check if there's a user preference for the panel heights
var cookieHeight = readCookie("reference_height");
if (cookieHeight) {
restoreHeight(cookieHeight);
}
}
// Resize once loading is finished
resizeNav();
// Check if there's an anchor that we need to scroll into view.
// A delay is needed, because some browsers do not immediately scroll down to the anchor
window.setTimeout(offsetScrollForSticky, 100);
/* init the language selector based on user cookie for lang */
loadLangPref();
changeNavLang(getLangPref());
/* setup event handlers to ensure the overflow menu is visible while picking lang */
$("#language select")
.mousedown(function() {
$("div.morehover").addClass("hover"); })
.blur(function() {
$("div.morehover").removeClass("hover"); });
/* some global variable setup */
resizePackagesNav = $("#resize-packages-nav");
classesNav = $("#classes-nav");
devdocNav = $("#devdoc-nav");
var cookiePath = "";
if (location.href.indexOf("/reference/") != -1) {
cookiePath = "reference_";
} else if (location.href.indexOf("/guide/") != -1) {
cookiePath = "guide_";
} else if (location.href.indexOf("/tools/") != -1) {
cookiePath = "tools_";
} else if (location.href.indexOf("/training/") != -1) {
cookiePath = "training_";
} else if (location.href.indexOf("/design/") != -1) {
cookiePath = "design_";
} else if (location.href.indexOf("/distribute/") != -1) {
cookiePath = "distribute_";
}
/* setup shadowbox for any videos that want it */
var $videoLinks = $("a.video-shadowbox-button, a.notice-developers-video");
if ($videoLinks.length) {
// if there's at least one, add the shadowbox HTML to the body
$('body').prepend(
'<div id="video-container">'+
'<div id="video-frame">'+
'<div class="video-close">'+
'<span id="icon-video-close" onclick="closeVideo()">&nbsp;</span>'+
'</div>'+
'<div id="youTubePlayer"></div>'+
'</div>'+
'</div>');
// loads the IFrame Player API code asynchronously.
$.getScript("https://www.youtube.com/iframe_api");
$videoLinks.each(function() {
var videoId = $(this).attr('href').split('?v=')[1];
$(this).click(function(event) {
event.preventDefault();
startYouTubePlayer(videoId);
});
});
}
});
// END of the onload event
var youTubePlayer;
function onYouTubeIframeAPIReady() {
}
/* Returns the height the shadowbox video should be. It's based on the current
height of the "video-frame" element, which is 100% height for the window.
Then minus the margin so the video isn't actually the full window height. */
function getVideoHeight() {
var frameHeight = $("#video-frame").height();
var marginTop = $("#video-frame").css('margin-top').split('px')[0];
return frameHeight - (marginTop * 2);
}
var mPlayerPaused = false;
function startYouTubePlayer(videoId) {
$("#video-container").show();
$("#video-frame").show();
mPlayerPaused = false;
// compute the size of the player so it's centered in window
var maxWidth = 940; // the width of the web site content
var videoAspect = .5625; // based on 1280x720 resolution
var maxHeight = maxWidth * videoAspect;
var videoHeight = getVideoHeight();
var videoWidth = videoHeight / videoAspect;
if (videoWidth > maxWidth) {
videoWidth = maxWidth;
videoHeight = maxHeight;
}
$("#video-frame").css('width', videoWidth);
// check if we've already created this player
if (youTubePlayer == null) {
// check if there's a start time specified
var idAndHash = videoId.split("#");
var startTime = 0;
if (idAndHash.length > 1) {
startTime = idAndHash[1].split("t=")[1] != undefined ? idAndHash[1].split("t=")[1] : 0;
}
// enable localized player
var lang = getLangPref();
var captionsOn = lang == 'en' ? 0 : 1;
youTubePlayer = new YT.Player('youTubePlayer', {
height: videoHeight,
width: videoWidth,
videoId: idAndHash[0],
playerVars: {start: startTime, hl: lang, cc_load_policy: captionsOn},
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
} else {
// reset the size in case the user adjusted the window since last play
youTubePlayer.setSize(videoWidth, videoHeight);
// if a video different from the one already playing was requested, cue it up
if (videoId != youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0]) {
youTubePlayer.cueVideoById(videoId);
}
youTubePlayer.playVideo();
}
}
function onPlayerReady(event) {
event.target.playVideo();
mPlayerPaused = false;
}
function closeVideo() {
try {
youTubePlayer.pauseVideo();
} catch(e) {
}
$("#video-container").fadeOut(200);
}
/* Track youtube playback for analytics */
function onPlayerStateChange(event) {
// Video starts, send the video ID
if (event.data == YT.PlayerState.PLAYING) {
if (mPlayerPaused) {
ga('send', 'event', 'Videos', 'Resume',
youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0]);
} else {
// track the start playing event so we know from which page the video was selected
ga('send', 'event', 'Videos', 'Start: ' +
youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0],
'on: ' + document.location.href);
}
mPlayerPaused = false;
}
// Video paused, send video ID and video elapsed time
if (event.data == YT.PlayerState.PAUSED) {
ga('send', 'event', 'Videos', 'Paused',
youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0],
youTubePlayer.getCurrentTime());
mPlayerPaused = true;
}
// Video finished, send video ID and video elapsed time
if (event.data == YT.PlayerState.ENDED) {
ga('send', 'event', 'Videos', 'Finished',
youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0],
youTubePlayer.getCurrentTime());
mPlayerPaused = true;
}
}
function initExpandableNavItems(rootTag) {
$(rootTag + ' li.nav-section .nav-section-header').click(function() {
var section = $(this).closest('li.nav-section');
if (section.hasClass('expanded')) {
/* hide me and descendants */
section.find('ul').slideUp(250, function() {
// remove 'expanded' class from my section and any children
section.closest('li').removeClass('expanded');
$('li.nav-section', section).removeClass('expanded');
resizeNav();
});
} else {
/* show me */
// first hide all other siblings
var $others = $('li.nav-section.expanded', $(this).closest('ul')).not('.sticky');
$others.removeClass('expanded').children('ul').slideUp(250);
// now expand me
section.closest('li').addClass('expanded');
section.children('ul').slideDown(250, function() {
resizeNav();
});
}
});
// Stop expand/collapse behavior when clicking on nav section links
// (since we're navigating away from the page)
// This selector captures the first instance of <a>, but not those with "#" as the href.
$('.nav-section-header').find('a:eq(0)').not('a[href="#"]').click(function(evt) {
window.location.href = $(this).attr('href');
return false;
});
}
/** Create the list of breadcrumb links in the sticky header */
function buildBreadcrumbs() {
var $breadcrumbUl = $(".dac-header-crumbs");
var primaryNavLink = ".dac-nav-list > .dac-nav-item > .dac-nav-link";
// Add the secondary horizontal nav item, if provided
var $selectedSecondNav = $(".dac-nav-secondary .dac-nav-link.selected").clone()
.attr('class', 'dac-header-crumbs-link');
if ($selectedSecondNav.length) {
$breadcrumbUl.prepend($('<li class="dac-header-crumbs-item">').append($selectedSecondNav));
}
// Add the primary horizontal nav
var $selectedFirstNav = $(primaryNavLink + ".selected, " + primaryNavLink + ".has-subnav").clone()
.attr('class', 'dac-header-crumbs-link');
// If there's no header nav item, use the logo link and title from alt text
if ($selectedFirstNav.length < 1) {
$selectedFirstNav = $('<a class="dac-header-crumbs-link">')
.attr('href', $("div#header .logo a").attr('href'))
.text($("div#header .logo img").attr('alt'));
}
$breadcrumbUl.prepend($('<li class="dac-header-crumbs-item">').append($selectedFirstNav));
}
/** Highlight the current page in sidenav, expanding children as appropriate */
function highlightSidenav() {
// if something is already highlighted, undo it. This is for dynamic navigation (Samples index)
if ($("ul#nav li.selected").length) {
unHighlightSidenav();
}
// look for URL in sidenav, including the hash
var $selNavLink = $('#nav').find('a[href="' + mPagePath + location.hash + '"]');
// If the selNavLink is still empty, look for it without the hash
if ($selNavLink.length == 0) {
$selNavLink = $('#nav').find('a[href="' + mPagePath + '"]');
}
var $selListItem;
if ($selNavLink.length) {
// Find this page's <li> in sidenav and set selected
$selListItem = $selNavLink.closest('li');
$selListItem.addClass('selected');
// Traverse up the tree and expand all parent nav-sections
$selNavLink.parents('li.nav-section').each(function() {
$(this).addClass('expanded');
$(this).children('ul').show();
});
}
}
function unHighlightSidenav() {
$("ul#nav li.selected").removeClass("selected");
$('ul#nav li.nav-section.expanded').removeClass('expanded').children('ul').hide();
}
function toggleFullscreen(enable) {
var delay = 20;
var enabled = true;
var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
if (enable) {
// Currently NOT USING fullscreen; enable fullscreen
stylesheet.removeAttr('disabled');
$('#nav-swap .fullscreen').removeClass('disabled');
$('#devdoc-nav').css({left:''});
setTimeout(updateSidenavFullscreenWidth,delay); // need to wait a moment for css to switch
enabled = true;
} else {
// Currently USING fullscreen; disable fullscreen
stylesheet.attr('disabled', 'disabled');
$('#nav-swap .fullscreen').addClass('disabled');
setTimeout(updateSidenavFixedWidth,delay); // need to wait a moment for css to switch
enabled = false;
}
writeCookie("fullscreen", enabled, null);
setNavBarDimensions();
resizeNav(delay);
updateSideNavDimensions();
setTimeout(initSidenavHeightResize,delay);
}
// TODO: Refactor into a closure.
var navBarLeftPos;
var navBarWidth;
function setNavBarDimensions() {
navBarLeftPos = $('#body-content').offset().left;
navBarWidth = $('#side-nav').width();
}
function updateSideNavDimensions() {
var newLeft = $(window).scrollLeft() - navBarLeftPos;
$('#devdoc-nav').css({left: -newLeft, width: navBarWidth});
$('#devdoc-nav .totop').css({left: -(newLeft - parseInt($('#side-nav').css('padding-left')))});
}
// TODO: use $(document).ready instead
function addLoadEvent(newfun) {
var current = window.onload;
if (typeof window.onload != 'function') {
window.onload = newfun;
} else {
window.onload = function() {
current();
newfun();
}
}
}
var agent = navigator['userAgent'].toLowerCase();
// If a mobile phone, set flag and do mobile setup
if ((agent.indexOf("mobile") != -1) || // android, iphone, ipod
(agent.indexOf("blackberry") != -1) ||
(agent.indexOf("webos") != -1) ||
(agent.indexOf("mini") != -1)) { // opera mini browsers
isMobile = true;
}
$(document).ready(function() {
$("pre:not(.no-pretty-print)").addClass("prettyprint");
prettyPrint();
});
/* ######### RESIZE THE SIDENAV ########## */
function resizeNav(delay) {
var $nav = $("#devdoc-nav");
var $window = $(window);
var navHeight;
// Get the height of entire window and the total header height.
// Then figure out based on scroll position whether the header is visible
var windowHeight = $window.height();
var scrollTop = $window.scrollTop();
var headerHeight = $('#header-wrapper').outerHeight();
var headerVisible = scrollTop < stickyTop;
// get the height of space between nav and top of window.
// Could be either margin or top position, depending on whether the nav is fixed.
var topMargin = (parseInt($nav.css('top')) || 20) + 1;
// add 1 for the #side-nav bottom margin
// Depending on whether the header is visible, set the side nav's height.
if (headerVisible) {
// The sidenav height grows as the header goes off screen
navHeight = windowHeight - (headerHeight - scrollTop) - topMargin;
} else {
// Once header is off screen, the nav height is almost full window height
navHeight = windowHeight - topMargin;
}
$scrollPanes = $(".scroll-pane");
if ($window.width() < 720) {
$nav.css('height', '');
} else if ($scrollPanes.length > 1) {
// subtract the height of the api level widget and nav swapper from the available nav height
navHeight -= ($('#api-nav-header').outerHeight(true) + $('#nav-swap').outerHeight(true));
$("#swapper").css({height:navHeight + "px"});
if ($("#nav-tree").is(":visible")) {
$("#nav-tree").css({height:navHeight});
}
var classesHeight = navHeight - parseInt($("#resize-packages-nav").css("height")) - 10 + "px";
//subtract 10px to account for drag bar
// if the window becomes small enough to make the class panel height 0,
// then the package panel should begin to shrink
if (parseInt(classesHeight) <= 0) {
$("#resize-packages-nav").css({height:navHeight - 10}); //subtract 10px for drag bar
$("#packages-nav").css({height:navHeight - 10});
}
$("#classes-nav").css({'height':classesHeight, 'margin-top':'10px'});
$("#classes-nav .jspContainer").css({height:classesHeight});
} else {
$nav.height(navHeight);
}
if (delay) {
updateFromResize = true;
delayedReInitScrollbars(delay);
} else {
reInitScrollbars();
}
}
var updateScrollbars = false;
var updateFromResize = false;
/* Re-initialize the scrollbars to account for changed nav size.
* This method postpones the actual update by a 1/4 second in order to optimize the
* scroll performance while the header is still visible, because re-initializing the
* scroll panes is an intensive process.
*/
function delayedReInitScrollbars(delay) {
// If we're scheduled for an update, but have received another resize request
// before the scheduled resize has occured, just ignore the new request
// (and wait for the scheduled one).
if (updateScrollbars && updateFromResize) {
updateFromResize = false;
return;
}
// We're scheduled for an update and the update request came from this method's setTimeout
if (updateScrollbars && !updateFromResize) {
reInitScrollbars();
updateScrollbars = false;
} else {
updateScrollbars = true;
updateFromResize = false;
setTimeout('delayedReInitScrollbars()',delay);
}
}
/* Re-initialize the scrollbars to account for changed nav size. */
function reInitScrollbars() {
var pane = $(".scroll-pane").each(function(){
var api = $(this).data('jsp');
if (!api) {return;}
api.reinitialise( {verticalGutter:0} );
});
$(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
}
/* Resize the height of the nav panels in the reference,
* and save the new size to a cookie */
function saveNavPanels() {
var basePath = getBaseUri(location.pathname);
var section = basePath.substring(1,basePath.indexOf("/",1));
writeCookie("height", resizePackagesNav.css("height"), section);
}
function restoreHeight(packageHeight) {
$("#resize-packages-nav").height(packageHeight);
$("#packages-nav").height(packageHeight);
// var classesHeight = navHeight - packageHeight;
// $("#classes-nav").css({height:classesHeight});
// $("#classes-nav .jspContainer").css({height:classesHeight});
}
/* ######### END RESIZE THE SIDENAV HEIGHT ########## */
/** Scroll the jScrollPane to make the currently selected item visible
This is called when the page finished loading. */
function scrollIntoView(nav) {
return;
var $nav = $("#"+nav);
var element = $nav.jScrollPane({/* ...settings... */});
var api = element.data('jsp');
if ($nav.is(':visible')) {
var $selected = $(".selected", $nav);
if ($selected.length == 0) {
// If no selected item found, exit
return;
}
// get the selected item's offset from its container nav by measuring the item's offset
// relative to the document then subtract the container nav's offset relative to the document
var selectedOffset = $selected.offset().top - $nav.offset().top;
if (selectedOffset > $nav.height() * .8) { // multiply nav height by .8 so we move up the item
// if it's more than 80% down the nav
// scroll the item up by an amount equal to 80% the container nav's height
api.scrollTo(0, selectedOffset - ($nav.height() * .8), false);
}
}
}
/* Show popup dialogs */
function showDialog(id) {
$dialog = $("#"+id);
$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>');
$dialog.wrapInner('<div/>');
$dialog.removeClass("hide");
}
/* ######### COOKIES! ########## */
function readCookie(cookie) {
var myCookie = cookie_namespace+"_"+cookie+"=";
if (document.cookie) {
var index = document.cookie.indexOf(myCookie);
if (index != -1) {
var valStart = index + myCookie.length;
var valEnd = document.cookie.indexOf(";", valStart);
if (valEnd == -1) {
valEnd = document.cookie.length;
}
var val = document.cookie.substring(valStart, valEnd);
return val;
}
}
return 0;
}
function writeCookie(cookie, val, section) {
if (val==undefined) return;
section = section == null ? "_" : "_"+section+"_";
var age = 2*365*24*60*60; // set max-age to 2 years
var cookieValue = cookie_namespace + section + cookie + "=" + val
+ "; max-age=" + age +"; path=/";
document.cookie = cookieValue;
}
/* ######### END COOKIES! ########## */
var sticky = false;
var stickyTop;
var prevScrollLeft = 0; // used to compare current position to previous position of horiz scroll
/* Sets the vertical scoll position at which the sticky bar should appear.
This method is called to reset the position when search results appear or hide */
function setStickyTop() {
stickyTop = $('#header-wrapper').outerHeight() - $('#header > .dac-header-inner').outerHeight();
}
/*
* Displays sticky nav bar on pages when dac header scrolls out of view
*/
$(window).scroll(function(event) {
// Exit if the mouse target is a DIV, because that means the event is coming
// from a scrollable div and so there's no need to make adjustments to our layout
if ($(event.target).nodeName == "DIV") {
return;
}
checkSticky();
});
function checkSticky() {
setStickyTop();
var $headerEl = $('#header');
// Exit if there's no sidenav
if ($('#side-nav').length == 0) return;
var top = $(window).scrollTop();
// we set the navbar fixed when the scroll position is beyond the height of the site header...
var shouldBeSticky = top > stickyTop;
// ... except if the document content is shorter than the sidenav height.
// (this is necessary to avoid crazy behavior on OSX Lion due to overscroll bouncing)
if ($("#doc-col").height() < $("#side-nav").height()) {
shouldBeSticky = false;
}
// Nor on mobile
if (window.innerWidth < 720) {
shouldBeSticky = false;
}
// Account for horizontal scroll
var scrollLeft = $(window).scrollLeft();
// When the sidenav is fixed and user scrolls horizontally, reposition the sidenav to match
if (sticky && (scrollLeft != prevScrollLeft)) {
updateSideNavDimensions();
prevScrollLeft = scrollLeft;
}
// Don't continue if the header is sufficently far away
// (to avoid intensive resizing that slows scrolling)
if (sticky == shouldBeSticky) {
return;
}
// If sticky header visible and position is now near top, hide sticky
if (sticky && !shouldBeSticky) {
sticky = false;
// make the sidenav static again
$('#devdoc-nav')
.removeClass('fixed')
.css({'width':'auto','margin':''});
// delay hide the sticky
$headerEl.removeClass('is-sticky');
// update the sidenaav position for side scrolling
updateSideNavDimensions();
} else if (!sticky && shouldBeSticky) {
sticky = true;
$headerEl.addClass('is-sticky');
// make the sidenav fixed
$('#devdoc-nav')
.addClass('fixed');
// update the sidenaav position for side scrolling
updateSideNavDimensions();
}
resizeNav(250); // pass true in order to delay the scrollbar re-initialization for performance
}
/*
* Manages secion card states and nav resize to conclude loading
*/
(function() {
$(document).ready(function() {
// Stack hover states
$('.section-card-menu').each(function(index, el) {
var height = $(el).height();
$(el).css({height:height+'px', position:'relative'});
var $cardInfo = $(el).find('.card-info');
$cardInfo.css({position: 'absolute', bottom:'0px', left:'0px', right:'0px', overflow:'visible'});
});
});
})();
/* MISC LIBRARY FUNCTIONS */
function toggle(obj, slide) {
var ul = $("ul:first", obj);
var li = ul.parent();
if (li.hasClass("closed")) {
if (slide) {
ul.slideDown("fast");
} else {
ul.show();
}
li.removeClass("closed");
li.addClass("open");
$(".toggle-img", li).attr("title", "hide pages");
} else {
ul.slideUp("fast");
li.removeClass("open");
li.addClass("closed");
$(".toggle-img", li).attr("title", "show pages");
}
}
function buildToggleLists() {
$(".toggle-list").each(
function(i) {
$("div:first", this).append("<a class='toggle-img' href='#' title='show pages' onClick='toggle(this.parentNode.parentNode, true); return false;'></a>");
$(this).addClass("closed");
});
}
function hideNestedItems(list, toggle) {
$list = $(list);
// hide nested lists
if($list.hasClass('showing')) {
$("li ol", $list).hide('fast');
$list.removeClass('showing');
// show nested lists
} else {
$("li ol", $list).show('fast');
$list.addClass('showing');
}
$(".more,.less",$(toggle)).toggle();
}
/* Call this to add listeners to a <select> element for Studio/Eclipse/Other docs */
function setupIdeDocToggle() {
$( "select.ide" ).change(function() {
var selected = $(this).find("option:selected").attr("value");
$(".select-ide").hide();
$(".select-ide."+selected).show();
$("select.ide").val(selected);
});
}
/* REFERENCE NAV SWAP */
function getNavPref() {
var v = readCookie('reference_nav');
if (v != NAV_PREF_TREE) {
v = NAV_PREF_PANELS;
}
return v;
}
function chooseDefaultNav() {
nav_pref = getNavPref();
if (nav_pref == NAV_PREF_TREE) {
$("#nav-panels").toggle();
$("#panel-link").toggle();
$("#nav-tree").toggle();
$("#tree-link").toggle();
}
}
function swapNav() {
if (nav_pref == NAV_PREF_TREE) {
nav_pref = NAV_PREF_PANELS;
} else {
nav_pref = NAV_PREF_TREE;
init_default_navtree(toRoot);
}
writeCookie("nav", nav_pref, "reference");
$("#nav-panels").toggle();
$("#panel-link").toggle();
$("#nav-tree").toggle();
$("#tree-link").toggle();
resizeNav();
// Gross nasty hack to make tree view show up upon first swap by setting height manually
$("#nav-tree .jspContainer:visible")
.css({'height':$("#nav-tree .jspContainer .jspPane").height() +'px'});
// Another nasty hack to make the scrollbar appear now that we have height
resizeNav();
if ($("#nav-tree").is(':visible')) {
scrollIntoView("nav-tree");
} else {
scrollIntoView("packages-nav");
scrollIntoView("classes-nav");
}
}
/* ############################################ */
/* ########## LOCALIZATION ############ */
/* ############################################ */
function getBaseUri(uri) {
var intlUrl = (uri.substring(0,6) == "/intl/");
if (intlUrl) {
base = uri.substring(uri.indexOf('intl/')+5,uri.length);
base = base.substring(base.indexOf('/')+1, base.length);
//alert("intl, returning base url: /" + base);
return ("/" + base);
} else {
//alert("not intl, returning uri as found.");
return uri;
}
}
function requestAppendHL(uri) {
//append "?hl=<lang> to an outgoing request (such as to blog)
var lang = getLangPref();
if (lang) {
var q = 'hl=' + lang;
uri += '?' + q;
window.location = uri;
return false;
} else {
return true;
}
}
function changeNavLang(lang) {
if (lang === 'en') { return; }
var $links = $('a[' + lang + '-lang]');
$links.each(function(){ // for each link with a translation
var $link = $(this);
// put the desired language from the attribute as the text
$link.text($link.attr(lang + '-lang'))
});
}
function changeLangPref(lang, submit) {
writeCookie("pref_lang", lang, null);
// ####### TODO: Remove this condition once we're stable on devsite #######
// This condition is only needed if we still need to support legacy GAE server
if (devsite) {
// Switch language when on Devsite server
if (submit) {
$("#setlang").submit();
}
} else {
// Switch language when on legacy GAE server
if (submit) {
window.location = getBaseUri(location.pathname);
}
}
}
function loadLangPref() {
var lang = readCookie("pref_lang");
if (lang != 0) {
$("#language").find("option[value='"+lang+"']").attr("selected",true);
}
}
function getLangPref() {
var lang = $("#language").find(":selected").attr("value");
if (!lang) {
lang = readCookie("pref_lang");
}
return (lang != 0) ? lang : 'en';
}
/* ########## END LOCALIZATION ############ */
/* Used to hide and reveal supplemental content, such as long code samples.
See the companion CSS in android-developer-docs.css */
function toggleContent(obj) {
var div = $(obj).closest(".toggle-content");
var toggleMe = $(".toggle-content-toggleme:eq(0)",div);
if (div.hasClass("closed")) { // if it's closed, open it
toggleMe.slideDown();
$(".toggle-content-text:eq(0)", obj).toggle();
div.removeClass("closed").addClass("open");
$(".toggle-content-img:eq(0)", div).attr("title", "hide").attr("src", toRoot
+ "assets/images/triangle-opened.png");
} else { // if it's open, close it
toggleMe.slideUp('fast', function() { // Wait until the animation is done before closing arrow
$(".toggle-content-text:eq(0)", obj).toggle();
div.removeClass("open").addClass("closed");
div.find(".toggle-content").removeClass("open").addClass("closed")
.find(".toggle-content-toggleme").hide();
$(".toggle-content-img", div).attr("title", "show").attr("src", toRoot
+ "assets/images/triangle-closed.png");
});
}
return false;
}
/* New version of expandable content */
function toggleExpandable(link,id) {
if($(id).is(':visible')) {
$(id).slideUp();
$(link).removeClass('expanded');
} else {
$(id).slideDown();
$(link).addClass('expanded');
}
}
function hideExpandable(ids) {
$(ids).slideUp();
$(ids).prev('h4').find('a.expandable').removeClass('expanded');
}
/*
* Slideshow 1.0
* Used on /index.html and /develop/index.html for carousel
*
* Sample usage:
* HTML -
* <div class="slideshow-container">
* <a href="" class="slideshow-prev">Prev</a>
* <a href="" class="slideshow-next">Next</a>
* <ul>
* <li class="item"><img src="images/marquee1.jpg"></li>
* <li class="item"><img src="images/marquee2.jpg"></li>
* <li class="item"><img src="images/marquee3.jpg"></li>
* <li class="item"><img src="images/marquee4.jpg"></li>
* </ul>
* </div>
*
* <script type="text/javascript">
* $('.slideshow-container').dacSlideshow({
* auto: true,
* btnPrev: '.slideshow-prev',
* btnNext: '.slideshow-next'
* });
* </script>
*
* Options:
* btnPrev: optional identifier for previous button
* btnNext: optional identifier for next button
* btnPause: optional identifier for pause button
* auto: whether or not to auto-proceed
* speed: animation speed
* autoTime: time between auto-rotation
* easing: easing function for transition
* start: item to select by default
* scroll: direction to scroll in
* pagination: whether or not to include dotted pagination
*
*/
(function($) {
$.fn.dacSlideshow = function(o) {
//Options - see above
o = $.extend({
btnPrev: null,
btnNext: null,
btnPause: null,
auto: true,
speed: 500,
autoTime: 12000,
easing: null,
start: 0,
scroll: 1,
pagination: true
}, o || {});
//Set up a carousel for each
return this.each(function() {
var running = false;
var animCss = o.vertical ? "top" : "left";
var sizeCss = o.vertical ? "height" : "width";
var div = $(this);
var ul = $("ul", div);
var tLi = $("li", ul);
var tl = tLi.size();
var timer = null;
var li = $("li", ul);
var itemLength = li.size();
var curr = o.start;
li.css({float: o.vertical ? "none" : "left"});
ul.css({margin: "0", padding: "0", position: "relative", "list-style-type": "none", "z-index": "1"});
div.css({position: "relative", "z-index": "2", left: "0px"});
var liSize = o.vertical ? height(li) : width(li);
var ulSize = liSize * itemLength;
var divSize = liSize;
li.css({width: li.width(), height: li.height()});
ul.css(sizeCss, ulSize+"px").css(animCss, -(curr*liSize));
div.css(sizeCss, divSize+"px");
//Pagination
if (o.pagination) {
var pagination = $("<div class='pagination'></div>");
var pag_ul = $("<ul></ul>");
if (tl > 1) {
for (var i=0;i<tl;i++) {
var li = $("<li>"+i+"</li>");
pag_ul.append(li);
if (i==o.start) li.addClass('active');
li.click(function() {
go(parseInt($(this).text()));
})
}
pagination.append(pag_ul);
div.append(pagination);
}
}
//Previous button
if(o.btnPrev)
$(o.btnPrev).click(function(e) {
e.preventDefault();
return go(curr-o.scroll);
});
//Next button
if(o.btnNext)
$(o.btnNext).click(function(e) {
e.preventDefault();
return go(curr+o.scroll);
});
//Pause button
if(o.btnPause)
$(o.btnPause).click(function(e) {
e.preventDefault();
if ($(this).hasClass('paused')) {
startRotateTimer();
} else {
pauseRotateTimer();
}
});
//Auto rotation
if(o.auto) startRotateTimer();
function startRotateTimer() {
clearInterval(timer);
timer = setInterval(function() {
if (curr == tl-1) {
go(0);
} else {
go(curr+o.scroll);
}
}, o.autoTime);
$(o.btnPause).removeClass('paused');
}
function pauseRotateTimer() {
clearInterval(timer);
$(o.btnPause).addClass('paused');
}
//Go to an item
function go(to) {
if(!running) {
if(to<0) {
to = itemLength-1;
} else if (to>itemLength-1) {
to = 0;
}
curr = to;
running = true;
ul.animate(
animCss == "left" ? { left: -(curr*liSize) } : { top: -(curr*liSize) } , o.speed, o.easing,
function() {
running = false;
}
);
$(o.btnPrev + "," + o.btnNext).removeClass("disabled");
$( (curr-o.scroll<0 && o.btnPrev)
||
(curr+o.scroll > itemLength && o.btnNext)
||
[]
).addClass("disabled");
var nav_items = $('li', pagination);
nav_items.removeClass('active');
nav_items.eq(to).addClass('active');
}
if(o.auto) startRotateTimer();
return false;
};
});
};
function css(el, prop) {
return parseInt($.css(el[0], prop)) || 0;
};
function width(el) {
return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
};
function height(el) {
return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
};
})(jQuery);
/*
* dacSlideshow 1.0
* Used on develop/index.html for side-sliding tabs
*
* Sample usage:
* HTML -
* <div class="slideshow-container">
* <a href="" class="slideshow-prev">Prev</a>
* <a href="" class="slideshow-next">Next</a>
* <ul>
* <li class="item"><img src="images/marquee1.jpg"></li>
* <li class="item"><img src="images/marquee2.jpg"></li>
* <li class="item"><img src="images/marquee3.jpg"></li>
* <li class="item"><img src="images/marquee4.jpg"></li>
* </ul>
* </div>
*
* <script type="text/javascript">
* $('.slideshow-container').dacSlideshow({
* auto: true,
* btnPrev: '.slideshow-prev',
* btnNext: '.slideshow-next'
* });
* </script>
*
* Options:
* btnPrev: optional identifier for previous button
* btnNext: optional identifier for next button
* auto: whether or not to auto-proceed
* speed: animation speed
* autoTime: time between auto-rotation
* easing: easing function for transition
* start: item to select by default
* scroll: direction to scroll in
* pagination: whether or not to include dotted pagination
*
*/
(function($) {
$.fn.dacTabbedList = function(o) {
//Options - see above
o = $.extend({
speed : 250,
easing: null,
nav_id: null,
frame_id: null
}, o || {});
//Set up a carousel for each
return this.each(function() {
var curr = 0;
var running = false;
var animCss = "margin-left";
var sizeCss = "width";
var div = $(this);
var nav = $(o.nav_id, div);
var nav_li = $("li", nav);
var nav_size = nav_li.size();
var frame = div.find(o.frame_id);
var content_width = $(frame).find('ul').width();
//Buttons
$(nav_li).click(function(e) {
go($(nav_li).index($(this)));
})
//Go to an item
function go(to) {
if(!running) {
curr = to;
running = true;
frame.animate({ 'margin-left' : -(curr*content_width) }, o.speed, o.easing,
function() {
running = false;
}
);
nav_li.removeClass('active');
nav_li.eq(to).addClass('active');
}
return false;
};
});
};
function css(el, prop) {
return parseInt($.css(el[0], prop)) || 0;
};
function width(el) {
return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
};
function height(el) {
return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
};
})(jQuery);
/* ######################################################## */
/* ################ SEARCH SUGGESTIONS ################## */
/* ######################################################## */
var gSelectedIndex = -1; // the index position of currently highlighted suggestion
var gSelectedColumn = -1; // which column of suggestion lists is currently focused
var gMatches = new Array();
var gLastText = "";
var gInitialized = false;
var ROW_COUNT_FRAMEWORK = 20; // max number of results in list
var gListLength = 0;
var gGoogleMatches = new Array();
var ROW_COUNT_GOOGLE = 15; // max number of results in list
var gGoogleListLength = 0;
var gDocsMatches = new Array();
var ROW_COUNT_DOCS = 100; // max number of results in list
var gDocsListLength = 0;
function onSuggestionClick(link) {
// When user clicks a suggested document, track it
ga('send', 'event', 'Suggestion Click', 'clicked: ' + $(link).attr('href'),
'query: ' + $("#search_autocomplete").val().toLowerCase());
}
function set_item_selected($li, selected)
{
if (selected) {
$li.attr('class','jd-autocomplete jd-selected');
} else {
$li.attr('class','jd-autocomplete');
}
}
function set_item_values(toroot, $li, match)
{
var $link = $('a',$li);
$link.html(match.__hilabel || match.label);
$link.attr('href',toroot + match.link);
}
function set_item_values_jd(toroot, $li, match)
{
var $link = $('a',$li);
$link.html(match.title);
$link.attr('href',toroot + match.url);
}
function new_suggestion($list) {
var $li = $("<li class='jd-autocomplete'></li>");
$list.append($li);
$li.mousedown(function() {
window.location = this.firstChild.getAttribute("href");
});
$li.mouseover(function() {
$('.search_filtered_wrapper li').removeClass('jd-selected');
$(this).addClass('jd-selected');
gSelectedColumn = $(".search_filtered:visible").index($(this).closest('.search_filtered'));
gSelectedIndex = $("li", $(".search_filtered:visible")[gSelectedColumn]).index(this);
});
$li.append("<a onclick='onSuggestionClick(this)'></a>");
$li.attr('class','show-item');
return $li;
}
function sync_selection_table(toroot)
{
var $li; //list item jquery object
var i; //list item iterator
// if there are NO results at all, hide all columns
if (!(gMatches.length > 0) && !(gGoogleMatches.length > 0) && !(gDocsMatches.length > 0)) {
$('.suggest-card').hide(300);
return;
}
// if there are api results
if ((gMatches.length > 0) || (gGoogleMatches.length > 0)) {
// reveal suggestion list
$('.suggest-card.reference').show();
var listIndex = 0; // list index position
// reset the lists
$(".suggest-card.reference li").remove();
// ########### ANDROID RESULTS #############
if (gMatches.length > 0) {
// determine android results to show
gListLength = gMatches.length < ROW_COUNT_FRAMEWORK ?
gMatches.length : ROW_COUNT_FRAMEWORK;
for (i=0; i<gListLength; i++) {
var $li = new_suggestion($(".suggest-card.reference ul"));
set_item_values(toroot, $li, gMatches[i]);
set_item_selected($li, i == gSelectedIndex);
}
}
// ########### GOOGLE RESULTS #############
if (gGoogleMatches.length > 0) {
// show header for list
$(".suggest-card.reference ul").append("<li class='header'>in Google Services:</li>");
// determine google results to show
gGoogleListLength = gGoogleMatches.length < ROW_COUNT_GOOGLE ? gGoogleMatches.length : ROW_COUNT_GOOGLE;
for (i=0; i<gGoogleListLength; i++) {
var $li = new_suggestion($(".suggest-card.reference ul"));
set_item_values(toroot, $li, gGoogleMatches[i]);
set_item_selected($li, i == gSelectedIndex);
}
}
} else {
$('.suggest-card.reference').hide();
}
// ########### JD DOC RESULTS #############
if (gDocsMatches.length > 0) {
// reset the lists
$(".suggest-card:not(.reference) li").remove();
// determine google results to show
// NOTE: The order of the conditions below for the sugg.type MUST BE SPECIFIC:
// The order must match the reverse order that each section appears as a card in
// the suggestion UI... this may be only for the "develop" grouped items though.
gDocsListLength = gDocsMatches.length < ROW_COUNT_DOCS ? gDocsMatches.length : ROW_COUNT_DOCS;
for (i=0; i<gDocsListLength; i++) {
var sugg = gDocsMatches[i];
var $li;
if (sugg.type == "design") {
$li = new_suggestion($(".suggest-card.design ul"));
} else
if (sugg.type == "distribute") {
$li = new_suggestion($(".suggest-card.distribute ul"));
} else
if (sugg.type == "samples") {
$li = new_suggestion($(".suggest-card.develop .child-card.samples"));
} else
if (sugg.type == "training") {
$li = new_suggestion($(".suggest-card.develop .child-card.training"));
} else
if (sugg.type == "about"||"guide"||"tools"||"google") {
$li = new_suggestion($(".suggest-card.develop .child-card.guides"));
} else {
continue;
}
set_item_values_jd(toroot, $li, sugg);
set_item_selected($li, i == gSelectedIndex);
}
// add heading and show or hide card
if ($(".suggest-card.design li").length > 0) {
$(".suggest-card.design ul").prepend("<li class='header'>Design:</li>");
$(".suggest-card.design").show(300);
} else {
$('.suggest-card.design').hide(300);
}
if ($(".suggest-card.distribute li").length > 0) {
$(".suggest-card.distribute ul").prepend("<li class='header'>Distribute:</li>");
$(".suggest-card.distribute").show(300);
} else {
$('.suggest-card.distribute').hide(300);
}
if ($(".child-card.guides li").length > 0) {
$(".child-card.guides").prepend("<li class='header'>Guides:</li>");
$(".child-card.guides li").appendTo(".suggest-card.develop ul");
}
if ($(".child-card.training li").length > 0) {
$(".child-card.training").prepend("<li class='header'>Training:</li>");
$(".child-card.training li").appendTo(".suggest-card.develop ul");
}
if ($(".child-card.samples li").length > 0) {
$(".child-card.samples").prepend("<li class='header'>Samples:</li>");
$(".child-card.samples li").appendTo(".suggest-card.develop ul");
}
if ($(".suggest-card.develop li").length > 0) {
$(".suggest-card.develop").show(300);
} else {
$('.suggest-card.develop').hide(300);
}
} else {
$('.suggest-card:not(.reference)').hide(300);
}
}
/** Called by the search input's onkeydown and onkeyup events.
* Handles navigation with keyboard arrows, Enter key to invoke search,
* otherwise invokes search suggestions on key-up event.
* @param e The JS event
* @param kd True if the event is key-down
* @param toroot A string for the site's root path
* @returns True if the event should bubble up
*/
function search_changed(e, kd, toroot)
{
var currentLang = getLangPref();
var search = document.getElementById("search_autocomplete");
var text = search.value.replace(/(^ +)|( +$)/g, '');
// get the ul hosting the currently selected item
gSelectedColumn = gSelectedColumn >= 0 ? gSelectedColumn : 0;
var $columns = $(".search_filtered_wrapper").find(".search_filtered:visible");
var $selectedUl = $columns[gSelectedColumn];
// show/hide the close button
if (text != '') {
$("#search-close").removeClass("hide");
} else {
$("#search-close").addClass("hide");
}
// 27 = esc
if (e.keyCode == 27) {
// close all search results
if (kd) $('#search-close').trigger('click');
return true;
}
// 13 = enter
else if (e.keyCode == 13) {
if (gSelectedIndex < 0) {
$('.suggest-card').hide();
if ($("#searchResults").is(":hidden") && (search.value != "")) {
// if results aren't showing (and text not empty), return true to allow search to execute
$('body,html').animate({scrollTop:0}, '500', 'swing');
return true;
} else {
// otherwise, results are already showing, so allow ajax to auto refresh the results
// and ignore this Enter press to avoid the reload.
return false;
}
} else if (kd && gSelectedIndex >= 0) {
// click the link corresponding to selected item
$("a",$("li",$selectedUl)[gSelectedIndex]).get()[0].click();
return false;
}
}
// If Google results are showing, return true to allow ajax search to execute
else if ($("#searchResults").is(":visible")) {
// Also, if search_results is scrolled out of view, scroll to top to make results visible
if ((sticky ) && (search.value != "")) {
$('body,html').animate({scrollTop:0}, '500', 'swing');
}
return true;
}
// 38 UP ARROW
else if (kd && (e.keyCode == 38)) {
// if the next item is a header, skip it
if ($($("li", $selectedUl)[gSelectedIndex-1]).hasClass("header")) {
gSelectedIndex--;
}
if (gSelectedIndex >= 0) {
$('li', $selectedUl).removeClass('jd-selected');
gSelectedIndex--;
$('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
// If user reaches top, reset selected column
if (gSelectedIndex < 0) {
gSelectedColumn = -1;
}
}
return false;
}
// 40 DOWN ARROW
else if (kd && (e.keyCode == 40)) {
// if the next item is a header, skip it
if ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header")) {
gSelectedIndex++;
}
if ((gSelectedIndex < $("li", $selectedUl).length-1) ||
($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header"))) {
$('li', $selectedUl).removeClass('jd-selected');
gSelectedIndex++;
$('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
}
return false;
}
// Consider left/right arrow navigation
// NOTE: Order of suggest columns are reverse order (index position 0 is on right)
else if (kd && $columns.length > 1 && gSelectedColumn >= 0) {
// 37 LEFT ARROW
// go left only if current column is not left-most column (last column)
if (e.keyCode == 37 && gSelectedColumn < $columns.length - 1) {
$('li', $selectedUl).removeClass('jd-selected');
gSelectedColumn++;
$selectedUl = $columns[gSelectedColumn];
// keep or reset the selected item to last item as appropriate
gSelectedIndex = gSelectedIndex >
$("li", $selectedUl).length-1 ?
$("li", $selectedUl).length-1 : gSelectedIndex;
// if the corresponding item is a header, move down
if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
gSelectedIndex++;
}
// set item selected
$('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
return false;
}
// 39 RIGHT ARROW
// go right only if current column is not the right-most column (first column)
else if (e.keyCode == 39 && gSelectedColumn > 0) {
$('li', $selectedUl).removeClass('jd-selected');
gSelectedColumn--;
$selectedUl = $columns[gSelectedColumn];
// keep or reset the selected item to last item as appropriate
gSelectedIndex = gSelectedIndex >
$("li", $selectedUl).length-1 ?
$("li", $selectedUl).length-1 : gSelectedIndex;
// if the corresponding item is a header, move down
if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
gSelectedIndex++;
}
// set item selected
$('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
return false;
}
}
// if key-up event and not arrow down/up/left/right,
// read the search query and add suggestions to gMatches
else if (!kd && (e.keyCode != 40)
&& (e.keyCode != 38)
&& (e.keyCode != 37)
&& (e.keyCode != 39)) {
gSelectedIndex = -1;
gMatches = new Array();
matchedCount = 0;
gGoogleMatches = new Array();
matchedCountGoogle = 0;
gDocsMatches = new Array();
matchedCountDocs = 0;
// Search for Android matches
for (var i=0; i<DATA.length; i++) {
var s = DATA[i];
if (text.length != 0 &&
s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
gMatches[matchedCount] = s;
matchedCount++;
}
}
rank_autocomplete_api_results(text, gMatches);
for (var i=0; i<gMatches.length; i++) {
var s = gMatches[i];
}
// Search for Google matches
for (var i=0; i<GOOGLE_DATA.length; i++) {
var s = GOOGLE_DATA[i];
if (text.length != 0 &&
s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
gGoogleMatches[matchedCountGoogle] = s;
matchedCountGoogle++;
}
}
rank_autocomplete_api_results(text, gGoogleMatches);
for (var i=0; i<gGoogleMatches.length; i++) {
var s = gGoogleMatches[i];
}
highlight_autocomplete_result_labels(text);
// Search for matching JD docs
if (text.length >= 2) {
// match only the beginning of a word
var queryStr = text.toLowerCase();
// Search for Training classes
for (var i=0; i<TRAINING_RESOURCES.length; i++) {
// current search comparison, with counters for tag and title,
// used later to improve ranking
var s = TRAINING_RESOURCES[i];
s.matched_tag = 0;
s.matched_title = 0;
var matched = false;
// Check if query matches any tags; work backwards toward 1 to assist ranking
for (var j = s.keywords.length - 1; j >= 0; j--) {
// it matches a tag
if (s.keywords[j].toLowerCase().indexOf(queryStr) == 0) {
matched = true;
s.matched_tag = j + 1; // add 1 to index position
}
}
// Don't consider doc title for lessons (only for class landing pages),
// unless the lesson has a tag that already matches
if ((s.lang == currentLang) &&
(!(s.type == "training" && s.url.indexOf("index.html") == -1) || matched)) {
// it matches the doc title
if (s.title.toLowerCase().indexOf(queryStr) == 0) {
matched = true;
s.matched_title = 1;
}
}
if (matched) {
gDocsMatches[matchedCountDocs] = s;
matchedCountDocs++;
}
}
// Search for API Guides
for (var i=0; i<GUIDE_RESOURCES.length; i++) {
// current search comparison, with counters for tag and title,
// used later to improve ranking
var s = GUIDE_RESOURCES[i];
s.matched_tag = 0;
s.matched_title = 0;
var matched = false;
// Check if query matches any tags; work backwards toward 1 to assist ranking
for (var j = s.keywords.length - 1; j >= 0; j--) {
// it matches a tag
if (s.keywords[j].toLowerCase().indexOf(queryStr) == 0) {
matched = true;
s.matched_tag = j + 1; // add 1 to index position
}
}
// Check if query matches the doc title, but only for current language
if (s.lang == currentLang) {
// if query matches the doc title
if (s.title.toLowerCase().indexOf(queryStr) == 0) {
matched = true;
s.matched_title = 1;
}
}
if (matched) {
gDocsMatches[matchedCountDocs] = s;
matchedCountDocs++;
}
}
// Search for Tools Guides
for (var i=0; i<TOOLS_RESOURCES.length; i++) {
// current search comparison, with counters for tag and title,
// used later to improve ranking
var s = TOOLS_RESOURCES[i];
s.matched_tag = 0;
s.matched_title = 0;
var matched = false;
// Check if query matches any tags; work backwards toward 1 to assist ranking
for (var j = s.keywords.length - 1; j >= 0; j--) {
// it matches a tag
if (s.keywords[j].toLowerCase().indexOf(queryStr) == 0) {
matched = true;
s.matched_tag = j + 1; // add 1 to index position
}
}
// Check if query matches the doc title, but only for current language
if (s.lang == currentLang) {
// if query matches the doc title
if (s.title.toLowerCase().indexOf(queryStr) == 0) {
matched = true;
s.matched_title = 1;
}
}
if (matched) {
gDocsMatches[matchedCountDocs] = s;
matchedCountDocs++;
}
}
// Search for About docs
for (var i=0; i<ABOUT_RESOURCES.length; i++) {
// current search comparison, with counters for tag and title,
// used later to improve ranking
var s = ABOUT_RESOURCES[i];
s.matched_tag = 0;
s.matched_title = 0;
var matched = false;
// Check if query matches any tags; work backwards toward 1 to assist ranking
for (var j = s.keywords.length - 1; j >= 0; j--) {
// it matches a tag
if (s.keywords[j].toLowerCase().indexOf(queryStr) == 0) {
matched = true;
s.matched_tag = j + 1; // add 1 to index position
}
}
// Check if query matches the doc title, but only for current language
if (s.lang == currentLang) {
// if query matches the doc title
if (s.title.toLowerCase().indexOf(queryStr) == 0) {
matched = true;
s.matched_title = 1;
}
}
if (matched) {
gDocsMatches[matchedCountDocs] = s;
matchedCountDocs++;
}
}
// Search for Design guides
for (var i=0; i<DESIGN_RESOURCES.length; i++) {
// current search comparison, with counters for tag and title,
// used later to improve ranking
var s = DESIGN_RESOURCES[i];
s.matched_tag = 0;
s.matched_title = 0;
var matched = false;
// Check if query matches any tags; work backwards toward 1 to assist ranking
for (var j = s.keywords.length - 1; j >= 0; j--) {
// it matches a tag
if (s.keywords[j].toLowerCase().indexOf(queryStr) == 0) {
matched = true;
s.matched_tag = j + 1; // add 1 to index position
}
}
// Check if query matches the doc title, but only for current language
if (s.lang == currentLang) {
// if query matches the doc title
if (s.title.toLowerCase().indexOf(queryStr) == 0) {
matched = true;
s.matched_title = 1;
}
}
if (matched) {
gDocsMatches[matchedCountDocs] = s;
matchedCountDocs++;
}
}
// Search for Distribute guides
for (var i=0; i<DISTRIBUTE_RESOURCES.length; i++) {
// current search comparison, with counters for tag and title,
// used later to improve ranking
var s = DISTRIBUTE_RESOURCES[i];
s.matched_tag = 0;
s.matched_title = 0;
var matched = false;
// Check if query matches any tags; work backwards toward 1 to assist ranking
for (var j = s.keywords.length - 1; j >= 0; j--) {
// it matches a tag
if (s.keywords[j].toLowerCase().indexOf(queryStr) == 0) {
matched = true;
s.matched_tag = j + 1; // add 1 to index position
}
}
// Check if query matches the doc title, but only for current language
if (s.lang == currentLang) {
// if query matches the doc title
if (s.title.toLowerCase().indexOf(queryStr) == 0) {
matched = true;
s.matched_title = 1;
}
}
if (matched) {
gDocsMatches[matchedCountDocs] = s;
matchedCountDocs++;
}
}
// Search for Google guides
for (var i=0; i<GOOGLE_RESOURCES.length; i++) {
// current search comparison, with counters for tag and title,
// used later to improve ranking
var s = GOOGLE_RESOURCES[i];
s.matched_tag = 0;
s.matched_title = 0;
var matched = false;
// Check if query matches any tags; work backwards toward 1 to assist ranking
for (var j = s.keywords.length - 1; j >= 0; j--) {
// it matches a tag
if (s.keywords[j].toLowerCase().indexOf(queryStr) == 0) {
matched = true;
s.matched_tag = j + 1; // add 1 to index position
}
}
// Check if query matches the doc title, but only for current language
if (s.lang == currentLang) {
// if query matches the doc title
if (s.title.toLowerCase().indexOf(queryStr) == 0) {
matched = true;
s.matched_title = 1;
}
}
if (matched) {
gDocsMatches[matchedCountDocs] = s;
matchedCountDocs++;
}
}
// Search for Samples
for (var i=0; i<SAMPLES_RESOURCES.length; i++) {
// current search comparison, with counters for tag and title,
// used later to improve ranking
var s = SAMPLES_RESOURCES[i];
s.matched_tag = 0;
s.matched_title = 0;
var matched = false;
// Check if query matches any tags; work backwards toward 1 to assist ranking
for (var j = s.keywords.length - 1; j >= 0; j--) {
// it matches a tag
if (s.keywords[j].toLowerCase().indexOf(queryStr) == 0) {
matched = true;
s.matched_tag = j + 1; // add 1 to index position
}
}
// Check if query matches the doc title, but only for current language
if (s.lang == currentLang) {
// if query matches the doc title.t
if (s.title.toLowerCase().indexOf(queryStr) == 0) {
matched = true;
s.matched_title = 1;
}
}
if (matched) {
gDocsMatches[matchedCountDocs] = s;
matchedCountDocs++;
}
}
// Search for Preview Guides
for (var i=0; i<PREVIEW_RESOURCES.length; i++) {
// current search comparison, with counters for tag and title,
// used later to improve ranking
var s = PREVIEW_RESOURCES[i];
s.matched_tag = 0;
s.matched_title = 0;
var matched = false;
// Check if query matches any tags; work backwards toward 1 to assist ranking
for (var j = s.keywords.length - 1; j >= 0; j--) {
// it matches a tag
if (s.keywords[j].toLowerCase().indexOf(queryStr) == 0) {
matched = true;
s.matched_tag = j + 1; // add 1 to index position
}
}
// Check if query matches the doc title, but only for current language
if (s.lang == currentLang) {
// if query matches the doc title
if (s.title.toLowerCase().indexOf(queryStr) == 0) {
matched = true;
s.matched_title = 1;
}
}
if (matched) {
gDocsMatches[matchedCountDocs] = s;
matchedCountDocs++;
}
}
// Rank/sort all the matched pages
rank_autocomplete_doc_results(text, gDocsMatches);
}
// draw the suggestions
sync_selection_table(toroot);
return true; // allow the event to bubble up to the search api
}
}
/* Order the jd doc result list based on match quality */
function rank_autocomplete_doc_results(query, matches) {
query = query || '';
if (!matches || !matches.length)
return;
var _resultScoreFn = function(match) {
var score = 1.0;
// if the query matched a tag
if (match.matched_tag > 0) {
// multiply score by factor relative to position in tags list (max of 3)
score *= 3 / match.matched_tag;
// if it also matched the title
if (match.matched_title > 0) {
score *= 2;
}
} else if (match.matched_title > 0) {
score *= 3;
}
return score;
};
for (var i=0; i<matches.length; i++) {
matches[i].__resultScore = _resultScoreFn(matches[i]);
}
matches.sort(function(a,b){
var n = b.__resultScore - a.__resultScore;
if (n == 0) // lexicographical sort if scores are the same
n = (a.label < b.label) ? -1 : 1;
return n;
});
}
/* Order the result list based on match quality */
function rank_autocomplete_api_results(query, matches) {
query = query || '';
if (!matches || !matches.length)
return;
// helper function that gets the last occurence index of the given regex
// in the given string, or -1 if not found
var _lastSearch = function(s, re) {
if (s == '')
return -1;
var l = -1;
var tmp;
while ((tmp = s.search(re)) >= 0) {
if (l < 0) l = 0;
l += tmp;
s = s.substr(tmp + 1);
}
return l;
};
// helper function that counts the occurrences of a given character in
// a given string
var _countChar = function(s, c) {
var n = 0;
for (var i=0; i<s.length; i++)
if (s.charAt(i) == c) ++n;
return n;
};
var queryLower = query.toLowerCase();
var queryAlnum = (queryLower.match(/\w+/) || [''])[0];
var partPrefixAlnumRE = new RegExp('\\b' + queryAlnum);
var partExactAlnumRE = new RegExp('\\b' + queryAlnum + '\\b');
var _resultScoreFn = function(result) {
// scores are calculated based on exact and prefix matches,
// and then number of path separators (dots) from the last
// match (i.e. favoring classes and deep package names)
var score = 1.0;
var labelLower = result.label.toLowerCase();
var t;
t = _lastSearch(labelLower, partExactAlnumRE);
if (t >= 0) {
// exact part match
var partsAfter = _countChar(labelLower.substr(t + 1), '.');
score *= 200 / (partsAfter + 1);
} else {
t = _lastSearch(labelLower, partPrefixAlnumRE);
if (t >= 0) {
// part prefix match
var partsAfter = _countChar(labelLower.substr(t + 1), '.');
score *= 20 / (partsAfter + 1);
}
}
return score;
};
for (var i=0; i<matches.length; i++) {
// if the API is deprecated, default score is 0; otherwise, perform scoring
if (matches[i].deprecated == "true") {
matches[i].__resultScore = 0;
} else {
matches[i].__resultScore = _resultScoreFn(matches[i]);
}
}
matches.sort(function(a,b){
var n = b.__resultScore - a.__resultScore;
if (n == 0) // lexicographical sort if scores are the same
n = (a.label < b.label) ? -1 : 1;
return n;
});
}
/* Add emphasis to part of string that matches query */
function highlight_autocomplete_result_labels(query) {
query = query || '';
if ((!gMatches || !gMatches.length) && (!gGoogleMatches || !gGoogleMatches.length))
return;
var queryLower = query.toLowerCase();
var queryAlnumDot = (queryLower.match(/[\w\.]+/) || [''])[0];
var queryRE = new RegExp(
'(' + queryAlnumDot.replace(/\./g, '\\.') + ')', 'ig');
for (var i=0; i<gMatches.length; i++) {
gMatches[i].__hilabel = gMatches[i].label.replace(
queryRE, '<b>$1</b>');
}
for (var i=0; i<gGoogleMatches.length; i++) {
gGoogleMatches[i].__hilabel = gGoogleMatches[i].label.replace(
queryRE, '<b>$1</b>');
}
}
function search_focus_changed(obj, focused)
{
if (!focused) {
if(obj.value == ""){
$("#search-close").addClass("hide");
}
$(".suggest-card").hide();
}
}
function submit_search() {
var query = document.getElementById('search_autocomplete').value;
location.hash = 'q=' + query;
loadSearchResults();
$("#searchResults").slideDown('slow', setStickyTop);
return false;
}
function hideResults() {
$("#searchResults").slideUp('fast', setStickyTop);
$("#search-close").addClass("hide");
location.hash = '';
$("#search_autocomplete").val("").blur();
// reset the ajax search callback to nothing, so results don't appear unless ENTER
searchControl.setSearchStartingCallback(this, function(control, searcher, query) {});
// forcefully regain key-up event control (previously jacked by search api)
$("#search_autocomplete").keyup(function(event) {
return search_changed(event, false, toRoot);
});
return false;
}
/* ########################################################## */
/* ################ CUSTOM SEARCH ENGINE ################## */
/* ########################################################## */
var searchControl;
google.load('search', '1', {"callback" : function() {
searchControl = new google.search.SearchControl();
} });
function loadSearchResults() {
document.getElementById("search_autocomplete").style.color = "#000";
searchControl = new google.search.SearchControl();
// use our existing search form and use tabs when multiple searchers are used
drawOptions = new google.search.DrawOptions();
drawOptions.setDrawMode(google.search.SearchControl.DRAW_MODE_TABBED);
drawOptions.setInput(document.getElementById("search_autocomplete"));
// configure search result options
searchOptions = new google.search.SearcherOptions();
searchOptions.setExpandMode(GSearchControl.EXPAND_MODE_OPEN);
// configure each of the searchers, for each tab
devSiteSearcher = new google.search.WebSearch();
devSiteSearcher.setUserDefinedLabel("All");
devSiteSearcher.setSiteRestriction("001482626316274216503:zu90b7s047u");
designSearcher = new google.search.WebSearch();
designSearcher.setUserDefinedLabel("Design");
designSearcher.setSiteRestriction("http://developer.android.com/design/");
trainingSearcher = new google.search.WebSearch();
trainingSearcher.setUserDefinedLabel("Training");
trainingSearcher.setSiteRestriction("http://developer.android.com/training/");
guidesSearcher = new google.search.WebSearch();
guidesSearcher.setUserDefinedLabel("Guides");
guidesSearcher.setSiteRestriction("http://developer.android.com/guide/");
referenceSearcher = new google.search.WebSearch();
referenceSearcher.setUserDefinedLabel("Reference");
referenceSearcher.setSiteRestriction("http://developer.android.com/reference/");
googleSearcher = new google.search.WebSearch();
googleSearcher.setUserDefinedLabel("Google Services");
googleSearcher.setSiteRestriction("http://developer.android.com/google/");
blogSearcher = new google.search.WebSearch();
blogSearcher.setUserDefinedLabel("Blog");
blogSearcher.setSiteRestriction("http://android-developers.blogspot.com");
// add each searcher to the search control
searchControl.addSearcher(devSiteSearcher, searchOptions);
searchControl.addSearcher(designSearcher, searchOptions);
searchControl.addSearcher(trainingSearcher, searchOptions);
searchControl.addSearcher(guidesSearcher, searchOptions);
searchControl.addSearcher(referenceSearcher, searchOptions);
searchControl.addSearcher(googleSearcher, searchOptions);
searchControl.addSearcher(blogSearcher, searchOptions);
// configure result options
searchControl.setResultSetSize(google.search.Search.LARGE_RESULTSET);
searchControl.setLinkTarget(google.search.Search.LINK_TARGET_SELF);
searchControl.setTimeoutInterval(google.search.SearchControl.TIMEOUT_SHORT);
searchControl.setNoResultsString(google.search.SearchControl.NO_RESULTS_DEFAULT_STRING);
// upon ajax search, refresh the url and search title
searchControl.setSearchStartingCallback(this, function(control, searcher, query) {
updateResultTitle(query);
var query = document.getElementById('search_autocomplete').value;
location.hash = 'q=' + query;
});
// once search results load, set up click listeners
searchControl.setSearchCompleteCallback(this, function(control, searcher, query) {
addResultClickListeners();
});
// draw the search results box
searchControl.draw(document.getElementById("leftSearchControl"), drawOptions);
// get query and execute the search
searchControl.execute(decodeURI(getQuery(location.hash)));
document.getElementById("search_autocomplete").focus();
addTabListeners();
}
// End of loadSearchResults
google.setOnLoadCallback(function(){
if (location.hash.indexOf("q=") == -1) {
// if there's no query in the url, don't search and make sure results are hidden
$('#searchResults').hide();
return;
} else {
// first time loading search results for this page
$('#searchResults').slideDown('slow', setStickyTop);
$("#search-close").removeClass("hide");
loadSearchResults();
}
}, true);
/* Adjust the scroll position to account for sticky header, only if the hash matches an id.
This does not handle <a name=""> tags. Some CSS fixes those, but only for reference docs. */
function offsetScrollForSticky() {
// Ignore if there's no search bar (some special pages have no header)
if ($("#search-container").length < 1) return;
var hash = escape(location.hash.substr(1));
var $matchingElement = $("#"+hash);
// Sanity check that there's an element with that ID on the page
if ($matchingElement.length) {
// If the position of the target element is near the top of the page (<20px, where we expect it
// to be because we need to move it down 60px to become in view), then move it down 60px
if (Math.abs($matchingElement.offset().top - $(window).scrollTop()) < 20) {
$(window).scrollTop($(window).scrollTop() - 60);
}
}
}
// when an event on the browser history occurs (back, forward, load) requery hash and do search
$(window).hashchange( function(){
// Ignore if there's no search bar (some special pages have no header)
if ($("#search-container").length < 1) return;
// If the hash isn't a search query or there's an error in the query,
// then adjust the scroll position to account for sticky header, then exit.
if ((location.hash.indexOf("q=") == -1) || (query == "undefined")) {
// If the results pane is open, close it.
if (!$("#searchResults").is(":hidden")) {
hideResults();
}
offsetScrollForSticky();
return;
}
// Otherwise, we have a search to do
var query = decodeURI(getQuery(location.hash));
searchControl.execute(query);
$('#searchResults').slideDown('slow', setStickyTop);
$("#search_autocomplete").focus();
$("#search-close").removeClass("hide");
updateResultTitle(query);
});
function updateResultTitle(query) {
$("#searchTitle").html("Results for <em>" + escapeHTML(query) + "</em>");
}
// forcefully regain key-up event control (previously jacked by search api)
$("#search_autocomplete").keyup(function(event) {
return search_changed(event, false, toRoot);
});
// add event listeners to each tab so we can track the browser history
function addTabListeners() {
var tabHeaders = $(".gsc-tabHeader");
for (var i = 0; i < tabHeaders.length; i++) {
$(tabHeaders[i]).attr("id",i).click(function() {
/*
// make a copy of the page numbers for the search left pane
setTimeout(function() {
// remove any residual page numbers
$('#searchResults .gsc-tabsArea .gsc-cursor-box.gs-bidi-start-align').remove();
// move the page numbers to the left position; make a clone,
// because the element is drawn to the DOM only once
// and because we're going to remove it (previous line),
// we need it to be available to move again as the user navigates
$('#searchResults .gsc-webResult .gsc-cursor-box.gs-bidi-start-align:visible')
.clone().appendTo('#searchResults .gsc-tabsArea');
}, 200);
*/
});
}
setTimeout(function(){$(tabHeaders[0]).click()},200);
}
// add analytics tracking events to each result link
function addResultClickListeners() {
$("#searchResults a.gs-title").each(function(index, link) {
// When user clicks enter for Google search results, track it
$(link).click(function() {
ga('send', 'event', 'Google Click', 'clicked: ' + $(this).attr('href'),
'query: ' + $("#search_autocomplete").val().toLowerCase());
});
});
}
function getQuery(hash) {
var queryParts = hash.split('=');
return queryParts[1];
}
/* returns the given string with all HTML brackets converted to entities
TODO: move this to the site's JS library */
function escapeHTML(string) {
return string.replace(/</g,"&lt;")
.replace(/>/g,"&gt;");
}
/* ######################################################## */
/* ################# JAVADOC REFERENCE ################### */
/* ######################################################## */
/* Initialize some droiddoc stuff, but only if we're in the reference */
if (location.pathname.indexOf("/reference") == 0) {
if(!(location.pathname.indexOf("/reference-gms/packages.html") == 0)
&& !(location.pathname.indexOf("/reference-gcm/packages.html") == 0)
&& !(location.pathname.indexOf("/reference/com/google") == 0)) {
$(document).ready(function() {
// init available apis based on user pref
changeApiLevel();
initSidenavHeightResize()
});
}
}
var API_LEVEL_COOKIE = "api_level";
var minLevel = 1;
var maxLevel = 1;
/******* SIDENAV DIMENSIONS ************/
function initSidenavHeightResize() {
// Change the drag bar size to nicely fit the scrollbar positions
var $dragBar = $(".ui-resizable-s");
$dragBar.css({'width': $dragBar.parent().width() - 5 + "px"});
$( "#resize-packages-nav" ).resizable({
containment: "#nav-panels",
handles: "s",
alsoResize: "#packages-nav",
resize: function(event, ui) { resizeNav(); }, /* resize the nav while dragging */
stop: function(event, ui) { saveNavPanels(); } /* once stopped, save the sizes to cookie */
});
}
function updateSidenavFixedWidth() {
if (!sticky) return;
$('#devdoc-nav').css({
'width' : $('#side-nav').css('width'),
'margin' : $('#side-nav').css('margin')
});
$('#devdoc-nav a.totop').css({'display':'block','width':$("#nav").innerWidth()+'px'});
initSidenavHeightResize();
}
function updateSidenavFullscreenWidth() {
if (!sticky) return;
$('#devdoc-nav').css({
'width' : $('#side-nav').css('width'),
'margin' : $('#side-nav').css('margin')
});
$('#devdoc-nav .totop').css({'left': 'inherit'});
initSidenavHeightResize();
}
function buildApiLevelSelector() {
maxLevel = SINCE_DATA.length;
var userApiLevel = parseInt(readCookie(API_LEVEL_COOKIE));
userApiLevel = userApiLevel == 0 ? maxLevel : userApiLevel; // If there's no cookie (zero), use the max by default
minLevel = parseInt($("#doc-api-level").attr("class"));
// Handle provisional api levels; the provisional level will always be the highest possible level
// Provisional api levels will also have a length; other stuff that's just missing a level won't,
// so leave those kinds of entities at the default level of 1 (for example, the R.styleable class)
if (isNaN(minLevel) && minLevel.length) {
minLevel = maxLevel;
}
var select = $("#apiLevelSelector").html("").change(changeApiLevel);
for (var i = maxLevel-1; i >= 0; i--) {
var option = $("<option />").attr("value",""+SINCE_DATA[i]).append(""+SINCE_DATA[i]);
// if (SINCE_DATA[i] < minLevel) option.addClass("absent"); // always false for strings (codenames)
select.append(option);
}
// get the DOM element and use setAttribute cuz IE6 fails when using jquery .attr('selected',true)
var selectedLevelItem = $("#apiLevelSelector option[value='"+userApiLevel+"']").get(0);
selectedLevelItem.setAttribute('selected',true);
}
function changeApiLevel() {
maxLevel = SINCE_DATA.length;
var selectedLevel = maxLevel;
selectedLevel = parseInt($("#apiLevelSelector option:selected").val());
toggleVisisbleApis(selectedLevel, "body");
writeCookie(API_LEVEL_COOKIE, selectedLevel, null);
if (selectedLevel < minLevel) {
var thing = ($("#jd-header").html().indexOf("package") != -1) ? "package" : "class";
$("#naMessage").show().html("<div><p><strong>This " + thing
+ " requires API level " + minLevel + " or higher.</strong></p>"
+ "<p>This document is hidden because your selected API level for the documentation is "
+ selectedLevel + ". You can change the documentation API level with the selector "
+ "above the left navigation.</p>"
+ "<p>For more information about specifying the API level your app requires, "
+ "read <a href='" + toRoot + "training/basics/supporting-devices/platforms.html'"
+ ">Supporting Different Platform Versions</a>.</p>"
+ "<input type='button' value='OK, make this page visible' "
+ "title='Change the API level to " + minLevel + "' "
+ "onclick='$(\"#apiLevelSelector\").val(\"" + minLevel + "\");changeApiLevel();' />"
+ "</div>");
} else {
$("#naMessage").hide();
}
}
function toggleVisisbleApis(selectedLevel, context) {
var apis = $(".api",context);
apis.each(function(i) {
var obj = $(this);
var className = obj.attr("class");
var apiLevelIndex = className.lastIndexOf("-")+1;
var apiLevelEndIndex = className.indexOf(" ", apiLevelIndex);
apiLevelEndIndex = apiLevelEndIndex != -1 ? apiLevelEndIndex : className.length;
var apiLevel = className.substring(apiLevelIndex, apiLevelEndIndex);
if (apiLevel.length == 0) { // for odd cases when the since data is actually missing, just bail
return;
}
apiLevel = parseInt(apiLevel);
// Handle provisional api levels; if this item's level is the provisional one, set it to the max
var selectedLevelNum = parseInt(selectedLevel)
var apiLevelNum = parseInt(apiLevel);
if (isNaN(apiLevelNum)) {
apiLevelNum = maxLevel;
}
// Grey things out that aren't available and give a tooltip title
if (apiLevelNum > selectedLevelNum) {
obj.addClass("absent").attr("title","Requires API Level \""
+ apiLevel + "\" or higher. To reveal, change the target API level "
+ "above the left navigation.");
}
else obj.removeClass("absent").removeAttr("title");
});
}
/* ################# SIDENAV TREE VIEW ################### */
function new_node(me, mom, text, link, children_data, api_level)
{
var node = new Object();
node.children = Array();
node.children_data = children_data;
node.depth = mom.depth + 1;
node.li = document.createElement("li");
mom.get_children_ul().appendChild(node.li);
node.label_div = document.createElement("div");
node.label_div.className = "label";
if (api_level != null) {
$(node.label_div).addClass("api");
$(node.label_div).addClass("api-level-"+api_level);
}
node.li.appendChild(node.label_div);
if (children_data != null) {
node.expand_toggle = document.createElement("a");
node.expand_toggle.href = "javascript:void(0)";
node.expand_toggle.onclick = function() {
if (node.expanded) {
$(node.get_children_ul()).slideUp("fast");
node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
node.expanded = false;
} else {
expand_node(me, node);
}
};
node.label_div.appendChild(node.expand_toggle);
node.plus_img = document.createElement("img");
node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
node.plus_img.className = "plus";
node.plus_img.width = "8";
node.plus_img.border = "0";
node.expand_toggle.appendChild(node.plus_img);
node.expanded = false;
}
var a = document.createElement("a");
node.label_div.appendChild(a);
node.label = document.createTextNode(text);
a.appendChild(node.label);
if (link) {
a.href = me.toroot + link;
} else {
if (children_data != null) {
a.className = "nolink";
a.href = "javascript:void(0)";
a.onclick = node.expand_toggle.onclick;
// This next line shouldn't be necessary. I'll buy a beer for the first
// person who figures out how to remove this line and have the link
// toggle shut on the first try. --joeo@android.com
node.expanded = false;
}
}
node.children_ul = null;
node.get_children_ul = function() {
if (!node.children_ul) {
node.children_ul = document.createElement("ul");
node.children_ul.className = "children_ul";
node.children_ul.style.display = "none";
node.li.appendChild(node.children_ul);
}
return node.children_ul;
};
return node;
}
function expand_node(me, node)
{
if (node.children_data && !node.expanded) {
if (node.children_visited) {
$(node.get_children_ul()).slideDown("fast");
} else {
get_node(me, node);
if ($(node.label_div).hasClass("absent")) {
$(node.get_children_ul()).addClass("absent");
}
$(node.get_children_ul()).slideDown("fast");
}
node.plus_img.src = me.toroot + "assets/images/triangle-opened-small.png";
node.expanded = true;
// perform api level toggling because new nodes are new to the DOM
var selectedLevel = $("#apiLevelSelector option:selected").val();
toggleVisisbleApis(selectedLevel, "#side-nav");
}
}
function get_node(me, mom)
{
mom.children_visited = true;
for (var i in mom.children_data) {
var node_data = mom.children_data[i];
mom.children[i] = new_node(me, mom, node_data[0], node_data[1],
node_data[2], node_data[3]);
}
}
function this_page_relative(toroot)
{
var full = document.location.pathname;
var file = "";
if (toroot.substr(0, 1) == "/") {
if (full.substr(0, toroot.length) == toroot) {
return full.substr(toroot.length);
} else {
// the file isn't under toroot. Fail.
return null;
}
} else {
if (toroot != "./") {
toroot = "./" + toroot;
}
do {
if (toroot.substr(toroot.length-3, 3) == "../" || toroot == "./") {
var pos = full.lastIndexOf("/");
file = full.substr(pos) + file;
full = full.substr(0, pos);
toroot = toroot.substr(0, toroot.length-3);
}
} while (toroot != "" && toroot != "/");
return file.substr(1);
}
}
function find_page(url, data)
{
var nodes = data;
var result = null;
for (var i in nodes) {
var d = nodes[i];
if (d[1] == url) {
return new Array(i);
}
else if (d[2] != null) {
result = find_page(url, d[2]);
if (result != null) {
return (new Array(i).concat(result));
}
}
}
return null;
}
function init_default_navtree(toroot) {
// load json file for navtree data
$.getScript(toRoot + 'navtree_data.js', function(data, textStatus, jqxhr) {
// when the file is loaded, initialize the tree
if(jqxhr.status === 200) {
init_navtree("tree-list", toroot, NAVTREE_DATA);
}
});
// perform api level toggling because because the whole tree is new to the DOM
var selectedLevel = $("#apiLevelSelector option:selected").val();
toggleVisisbleApis(selectedLevel, "#side-nav");
}
function init_navtree(navtree_id, toroot, root_nodes)
{
var me = new Object();
me.toroot = toroot;
me.node = new Object();
me.node.li = document.getElementById(navtree_id);
me.node.children_data = root_nodes;
me.node.children = new Array();
me.node.children_ul = document.createElement("ul");
me.node.get_children_ul = function() { return me.node.children_ul; };
//me.node.children_ul.className = "children_ul";
me.node.li.appendChild(me.node.children_ul);
me.node.depth = 0;
get_node(me, me.node);
me.this_page = this_page_relative(toroot);
me.breadcrumbs = find_page(me.this_page, root_nodes);
if (me.breadcrumbs != null && me.breadcrumbs.length != 0) {
var mom = me.node;
for (var i in me.breadcrumbs) {
var j = me.breadcrumbs[i];
mom = mom.children[j];
expand_node(me, mom);
}
mom.label_div.className = mom.label_div.className + " selected";
addLoadEvent(function() {
scrollIntoView("nav-tree");
});
}
}
/* TODO: eliminate redundancy with non-google functions */
function init_google_navtree(navtree_id, toroot, root_nodes)
{
var me = new Object();
me.toroot = toroot;
me.node = new Object();
me.node.li = document.getElementById(navtree_id);
if (!me.node.li) {
return;
}
me.node.children_data = root_nodes;
me.node.children = new Array();
me.node.children_ul = document.createElement("ul");
me.node.get_children_ul = function() { return me.node.children_ul; };
//me.node.children_ul.className = "children_ul";
me.node.li.appendChild(me.node.children_ul);
me.node.depth = 0;
get_google_node(me, me.node);
}
function new_google_node(me, mom, text, link, children_data, api_level)
{
var node = new Object();
var child;
node.children = Array();
node.children_data = children_data;
node.depth = mom.depth + 1;
node.get_children_ul = function() {
if (!node.children_ul) {
node.children_ul = document.createElement("ul");
node.children_ul.className = "tree-list-children";
node.li.appendChild(node.children_ul);
}
return node.children_ul;
};
node.li = document.createElement("li");
mom.get_children_ul().appendChild(node.li);
if(link) {
child = document.createElement("a");
}
else {
child = document.createElement("span");
child.className = "tree-list-subtitle";
}
if (children_data != null) {
node.li.className="nav-section";
node.label_div = document.createElement("div");
node.label_div.className = "nav-section-header-ref";
node.li.appendChild(node.label_div);
get_google_node(me, node);
node.label_div.appendChild(child);
}
else {
node.li.appendChild(child);
}
if(link) {
child.href = me.toroot + link;
}
node.label = document.createTextNode(text);
child.appendChild(node.label);
node.children_ul = null;
return node;
}
function get_google_node(me, mom)
{
mom.children_visited = true;
var linkText;
for (var i in mom.children_data) {
var node_data = mom.children_data[i];
linkText = node_data[0];
if(linkText.match("^"+"com.google.android")=="com.google.android"){
linkText = linkText.substr(19, linkText.length);
}
mom.children[i] = new_google_node(me, mom, linkText, node_data[1],
node_data[2], node_data[3]);
}
}
/****** NEW version of script to build google and sample navs dynamically ******/
// TODO: update Google reference docs to tolerate this new implementation
var NODE_NAME = 0;
var NODE_HREF = 1;
var NODE_GROUP = 2;
var NODE_TAGS = 3;
var NODE_CHILDREN = 4;
function init_google_navtree2(navtree_id, data)
{
var $containerUl = $("#"+navtree_id);
for (var i in data) {
var node_data = data[i];
$containerUl.append(new_google_node2(node_data));
}
// Make all third-generation list items 'sticky' to prevent them from collapsing
$containerUl.find('li li li.nav-section').addClass('sticky');
initExpandableNavItems("#"+navtree_id);
}
function new_google_node2(node_data)
{
var linkText = node_data[NODE_NAME];
if(linkText.match("^"+"com.google.android")=="com.google.android"){
linkText = linkText.substr(19, linkText.length);
}
var $li = $('<li>');
var $a;
if (node_data[NODE_HREF] != null) {
$a = $('<a href="' + toRoot + node_data[NODE_HREF] + '" title="' + linkText + '" >'
+ linkText + '</a>');
} else {
$a = $('<a href="#" onclick="return false;" title="' + linkText + '" >'
+ linkText + '/</a>');
}
var $childUl = $('<ul>');
if (node_data[NODE_CHILDREN] != null) {
$li.addClass("nav-section");
$a = $('<div class="nav-section-header">').append($a);
if (node_data[NODE_HREF] == null) $a.addClass('empty');
for (var i in node_data[NODE_CHILDREN]) {
var child_node_data = node_data[NODE_CHILDREN][i];
$childUl.append(new_google_node2(child_node_data));
}
$li.append($childUl);
}
$li.prepend($a);
return $li;
}
function showGoogleRefTree() {
init_default_google_navtree(toRoot);
init_default_gcm_navtree(toRoot);
}
function init_default_google_navtree(toroot) {
// load json file for navtree data
$.getScript(toRoot + 'gms_navtree_data.js', function(data, textStatus, jqxhr) {
// when the file is loaded, initialize the tree
if(jqxhr.status === 200) {
init_google_navtree("gms-tree-list", toroot, GMS_NAVTREE_DATA);
highlightSidenav();
resizeNav();
}
});
}
function init_default_gcm_navtree(toroot) {
// load json file for navtree data
$.getScript(toRoot + 'gcm_navtree_data.js', function(data, textStatus, jqxhr) {
// when the file is loaded, initialize the tree
if(jqxhr.status === 200) {
init_google_navtree("gcm-tree-list", toroot, GCM_NAVTREE_DATA);
highlightSidenav();
resizeNav();
}
});
}
function showSamplesRefTree() {
init_default_samples_navtree(toRoot);
}
function init_default_samples_navtree(toroot) {
// load json file for navtree data
$.getScript(toRoot + 'samples_navtree_data.js', function(data, textStatus, jqxhr) {
// when the file is loaded, initialize the tree
if(jqxhr.status === 200) {
// hack to remove the "about the samples" link then put it back in
// after we nuke the list to remove the dummy static list of samples
var $firstLi = $("#nav.samples-nav > li:first-child").clone();
$("#nav.samples-nav").empty();
$("#nav.samples-nav").append($firstLi);
init_google_navtree2("nav.samples-nav", SAMPLES_NAVTREE_DATA);
highlightSidenav();
resizeNav();
if ($("#jd-content #samples").length) {
showSamples();
}
}
});
}
/* TOGGLE INHERITED MEMBERS */
/* Toggle an inherited class (arrow toggle)
* @param linkObj The link that was clicked.
* @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
* 'null' to simply toggle.
*/
function toggleInherited(linkObj, expand) {
var base = linkObj.getAttribute("id");
var list = document.getElementById(base + "-list");
var summary = document.getElementById(base + "-summary");
var trigger = document.getElementById(base + "-trigger");
var a = $(linkObj);
if ( (expand == null && a.hasClass("closed")) || expand ) {
list.style.display = "none";
summary.style.display = "block";
trigger.src = toRoot + "assets/images/triangle-opened.png";
a.removeClass("closed");
a.addClass("opened");
} else if ( (expand == null && a.hasClass("opened")) || (expand == false) ) {
list.style.display = "block";
summary.style.display = "none";
trigger.src = toRoot + "assets/images/triangle-closed.png";
a.removeClass("opened");
a.addClass("closed");
}
return false;
}
/* Toggle all inherited classes in a single table (e.g. all inherited methods)
* @param linkObj The link that was clicked.
* @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
* 'null' to simply toggle.
*/
function toggleAllInherited(linkObj, expand) {
var a = $(linkObj);
var table = $(a.parent().parent().parent()); // ugly way to get table/tbody
var expandos = $(".jd-expando-trigger", table);
if ( (expand == null && a.text() == "[Expand]") || expand ) {
expandos.each(function(i) {
toggleInherited(this, true);
});
a.text("[Collapse]");
} else if ( (expand == null && a.text() == "[Collapse]") || (expand == false) ) {
expandos.each(function(i) {
toggleInherited(this, false);
});
a.text("[Expand]");
}
return false;
}
/* Toggle all inherited members in the class (link in the class title)
*/
function toggleAllClassInherited() {
var a = $("#toggleAllClassInherited"); // get toggle link from class title
var toggles = $(".toggle-all", $("#body-content"));
if (a.text() == "[Expand All]") {
toggles.each(function(i) {
toggleAllInherited(this, true);
});
a.text("[Collapse All]");
} else {
toggles.each(function(i) {
toggleAllInherited(this, false);
});
a.text("[Expand All]");
}
return false;
}
/* Expand all inherited members in the class. Used when initiating page search */
function ensureAllInheritedExpanded() {
var toggles = $(".toggle-all", $("#body-content"));
toggles.each(function(i) {
toggleAllInherited(this, true);
});
$("#toggleAllClassInherited").text("[Collapse All]");
}
/* HANDLE KEY EVENTS
* - Listen for Ctrl+F (Cmd on Mac) and expand all inherited members (to aid page search)
*/
var agent = navigator['userAgent'].toLowerCase();
var mac = agent.indexOf("macintosh") != -1;
$(document).keydown( function(e) {
var control = mac ? e.metaKey && !e.ctrlKey : e.ctrlKey; // get ctrl key
if (control && e.which == 70) { // 70 is "F"
ensureAllInheritedExpanded();
}
});
/* On-demand functions */
/** Move sample code line numbers out of PRE block and into non-copyable column */
function initCodeLineNumbers() {
var numbers = $("#codesample-block a.number");
if (numbers.length) {
$("#codesample-line-numbers").removeClass("hidden").append(numbers);
}
$(document).ready(function() {
// select entire line when clicked
$("span.code-line").click(function() {
if (!shifted) {
selectText(this);
}
});
// invoke line link on double click
$(".code-line").dblclick(function() {
document.location.hash = $(this).attr('id');
});
// highlight the line when hovering on the number
$("#codesample-line-numbers a.number").mouseover(function() {
var id = $(this).attr('href');
$(id).css('background','#e7e7e7');
});
$("#codesample-line-numbers a.number").mouseout(function() {
var id = $(this).attr('href');
$(id).css('background','none');
});
});
}
// create SHIFT key binder to avoid the selectText method when selecting multiple lines
var shifted = false;
$(document).bind('keyup keydown', function(e){shifted = e.shiftKey; return true;} );
// courtesy of jasonedelman.com
function selectText(element) {
var doc = document
, range, selection
;
if (doc.body.createTextRange) { //ms
range = doc.body.createTextRange();
range.moveToElementText(element);
range.select();
} else if (window.getSelection) { //all others
selection = window.getSelection();
range = doc.createRange();
range.selectNodeContents(element);
selection.removeAllRanges();
selection.addRange(range);
}
}
/** Display links and other information about samples that match the
group specified by the URL */
function showSamples() {
var group = $("#samples").attr('class');
$("#samples").html("<p>Here are some samples for <b>" + group + "</b> apps:</p>");
var $ul = $("<ul>");
$selectedLi = $("#nav li.selected");
$selectedLi.children("ul").children("li").each(function() {
var $li = $("<li>").append($(this).find("a").first().clone());
$ul.append($li);
});
$("#samples").append($ul);
}
/* ########################################################## */
/* ################### RESOURCE CARDS ##################### */
/* ########################################################## */
/** Handle resource queries, collections, and grids (sections). Requires
jd_tag_helpers.js and the *_unified_data.js to be loaded. */
(function() {
// Prevent the same resource from being loaded more than once per page.
var addedPageResources = {};
$(document).ready(function() {
// Need to initialize hero carousel before other sections for dedupe
// to work correctly.
$('[data-carousel-query]').dacCarouselQuery();
$('.resource-widget').each(function() {
initResourceWidget(this);
});
/* Pass the line height to ellipsisfade() to adjust the height of the
text container to show the max number of lines possible, without
showing lines that are cut off. This works with the css ellipsis
classes to fade last text line and apply an ellipsis char. */
//card text currently uses 20px line height.
var lineHeight = 20;
$('.card-info .text').ellipsisfade(lineHeight);
});
/*
Three types of resource layouts:
Flow - Uses a fixed row-height flow using float left style.
Carousel - Single card slideshow all same dimension absolute.
Stack - Uses fixed columns and flexible element height.
*/
function initResourceWidget(widget) {
var $widget = $(widget);
var isFlow = $widget.hasClass('resource-flow-layout'),
isCarousel = $widget.hasClass('resource-carousel-layout'),
isStack = $widget.hasClass('resource-stack-layout');
// remove illegal col-x class which is not relevant anymore thanks to responsive styles.
var m = $widget.get(0).className.match(/\bcol-(\d+)\b/);
if (m && !$widget.is('.cols > *')) {
$widget.removeClass('col-' + m[1]);
}
var opts = {
cardSizes: ($widget.data('cardsizes') || '').split(','),
maxResults: parseInt($widget.data('maxresults') || '100', 10),
initialResults: $widget.data('initialResults'),
itemsPerPage: $widget.data('itemsperpage'),
sortOrder: $widget.data('sortorder'),
query: $widget.data('query'),
section: $widget.data('section'),
/* Added by LFL 6/6/14 */
resourceStyle: $widget.data('resourcestyle') || 'card',
stackSort: $widget.data('stacksort') || 'true'
};
// run the search for the set of resources to show
var resources = buildResourceList(opts);
if (isFlow) {
drawResourcesFlowWidget($widget, opts, resources);
} else if (isCarousel) {
drawResourcesCarouselWidget($widget, opts, resources);
} else if (isStack) {
/* Looks like this got removed and is not used, so repurposing for the
homepage style layout.
Modified by LFL 6/6/14
*/
//var sections = buildSectionList(opts);
opts['numStacks'] = $widget.data('numstacks');
drawResourcesStackWidget($widget, opts, resources/*, sections*/);
}
}
/* Initializes a Resource Carousel Widget */
function drawResourcesCarouselWidget($widget, opts, resources) {
$widget.empty();
var plusone = false; // stop showing plusone buttons on cards
$widget.addClass('resource-card slideshow-container')
.append($('<a>').addClass('slideshow-prev').text('Prev'))
.append($('<a>').addClass('slideshow-next').text('Next'));
var css = { 'width': $widget.width() + 'px',
'height': $widget.height() + 'px' };
var $ul = $('<ul>');
for (var i = 0; i < resources.length; ++i) {
var $card = $('<a>')
.attr('href', cleanUrl(resources[i].url))
.decorateResourceCard(resources[i],plusone);
$('<li>').css(css)
.append($card)
.appendTo($ul);
}
$('<div>').addClass('frame')
.append($ul)
.appendTo($widget);
$widget.dacSlideshow({
auto: true,
btnPrev: '.slideshow-prev',
btnNext: '.slideshow-next'
});
};
/* Initializes a Resource Card Stack Widget (column-based layout)
Modified by LFL 6/6/14
*/
function drawResourcesStackWidget($widget, opts, resources, sections) {
// Don't empty widget, grab all items inside since they will be the first
// items stacked, followed by the resource query
var plusone = false; // stop showing plusone buttons on cards
var cards = $widget.find('.resource-card').detach().toArray();
var numStacks = opts.numStacks || 1;
var $stacks = [];
var urlString;
for (var i = 0; i < numStacks; ++i) {
$stacks[i] = $('<div>').addClass('resource-card-stack')
.appendTo($widget);
}
var sectionResources = [];
// Extract any subsections that are actually resource cards
if (sections) {
for (var i = 0; i < sections.length; ++i) {
if (!sections[i].sections || !sections[i].sections.length) {
// Render it as a resource card
sectionResources.push(
$('<a>')
.addClass('resource-card section-card')
.attr('href', cleanUrl(sections[i].resource.url))
.decorateResourceCard(sections[i].resource,plusone)[0]
);
} else {
cards.push(
$('<div>')
.addClass('resource-card section-card-menu')
.decorateResourceSection(sections[i],plusone)[0]
);
}
}
}
cards = cards.concat(sectionResources);
for (var i = 0; i < resources.length; ++i) {
var $card = createResourceElement(resources[i], opts);
if (opts.resourceStyle.indexOf('related') > -1) {
$card.addClass('related-card');
}
cards.push($card[0]);
}
if (opts.stackSort != 'false') {
for (var i = 0; i < cards.length; ++i) {
// Find the stack with the shortest height, but give preference to
// left to right order.
var minHeight = $stacks[0].height();
var minIndex = 0;
for (var j = 1; j < numStacks; ++j) {
var height = $stacks[j].height();
if (height < minHeight - 45) {
minHeight = height;
minIndex = j;
}
}
$stacks[minIndex].append($(cards[i]));
}
}
};
/*
Create a resource card using the given resource object and a list of html
configured options. Returns a jquery object containing the element.
*/
function createResourceElement(resource, opts, plusone) {
var $el;
// The difference here is that generic cards are not entirely clickable
// so its a div instead of an a tag, also the generic one is not given
// the resource-card class so it appears with a transparent background
// and can be styled in whatever way the css setup.
if (opts.resourceStyle == 'generic') {
$el = $('<div>')
.addClass('resource')
.attr('href', cleanUrl(resource.url))
.decorateResource(resource, opts);
} else {
var cls = 'resource resource-card';
$el = $('<a>')
.addClass(cls)
.attr('href', cleanUrl(resource.url))
.decorateResourceCard(resource, plusone);
}
return $el;
}
function createResponsiveFlowColumn(cardSize) {
var cardWidth = parseInt(cardSize.match(/(\d+)/)[1], 10);
var column = $('<div>').addClass('col-' + (cardWidth / 3) + 'of6');
if (cardWidth < 9) {
column.addClass('col-tablet-1of2');
} else if (cardWidth > 9 && cardWidth < 18) {
column.addClass('col-tablet-1of1');
}
if (cardWidth < 18) {
column.addClass('col-mobile-1of1')
}
return column;
}
/* Initializes a flow widget, see distribute.scss for generating accompanying css */
function drawResourcesFlowWidget($widget, opts, resources) {
$widget.empty().addClass('cols');
var cardSizes = opts.cardSizes || ['6x6'];
var initialResults = opts.initialResults || resources.length;
var i = 0, j = 0;
var plusone = false; // stop showing plusone buttons on cards
var cardParent = $widget;
while (i < resources.length) {
if (i === initialResults && initialResults < resources.length) {
// Toggle remaining cards
cardParent = $('<div class="dac-toggle-content clearfix">').appendTo($widget);
$widget.addClass('dac-toggle');
$('<div class="col-1of1 dac-section-links dac-text-center">')
.append(
$('<div class="dac-section-link" data-toggle="section">')
.append('<span class="dac-toggle-expand">More<i class="dac-sprite dac-auto-unfold-more"></i></span>')
.append('<span class="dac-toggle-collapse">Less<i class="dac-sprite dac-auto-unfold-less"></i></span>')
)
.appendTo($widget)
}
var cardSize = cardSizes[j++ % cardSizes.length];
cardSize = cardSize.replace(/^\s+|\s+$/,'');
var column = createResponsiveFlowColumn(cardSize).appendTo(cardParent);
// A stack has a third dimension which is the number of stacked items
var isStack = cardSize.match(/(\d+)x(\d+)x(\d+)/);
var stackCount = 0;
var $stackDiv = null;
if (isStack) {
// Create a stack container which should have the dimensions defined
// by the product of the items inside.
$stackDiv = $('<div>').addClass('resource-card-stack resource-card-' + isStack[1]
+ 'x' + isStack[2] * isStack[3]) .appendTo(column);
}
// Build each stack item or just a single item
do {
var resource = resources[i];
var $card = createResourceElement(resources[i], opts, plusone);
$card.addClass('resource-card-' + cardSize +
' resource-card-' + resource.type);
if (isStack) {
$card.addClass('resource-card-' + isStack[1] + 'x' + isStack[2]);
if (++stackCount == parseInt(isStack[3])) {
$card.addClass('resource-card-row-stack-last');
stackCount = 0;
}
} else {
stackCount = 0;
}
$card.appendTo($stackDiv || column);
} while (++i < resources.length && stackCount > 0);
}
}
/* Build a site map of resources using a section as a root. */
function buildSectionList(opts) {
if (opts.section && SECTION_BY_ID[opts.section]) {
return SECTION_BY_ID[opts.section].sections || [];
}
return [];
}
function buildResourceList(opts) {
return $.queryResources(opts);
}
$.queryResources = function(opts) {
var maxResults = opts.maxResults || 100;
var query = opts.query || '';
var expressions = parseResourceQuery(query);
var addedResourceIndices = {};
var results = [];
for (var i = 0; i < expressions.length; i++) {
var clauses = expressions[i];
// build initial set of resources from first clause
var firstClause = clauses[0];
var resources = [];
switch (firstClause.attr) {
case 'type':
resources = ALL_RESOURCES_BY_TYPE[firstClause.value];
break;
case 'lang':
resources = ALL_RESOURCES_BY_LANG[firstClause.value];
break;
case 'tag':
resources = ALL_RESOURCES_BY_TAG[firstClause.value];
break;
case 'collection':
var urls = RESOURCE_COLLECTIONS[firstClause.value].resources || [];
resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
break;
case 'section':
var urls = SITE_MAP[firstClause.value].sections || [];
resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
break;
}
// console.log(firstClause.attr + ':' + firstClause.value);
resources = resources || [];
// use additional clauses to filter corpus
if (clauses.length > 1) {
var otherClauses = clauses.slice(1);
resources = resources.filter(getResourceMatchesClausesFilter(otherClauses));
}
// filter out resources already added
if (i > 1) {
resources = resources.filter(getResourceNotAlreadyAddedFilter(addedResourceIndices));
}
// add to list of already added indices
for (var j = 0; j < resources.length; j++) {
if (resources[j]) {
addedResourceIndices[resources[j].index] = 1;
}
}
// concat to final results list
results = results.concat(resources);
}
if (opts.sortOrder && results.length) {
var attr = opts.sortOrder;
if (opts.sortOrder == 'random') {
var i = results.length, j, temp;
while (--i) {
j = Math.floor(Math.random() * (i + 1));
temp = results[i];
results[i] = results[j];
results[j] = temp;
}
} else {
var desc = attr.charAt(0) == '-';
if (desc) {
attr = attr.substring(1);
}
results = results.sort(function(x,y) {
return (desc ? -1 : 1) * (parseInt(x[attr], 10) - parseInt(y[attr], 10));
});
}
}
results = results.filter(getResourceNotAlreadyAddedFilter(addedPageResources));
results = results.slice(0, maxResults);
for (var j = 0; j < results.length; ++j) {
addedPageResources[results[j].index] = 1;
}
return results;
}
function getResourceNotAlreadyAddedFilter(addedResourceIndices) {
return function(resource) {
return resource && !addedResourceIndices[resource.index];
};
}
function getResourceMatchesClausesFilter(clauses) {
return function(resource) {
return doesResourceMatchClauses(resource, clauses);
};
}
function doesResourceMatchClauses(resource, clauses) {
for (var i = 0; i < clauses.length; i++) {
var map;
switch (clauses[i].attr) {
case 'type':
map = IS_RESOURCE_OF_TYPE[clauses[i].value];
break;
case 'lang':
map = IS_RESOURCE_IN_LANG[clauses[i].value];
break;
case 'tag':
map = IS_RESOURCE_TAGGED[clauses[i].value];
break;
}
if (!map || (!!clauses[i].negative ? map[resource.index] : !map[resource.index])) {
return clauses[i].negative;
}
}
return true;
}
function cleanUrl(url)
{
if (url && url.indexOf('//') === -1) {
url = toRoot + url;
}
return url;
}
function parseResourceQuery(query) {
// Parse query into array of expressions (expression e.g. 'tag:foo + type:video')
var expressions = [];
var expressionStrs = query.split(',') || [];
for (var i = 0; i < expressionStrs.length; i++) {
var expr = expressionStrs[i] || '';
// Break expression into clauses (clause e.g. 'tag:foo')
var clauses = [];
var clauseStrs = expr.split(/(?=[\+\-])/);
for (var j = 0; j < clauseStrs.length; j++) {
var clauseStr = clauseStrs[j] || '';
// Get attribute and value from clause (e.g. attribute='tag', value='foo')
var parts = clauseStr.split(':');
var clause = {};
clause.attr = parts[0].replace(/^\s+|\s+$/g,'');
if (clause.attr) {
if (clause.attr.charAt(0) == '+') {
clause.attr = clause.attr.substring(1);
} else if (clause.attr.charAt(0) == '-') {
clause.negative = true;
clause.attr = clause.attr.substring(1);
}
}
if (parts.length > 1) {
clause.value = parts[1].replace(/^\s+|\s+$/g,'');
}
clauses.push(clause);
}
if (!clauses.length) {
continue;
}
expressions.push(clauses);
}
return expressions;
}
})();
(function($) {
/*
Utility method for creating dom for the description area of a card.
Used in decorateResourceCard and decorateResource.
*/
function buildResourceCardDescription(resource, plusone) {
var $description = $('<div>').addClass('description ellipsis');
$description.append($('<div>').addClass('text').html(resource.summary));
if (resource.cta) {
$description.append($('<a>').addClass('cta').html(resource.cta));
}
if (plusone) {
var plusurl = resource.url.indexOf("//") > -1 ? resource.url :
"//developer.android.com/" + resource.url;
$description.append($('<div>').addClass('util')
.append($('<div>').addClass('g-plusone')
.attr('data-size', 'small')
.attr('data-align', 'right')
.attr('data-href', plusurl)));
}
return $description;
}
/* Simple jquery function to create dom for a standard resource card */
$.fn.decorateResourceCard = function(resource,plusone) {
var section = resource.group || resource.type;
var imgUrl = resource.image ||
'assets/images/resource-card-default-android.jpg';
if (imgUrl.indexOf('//') === -1) {
imgUrl = toRoot + imgUrl;
}
if (resource.type === 'youtube') {
$('<div>').addClass('play-button')
.append($('<i class="dac-sprite dac-play-white">'))
.appendTo(this);
}
$('<div>').addClass('card-bg')
.css('background-image', 'url(' + (imgUrl || toRoot +
'assets/images/resource-card-default-android.jpg') + ')')
.appendTo(this);
$('<div>').addClass('card-info' + (!resource.summary ? ' empty-desc' : ''))
.append($('<div>').addClass('section').text(section))
.append($('<div>').addClass('title').html(resource.title))
.append(buildResourceCardDescription(resource, plusone))
.appendTo(this);
return this;
};
/* Simple jquery function to create dom for a resource section card (menu) */
$.fn.decorateResourceSection = function(section,plusone) {
var resource = section.resource;
//keep url clean for matching and offline mode handling
var urlPrefix = resource.image.indexOf("//") > -1 ? "" : toRoot;
var $base = $('<a>')
.addClass('card-bg')
.attr('href', resource.url)
.append($('<div>').addClass('card-section-icon')
.append($('<div>').addClass('icon'))
.append($('<div>').addClass('section').html(resource.title)))
.appendTo(this);
var $cardInfo = $('<div>').addClass('card-info').appendTo(this);
if (section.sections && section.sections.length) {
// Recurse the section sub-tree to find a resource image.
var stack = [section];
while (stack.length) {
if (stack[0].resource.image) {
$base.css('background-image', 'url(' + urlPrefix + stack[0].resource.image + ')');
break;
}
if (stack[0].sections) {
stack = stack.concat(stack[0].sections);
}
stack.shift();
}
var $ul = $('<ul>')
.appendTo($cardInfo);
var max = section.sections.length > 3 ? 3 : section.sections.length;
for (var i = 0; i < max; ++i) {
var subResource = section.sections[i];
if (!plusone) {
$('<li>')
.append($('<a>').attr('href', subResource.url)
.append($('<div>').addClass('title').html(subResource.title))
.append($('<div>').addClass('description ellipsis')
.append($('<div>').addClass('text').html(subResource.summary))
.append($('<div>').addClass('util'))))
.appendTo($ul);
} else {
$('<li>')
.append($('<a>').attr('href', subResource.url)
.append($('<div>').addClass('title').html(subResource.title))
.append($('<div>').addClass('description ellipsis')
.append($('<div>').addClass('text').html(subResource.summary))
.append($('<div>').addClass('util')
.append($('<div>').addClass('g-plusone')
.attr('data-size', 'small')
.attr('data-align', 'right')
.attr('data-href', resource.url)))))
.appendTo($ul);
}
}
// Add a more row
if (max < section.sections.length) {
$('<li>')
.append($('<a>').attr('href', resource.url)
.append($('<div>')
.addClass('title')
.text('More')))
.appendTo($ul);
}
} else {
// No sub-resources, just render description?
}
return this;
};
/* Render other types of resource styles that are not cards. */
$.fn.decorateResource = function(resource, opts) {
var imgUrl = resource.image ||
'assets/images/resource-card-default-android.jpg';
var linkUrl = resource.url;
if (imgUrl.indexOf('//') === -1) {
imgUrl = toRoot + imgUrl;
}
if (linkUrl && linkUrl.indexOf('//') === -1) {
linkUrl = toRoot + linkUrl;
}
$(this).append(
$('<div>').addClass('image')
.css('background-image', 'url(' + imgUrl + ')'),
$('<div>').addClass('info').append(
$('<h4>').addClass('title').html(resource.title),
$('<p>').addClass('summary').html(resource.summary),
$('<a>').attr('href', linkUrl).addClass('cta').html('Learn More')
)
);
return this;
};
})(jQuery);
/* Calculate the vertical area remaining */
(function($) {
$.fn.ellipsisfade= function(lineHeight) {
this.each(function() {
// get element text
var $this = $(this);
var remainingHeight = $this.parent().parent().height();
$this.parent().siblings().each(function ()
{
if ($(this).is(":visible")) {
var h = $(this).outerHeight(true);
remainingHeight = remainingHeight - h;
}
});
adjustedRemainingHeight = ((remainingHeight)/lineHeight>>0)*lineHeight
$this.parent().css({'height': adjustedRemainingHeight});
$this.css({'height': "auto"});
});
return this;
};
}) (jQuery);
/*
Fullscreen Carousel
The following allows for an area at the top of the page that takes over the
entire browser height except for its top offset and an optional bottom
padding specified as a data attribute.
HTML:
<div class="fullscreen-carousel">
<div class="fullscreen-carousel-content">
<!-- content here -->
</div>
<div class="fullscreen-carousel-content">
<!-- content here -->
</div>
etc ...
</div>
Control over how the carousel takes over the screen can mostly be defined in
a css file. Setting min-height on the .fullscreen-carousel-content elements
will prevent them from shrinking to far vertically when the browser is very
short, and setting max-height on the .fullscreen-carousel itself will prevent
the area from becoming to long in the case that the browser is stretched very
tall.
There is limited functionality for having multiple sections since that request
was removed, but it is possible to add .next-arrow and .prev-arrow elements to
scroll between multiple content areas.
*/
(function() {
$(document).ready(function() {
$('.fullscreen-carousel').each(function() {
initWidget(this);
});
});
function initWidget(widget) {
var $widget = $(widget);
var topOffset = $widget.offset().top;
var padBottom = parseInt($widget.data('paddingbottom')) || 0;
var maxHeight = 0;
var minHeight = 0;
var $content = $widget.find('.fullscreen-carousel-content');
var $nextArrow = $widget.find('.next-arrow');
var $prevArrow = $widget.find('.prev-arrow');
var $curSection = $($content[0]);
if ($content.length <= 1) {
$nextArrow.hide();
$prevArrow.hide();
} else {
$nextArrow.click(function() {
var index = ($content.index($curSection) + 1);
$curSection.hide();
$curSection = $($content[index >= $content.length ? 0 : index]);
$curSection.show();
});
$prevArrow.click(function() {
var index = ($content.index($curSection) - 1);
$curSection.hide();
$curSection = $($content[index < 0 ? $content.length - 1 : 0]);
$curSection.show();
});
}
// Just hide all content sections except first.
$content.each(function(index) {
if ($(this).height() > minHeight) minHeight = $(this).height();
$(this).css({position: 'absolute', display: index > 0 ? 'none' : ''});
});
// Register for changes to window size, and trigger.
$(window).resize(resizeWidget);
resizeWidget();
function resizeWidget() {
var height = $(window).height() - topOffset - padBottom;
$widget.width($(window).width());
$widget.height(height < minHeight ? minHeight :
(maxHeight && height > maxHeight ? maxHeight : height));
}
}
})();
/*
Tab Carousel
The following allows tab widgets to be installed via the html below. Each
tab content section should have a data-tab attribute matching one of the
nav items'. Also each tab content section should have a width matching the
tab carousel.
HTML:
<div class="tab-carousel">
<ul class="tab-nav">
<li><a href="#" data-tab="handsets">Handsets</a>
<li><a href="#" data-tab="wearable">Wearable</a>
<li><a href="#" data-tab="tv">TV</a>
</ul>
<div class="tab-carousel-content">
<div data-tab="handsets">
<!--Full width content here-->
</div>
<div data-tab="wearable">
<!--Full width content here-->
</div>
<div data-tab="tv">
<!--Full width content here-->
</div>
</div>
</div>
*/
(function() {
$(document).ready(function() {
$('.tab-carousel').each(function() {
initWidget(this);
});
});
function initWidget(widget) {
var $widget = $(widget);
var $nav = $widget.find('.tab-nav');
var $anchors = $nav.find('[data-tab]');
var $li = $nav.find('li');
var $contentContainer = $widget.find('.tab-carousel-content');
var $tabs = $contentContainer.find('[data-tab]');
var $curTab = $($tabs[0]); // Current tab is first tab.
var width = $widget.width();
// Setup nav interactivity.
$anchors.click(function(evt) {
evt.preventDefault();
var query = '[data-tab=' + $(this).data('tab') + ']';
transitionWidget($tabs.filter(query));
});
// Add highlight for navigation on first item.
var $highlight = $('<div>').addClass('highlight')
.css({left:$li.position().left + 'px', width:$li.outerWidth() + 'px'})
.appendTo($nav);
// Store height since we will change contents to absolute.
$contentContainer.height($contentContainer.height());
// Absolutely position tabs so they're ready for transition.
$tabs.each(function(index) {
$(this).css({position: 'absolute', left: index > 0 ? width + 'px' : '0'});
});
function transitionWidget($toTab) {
if (!$curTab.is($toTab)) {
var curIndex = $tabs.index($curTab[0]);
var toIndex = $tabs.index($toTab[0]);
var dir = toIndex > curIndex ? 1 : -1;
// Animate content sections.
$toTab.css({left:(width * dir) + 'px'});
$curTab.animate({left:(width * -dir) + 'px'});
$toTab.animate({left:'0'});
// Animate navigation highlight.
$highlight.animate({left:$($li[toIndex]).position().left + 'px',
width:$($li[toIndex]).outerWidth() + 'px'})
// Store new current section.
$curTab = $toTab;
}
}
}
})();
/**
* Auto TOC
*
* Upgrades h2s on the page to have a rule and be toggle-able on mobile.
*/
(function($) {
var upgraded = false;
var h2Titles;
function initWidget() {
// add HRs below all H2s (except for a few other h2 variants)
// Consider doing this with css instead.
h2Titles = $('h2').not('#qv h2, #tb h2, .sidebox h2, #devdoc-nav h2, h2.norule');
h2Titles.css({marginBottom:0}).after('<hr/>');
// Exit early if on older browser.
if (!window.matchMedia) {
return;
}
// Only run logic in mobile layout.
var query = window.matchMedia('(max-width: 719px)');
if (query.matches) {
makeTogglable();
} else {
query.addListener(makeTogglable);
}
}
function makeTogglable() {
// Only run this logic once.
if (upgraded) { return; }
upgraded = true;
// Only make content h2s togglable.
var contentTitles = h2Titles.filter('#jd-content *');
// If there are more than 1
if (contentTitles.size() < 2) {
return;
}
contentTitles.each(function() {
// Find all the relevant nodes.
var $title = $(this);
var $hr = $title.next();
var $contents = $hr.nextUntil('h2, .next-docs');
var $section = $($title)
.add($hr)
.add($title.prev('a[name]'))
.add($contents);
var $anchor = $section.first().prev();
var anchorMethod = 'after';
if ($anchor.length === 0) {
$anchor = $title.parent();
anchorMethod = 'prepend';
}
// Some h2s are in their own container making it pretty hard to find the end, so skip.
if ($contents.length === 0) {
return;
}
// Remove from DOM before messing with it. DOM is slow!
$section.detach();
// Add mobile-only expand arrows.
$title.prepend('<span class="dac-visible-mobile-inline-block">' +
'<i class="dac-toggle-expand dac-sprite dac-expand-more-black"></i>' +
'<i class="dac-toggle-collapse dac-sprite dac-expand-less-black"></i>' +
'</span>')
.attr('data-toggle', 'section');
// Wrap in magic markup.
$section = $section.wrapAll('<div class="dac-toggle dac-mobile">').parent();
$contents.wrapAll('<div class="dac-toggle-content"><div>'); // extra div used for max-height calculation.
// Pre-expand section if requested.
if ($title.hasClass('is-expanded')) {
$section.addClass('is-expanded');
}
// Pre-expand section if targetted by hash.
if (location.hash && $section.find(location.hash).length) {
$section.addClass('is-expanded');
}
// Add it back to the dom.
$anchor[anchorMethod].call($anchor, $section);
});
}
$(function() {
initWidget();
});
})(jQuery);
(function($) {
'use strict';
/**
* Toggle Floating Label state.
* @param {HTMLElement} el - The DOM element.
* @param options
* @constructor
*/
function FloatingLabel(el, options) {
this.el = $(el);
this.options = $.extend({}, FloatingLabel.DEFAULTS_, options);
this.group = this.el.closest('.dac-form-input-group');
this.input = this.group.find('.dac-form-input');
this.checkValue_ = this.checkValue_.bind(this);
this.checkValue_();
this.input.on('focus', function() {
this.group.addClass('dac-focused');
}.bind(this));
this.input.on('blur', function() {
this.group.removeClass('dac-focused');
this.checkValue_();
}.bind(this));
this.input.on('keyup', this.checkValue_);
}
/**
* The label is moved out of the textbox when it has a value.
*/
FloatingLabel.prototype.checkValue_ = function() {
if (this.input.val().length) {
this.group.addClass('dac-has-value');
} else {
this.group.removeClass('dac-has-value');
}
};
/**
* jQuery plugin
* @param {object} options - Override default options.
*/
$.fn.dacFloatingLabel = function(options) {
return this.each(function() {
new FloatingLabel(this, options);
});
};
$(document).on('ready.aranja', function() {
$('.dac-form-floatlabel').each(function() {
$(this).dacFloatingLabel($(this).data());
});
});
})(jQuery);
/* global toRoot, CAROUSEL_OVERRIDE */
(function($) {
// Ordering matters
var TAG_MAP = [
{from: 'developerstory', to: 'Android Developer Story'},
{from: 'googleplay', to: 'Google Play'}
];
function DacCarouselQuery(el) {
this.el = $(el);
var opts = this.el.data();
opts.maxResults = parseInt(opts.maxResults || '100', 10);
opts.query = opts.carouselQuery;
var resources = $.queryResources(opts);
this.el.empty();
$(resources).map(function() {
var resource = $.extend({}, this, CAROUSEL_OVERRIDE[this.url]);
var slide = $('<article class="dac-expand dac-hero">');
var image = cleanUrl(resource.heroImage || resource.image);
var fullBleed = image && !resource.heroColor;
// Configure background
slide.css({
backgroundImage: fullBleed ? 'url(' + image + ')' : '',
backgroundColor: resource.heroColor || ''
});
// Should copy be inverted
slide.toggleClass('dac-invert', resource.heroInvert || fullBleed);
slide.toggleClass('dac-darken', fullBleed);
// Should be clickable
slide.append($('<a class="dac-hero-carousel-action">').attr('href', cleanUrl(resource.url)));
var cols = $('<div class="cols dac-hero-content">');
// inline image column
var rightCol = $('<div class="col-1of2 col-push-1of2 dac-hero-figure">')
.appendTo(cols);
if (!fullBleed && image) {
rightCol.append($('<img>').attr('src', image));
}
// info column
$('<div class="col-1of2 col-pull-1of2">')
.append($('<div class="dac-hero-tag">').text(formatTag(resource)))
.append($('<h1 class="dac-hero-title">').text(formatTitle(resource)))
.append($('<p class="dac-hero-description">').text(resource.summary))
.append($('<a class="dac-hero-cta">')
.text(formatCTA(resource))
.attr('href', cleanUrl(resource.url))
.prepend($('<span class="dac-sprite dac-auto-chevron">'))
)
.appendTo(cols);
slide.append(cols.wrap('<div class="wrap">').parent());
return slide[0];
}).prependTo(this.el);
// Pagination element.
this.el.append('<div class="dac-hero-carousel-pagination"><div class="wrap" data-carousel-pagination>');
this.el.dacCarousel();
}
function cleanUrl(url) {
if (url && url.indexOf('//') === -1) {
url = toRoot + url;
}
return url;
}
function formatTag(resource) {
// Hmm, need a better more scalable solution for this.
for (var i = 0, mapping; mapping = TAG_MAP[i]; i++) {
if (resource.tags.indexOf(mapping.from) > -1) {
return mapping.to;
}
}
return resource.type;
}
function formatTitle(resource) {
return resource.title.replace(/android developer story: /i, '');
}
function formatCTA(resource) {
return resource.type === 'youtube' ? 'Watch the video' : 'Learn more';
}
// jQuery plugin
$.fn.dacCarouselQuery = function() {
return this.each(function() {
var el = $(this);
var data = el.data('dac.carouselQuery');
if (!data) { el.data('dac.carouselQuery', (data = new DacCarouselQuery(el))); }
});
};
// Data API
$(function() {
$('[data-carousel-query]').dacCarouselQuery();
});
})(jQuery);
(function($) {
/**
* A CSS based carousel, inspired by SequenceJS.
* @param {jQuery} el
* @param {object} options
* @constructor
*/
function DacCarousel(el, options) {
this.el = $(el);
this.options = options = $.extend({}, DacCarousel.OPTIONS, this.el.data(), options || {});
this.frames = this.el.find(options.frameSelector);
this.count = this.frames.size();
this.current = options.start;
this.initPagination();
this.initEvents();
this.initFrame();
}
DacCarousel.OPTIONS = {
auto: true,
autoTime: 10000,
autoMinTime: 5000,
btnPrev: '[data-carousel-prev]',
btnNext: '[data-carousel-next]',
frameSelector: 'article',
loop: true,
start: 0,
swipeThreshold: 160,
pagination: '[data-carousel-pagination]'
};
DacCarousel.prototype.initPagination = function() {
this.pagination = $([]);
if (!this.options.pagination) { return; }
var pagination = $('<ul class="dac-pagination">');
var parent = this.el;
if (typeof this.options.pagination === 'string') { parent = this.el.find(this.options.pagination); }
if (this.count > 1) {
for (var i = 0; i < this.count; i++) {
var li = $('<li class="dac-pagination-item">').text(i);
if (i === this.options.start) { li.addClass('active'); }
li.click(this.go.bind(this, i));
pagination.append(li);
}
this.pagination = pagination.children();
parent.append(pagination);
}
};
DacCarousel.prototype.initEvents = function() {
var that = this;
this.touch = {
start: {x: 0, y: 0},
end: {x: 0, y: 0}
};
this.el.on('touchstart', this.touchstart_.bind(this));
this.el.on('touchend', this.touchend_.bind(this));
this.el.on('touchmove', this.touchmove_.bind(this));
this.el.hover(function() {
that.pauseRotateTimer();
}, function() {
that.startRotateTimer();
});
$(this.options.btnPrev).click(function(e) {
e.preventDefault();
that.prev();
});
$(this.options.btnNext).click(function(e) {
e.preventDefault();
that.next();
});
};
DacCarousel.prototype.touchstart_ = function(event) {
var t = event.originalEvent.touches[0];
this.touch.start = {x: t.screenX, y: t.screenY};
};
DacCarousel.prototype.touchend_ = function() {
var deltaX = this.touch.end.x - this.touch.start.x;
var deltaY = Math.abs(this.touch.end.y - this.touch.start.y);
var shouldSwipe = (deltaY < Math.abs(deltaX)) && (Math.abs(deltaX) >= this.options.swipeThreshold);
if (shouldSwipe) {
if (deltaX > 0) {
this.prev();
} else {
this.next();
}
}
};
DacCarousel.prototype.touchmove_ = function(event) {
var t = event.originalEvent.touches[0];
this.touch.end = {x: t.screenX, y: t.screenY};
};
DacCarousel.prototype.initFrame = function() {
this.frames.removeClass('active').eq(this.options.start).addClass('active');
};
DacCarousel.prototype.startRotateTimer = function() {
if (!this.options.auto || this.rotateTimer) { return; }
this.rotateTimer = setTimeout(this.next.bind(this), this.options.autoTime);
};
DacCarousel.prototype.pauseRotateTimer = function() {
clearTimeout(this.rotateTimer);
this.rotateTimer = null;
};
DacCarousel.prototype.prev = function() {
this.go(this.current - 1);
};
DacCarousel.prototype.next = function() {
this.go(this.current + 1);
};
DacCarousel.prototype.go = function(next) {
// Figure out what the next slide is.
while (this.count > 0 && next >= this.count) { next -= this.count; }
while (next < 0) { next += this.count; }
// Cancel if we're already on that slide.
if (next === this.current) { return; }
// Prepare next slide.
this.frames.eq(next).removeClass('out');
// Recalculate styles before starting slide transition.
this.el.resolveStyles();
// Update pagination
this.pagination.removeClass('active').eq(next).addClass('active');
// Transition out current frame
this.frames.eq(this.current).toggleClass('active out');
// Transition in a new frame
this.frames.eq(next).toggleClass('active');
this.current = next;
};
// Helper which resolves new styles for an element, so it can start transitioning
// from the new values.
$.fn.resolveStyles = function() {
/*jshint expr:true*/
this[0] && this[0].offsetTop;
return this;
};
// jQuery plugin
$.fn.dacCarousel = function() {
this.each(function() {
var $el = $(this);
$el.data('dac-carousel', new DacCarousel(this));
});
return this;
};
// Data API
$(function() {
$('[data-carousel]').dacCarousel();
});
})(jQuery);
(function($) {
'use strict';
function Modal(el, options) {
this.el = $(el);
this.options = $.extend({}, ToggleModal.DEFAULTS_, options);
this.isOpen = false;
this.el.on('click', function(event) {
if (!$.contains($('.dac-modal-window')[0], event.target)) {
return this.el.trigger('modal-close');
}
}.bind(this));
this.el.on('modal-open', this.open_.bind(this));
this.el.on('modal-close', this.close_.bind(this));
this.el.on('modal-toggle', this.toggle_.bind(this));
}
Modal.prototype.toggle_ = function() {
this.el.trigger('modal-' + (this.isOpen ? 'close' : 'open'));
};
Modal.prototype.close_ = function() {
this.el.removeClass('dac-active');
$('body').removeClass('dac-modal-open');
this.isOpen = false;
};
Modal.prototype.open_ = function() {
this.el.addClass('dac-active');
$('body').addClass('dac-modal-open');
this.isOpen = true;
};
function ToggleModal(el, options) {
this.el = $(el);
this.options = $.extend({}, ToggleModal.DEFAULTS_, options);
this.modal = this.options.modalToggle ? $('[data-modal="' + this.options.modalToggle + '"]') :
this.el.closest('[data-modal]');
this.el.on('click', this.clickHandler_.bind(this));
}
ToggleModal.prototype.clickHandler_ = function(event) {
event.preventDefault();
this.modal.trigger('modal-toggle');
};
/**
* jQuery plugin
* @param {object} options - Override default options.
*/
$.fn.dacModal = function(options) {
return this.each(function() {
new Modal(this, options);
});
};
$.fn.dacToggleModal = function(options) {
return this.each(function() {
new ToggleModal(this, options);
});
};
/**
* Data Attribute API
*/
$(document).on('ready.aranja', function() {
$('[data-modal]').each(function() {
$(this).dacModal($(this).data());
});
$('[data-modal-toggle]').each(function() {
$(this).dacToggleModal($(this).data());
});
});
})(jQuery);
(function($) {
'use strict';
/**
* Toggle the visabilty of the mobile navigation.
* @param {HTMLElement} el - The DOM element.
* @param options
* @constructor
*/
function ToggleNav(el, options) {
this.el = $(el);
this.options = $.extend({}, ToggleNav.DEFAULTS_, options);
this.options.target = [this.options.navigation];
if (this.options.body) {this.options.target.push('body')}
if (this.options.dimmer) {this.options.target.push(this.options.dimmer)}
this.el.on('click', this.clickHandler_.bind(this));
}
/**
* ToggleNav Default Settings
* @type {{body: boolean, dimmer: string, navigation: string, toggleClass: string}}
* @private
*/
ToggleNav.DEFAULTS_ = {
body: true,
dimmer: '.dac-nav-dimmer',
navigation: '[data-dac-nav]',
toggleClass: 'dac-nav-open'
};
/**
* The actual toggle logic.
* @param event
* @private
*/
ToggleNav.prototype.clickHandler_ = function(event) {
event.preventDefault();
$(this.options.target.join(', ')).toggleClass(this.options.toggleClass);
};
/**
* jQuery plugin
* @param {object} options - Override default options.
*/
$.fn.dacToggleMobileNav = function(options) {
return this.each(function() {
new ToggleNav(this, options);
});
};
/**
* Data Attribute API
*/
$(window).on('load.aranja', function() {
$('[data-dac-toggle-nav]').each(function() {
$(this).dacToggleMobileNav($(this).data());
});
});
})(jQuery);
(function($) {
'use strict';
/**
* Submit the newsletter form to a Google Form.
* @param {HTMLElement} el - The Form DOM element.
* @constructor
*/
function NewsletterForm(el) {
this.el = $(el);
this.form = this.el.find('form');
$('<iframe/>').hide()
.attr('name', 'dac-newsletter-iframe')
.attr('src', '')
.insertBefore(this.form);
this.form.on('submit', this.submitHandler_.bind(this));
}
/**
* Milliseconds until modal has vanished after modal-close is triggered.
* @type {number}
* @private
*/
NewsletterForm.CLOSE_DELAY_ = 300;
/**
* Switch view to display form after close.
* @private
*/
NewsletterForm.prototype.closeHandler_ = function() {
setTimeout(function() {
this.el.trigger('swap-reset');
}.bind(this), NewsletterForm.CLOSE_DELAY_);
};
/**
* Reset the modal to initial state.
* @private
*/
NewsletterForm.prototype.reset_ = function() {
this.form.trigger('reset');
this.el.one('modal-close', this.closeHandler_.bind(this));
};
/**
* Display a success view on submit.
* @private
*/
NewsletterForm.prototype.submitHandler_ = function() {
this.el.one('swap-complete', this.reset_.bind(this));
this.el.trigger('swap-content');
};
/**
* jQuery plugin
* @param {object} options - Override default options.
*/
$.fn.dacNewsletterForm = function(options) {
return this.each(function() {
new NewsletterForm(this, options);
});
};
/**
* Data Attribute API
*/
$(document).on('ready.aranja', function() {
$('[data-newsletter]').each(function() {
$(this).dacNewsletterForm();
});
});
})(jQuery);
(function($) {
'use strict';
/**
* Smoothly scroll to location on current page.
* @param el
* @param options
* @constructor
*/
function ScrollButton(el, options) {
this.el = $(el);
this.target = $(this.el.attr('href'));
this.options = $.extend({}, ScrollButton.DEFAULTS_, options);
if (typeof this.options.offset === 'string') {
this.options.offset = $(this.options.offset).height();
}
this.el.on('click', this.clickHandler_.bind(this));
}
/**
* Default options
* @type {{duration: number, easing: string, offset: number, scrollContainer: string}}
* @private
*/
ScrollButton.DEFAULTS_ = {
duration: 300,
easing: 'swing',
offset: 0,
scrollContainer: 'html, body'
};
/**
* Scroll logic
* @param event
* @private
*/
ScrollButton.prototype.clickHandler_ = function(event) {
if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
return;
}
event.preventDefault();
$(this.options.scrollContainer).animate({
scrollTop: this.target.offset().top - this.options.offset
}, this.options);
};
/**
* jQuery plugin
* @param {object} options - Override default options.
*/
$.fn.dacScrollButton = function(options) {
return this.each(function() {
new ScrollButton(this, options);
});
};
/**
* Data Attribute API
*/
$(document).on('ready.aranja', function() {
$('[data-scroll-button]').each(function() {
$(this).dacScrollButton($(this).data());
});
});
})(jQuery);
(function($) {
'use strict';
/**
* A component that swaps two dynamic height views with an animation.
* Listens for the following events:
* * swap-content: triggers SwapContent.swap_()
* * swap-reset: triggers SwapContent.reset()
* @param el
* @param options
* @constructor
*/
function SwapContent(el, options) {
this.el = $(el);
this.options = $.extend({}, SwapContent.DEFAULTS_, options);
this.containers = this.el.find(this.options.container);
this.initiallyActive = this.containers.children('.' + this.options.activeClass).eq(0);
this.el.on('swap-content', this.swap.bind(this));
this.el.on('swap-reset', this.reset.bind(this));
}
/**
* SwapContent's default settings.
* @type {{activeClass: string, container: string, transitionSpeed: number}}
* @private
*/
SwapContent.DEFAULTS_ = {
activeClass: 'dac-active',
container: '[data-swap-container]',
transitionSpeed: 500
};
/**
* Returns container's visible height.
* @param container
* @returns {number}
*/
SwapContent.prototype.currentHeight = function(container) {
return container.children('.' + this.options.activeClass).outerHeight();
};
/**
* Reset to show initial content
*/
SwapContent.prototype.reset = function() {
if (!this.initiallyActive.hasClass(this.initiallyActive)) {
this.containers.children().toggleClass(this.options.activeClass);
}
};
/**
* Complete the swap.
*/
SwapContent.prototype.complete = function() {
this.containers.height('auto');
this.containers.trigger('swap-complete');
};
/**
* Perform the swap of content.
*/
SwapContent.prototype.swap = function() {
console.log(this.containers);
this.containers.each(function(index, container) {
container = $(container);
container.height(this.currentHeight(container)).children().toggleClass(this.options.activeClass);
container.animate({height: this.currentHeight(container)}, this.options.transitionSpeed,
this.complete.bind(this));
}.bind(this));
};
/**
* jQuery plugin
* @param {object} options - Override default options.
*/
$.fn.dacSwapContent = function(options) {
return this.each(function() {
new SwapContent(this, options);
});
};
/**
* Data Attribute API
*/
$(document).on('ready.aranja', function() {
$('[data-swap]').each(function() {
$(this).dacSwapContent($(this).data());
});
});
})(jQuery);
(function($) {
function Toggle(el) {
$(el).on('click.dac.togglesection', this.toggle);
}
Toggle.prototype.toggle = function() {
var $this = $(this);
var $parent = getParent($this);
var isExpanded = $parent.hasClass('is-expanded');
transitionMaxHeight($parent.find('.dac-toggle-content'), !isExpanded);
$parent.toggleClass('is-expanded');
return false;
};
function getParent($this) {
var selector = $this.attr('data-target');
if (!selector) {
selector = $this.attr('href');
selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '');
}
var $parent = selector && $(selector);
$parent = $parent && $parent.length ? $parent : $this.closest('.dac-toggle');
return $parent.length ? $parent : $this.parent();
}
/**
* Runs a transition of max-height along with responsive styles which hide or expand the element.
* @param $el
* @param visible
*/
function transitionMaxHeight($el, visible) {
var contentHeight = $el.prop('scrollHeight');
var targetHeight = visible ? contentHeight : 0;
var duration = $el.transitionDuration();
// If we're hiding, first set the maxHeight we're transitioning from.
if (!visible) {
$el.css('maxHeight', contentHeight + 'px')
.resolveStyles();
}
// Transition to new state
$el.css('maxHeight', targetHeight);
// Reset maxHeight to css value after transition.
setTimeout(function() {
$el.css('maxHeight', '');
}, duration);
}
// Utility to get the transition duration for the element.
$.fn.transitionDuration = function() {
var d = $(this).css('transitionDuration') || '0s';
return +(parseFloat(d) * (/ms/.test(d) ? 1 : 1000)).toFixed(0);
};
// jQuery plugin
$.fn.toggleSection = function(option) {
return this.each(function() {
var $this = $(this);
var data = $this.data('dac.togglesection');
if (!data) {$this.data('dac.togglesection', (data = new Toggle(this)));}
if (typeof option === 'string') {data[option].call($this);}
});
};
// Data api
$(document)
.on('click.toggle', '[data-toggle="section"]', Toggle.prototype.toggle);
})(jQuery);