6691 lines
206 KiB
JavaScript
6691 lines
206 KiB
JavaScript
var cookie_namespace = 'android_developer';
|
||
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));
|
||
|
||
// TODO(akassay) generate this var in the reference doc build.
|
||
var API_LEVELS = ['1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||
'10', '11', '12', '13', '14', '15', '16',
|
||
'17', '18', '19', '20', '21', '22', '23', '24'];
|
||
var METADATA = METADATA || {};
|
||
var RESERVED_METADATA_CATEGORY_NAMES = ['extras', 'carousel', 'collections',
|
||
'searchHeroCollections'];
|
||
|
||
// Ensure that all ajax getScript() requests allow caching
|
||
$.ajaxSetup({
|
||
cache: true
|
||
});
|
||
|
||
/****** ON LOAD SET UP STUFF *********/
|
||
|
||
$(document).ready(function() {
|
||
|
||
// prep nav expandos
|
||
var pagePath = location.href.replace(location.hash, '');
|
||
// 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
|
||
}
|
||
|
||
// set global variable so we can highlight the sidenav a bit later (such as for google reference)
|
||
// and highlight the sidenav
|
||
mPagePath = pagePath;
|
||
|
||
// Check for params and remove them.
|
||
mPagePath = mPagePath.split('?')[0];
|
||
highlightSidenav();
|
||
|
||
// 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();
|
||
$('.content-footer .prev-page-link').hide();
|
||
|
||
if ($nextLink.length) {
|
||
$('.next-class-link').attr('href', $nextLink.attr('href'))
|
||
.removeClass("hide");
|
||
|
||
$('.content-footer .next-class-link').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 previous and next page titles
|
||
if ($prevLink.length) {
|
||
$('.content-footer .prev-page-link').append($prevLink.html());
|
||
}
|
||
if ($nextLink.length) {
|
||
$('.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);
|
||
});
|
||
$('#classes').append($olClasses);
|
||
}
|
||
|
||
// Set up expand/collapse behavior
|
||
initExpandableNavItems("#nav");
|
||
|
||
// 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 play-on-click for <video> tags with a "video-wrapper".
|
||
$('.video-wrapper > video').bind('click', function() {
|
||
this.play();
|
||
$(this.parentElement).addClass('playing');
|
||
});
|
||
|
||
// 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);
|
||
});
|
||
// END of the onload event
|
||
|
||
function initExpandableNavItems(rootTag) {
|
||
var toggleIcon = $(
|
||
rootTag + ' li.nav-section .nav-section-header .toggle-icon, ' +
|
||
rootTag + ' li.nav-section .nav-section-header a[href="#"]');
|
||
|
||
toggleIcon.on('click keypress', function(e) {
|
||
if (e.type == 'keypress' && e.which == 13 || e.type == 'click') {
|
||
doNavToggle(this);
|
||
}
|
||
});
|
||
|
||
// 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;
|
||
});
|
||
}
|
||
|
||
function doNavToggle(el) {
|
||
var section = $(el).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');
|
||
});
|
||
} else {
|
||
/* show me */
|
||
// first hide all other siblings
|
||
var $others = $('li.nav-section.expanded', $(el).closest('ul')).not('.sticky');
|
||
$others.removeClass('expanded').children('ul').slideUp(250);
|
||
|
||
// now expand me
|
||
section.closest('li').addClass('expanded');
|
||
section.children('ul').slideDown(250);
|
||
}
|
||
}
|
||
|
||
/** 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;
|
||
var breadcrumb = [];
|
||
|
||
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();
|
||
|
||
var link = $(this).find('a').first();
|
||
|
||
if (!$(this).is($selListItem)) {
|
||
breadcrumb.unshift(link)
|
||
}
|
||
});
|
||
|
||
$('#nav').scrollIntoView($selNavLink);
|
||
}
|
||
|
||
breadcrumb.forEach(function(link) {
|
||
link.dacCrumbs();
|
||
});
|
||
}
|
||
|
||
function unHighlightSidenav() {
|
||
$("ul#nav li.selected").removeClass("selected");
|
||
$('ul#nav li.nav-section.expanded').removeClass('expanded').children('ul').hide();
|
||
}
|
||
|
||
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();
|
||
});
|
||
|
||
/* 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! ########## */
|
||
|
||
/*
|
||
* 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);
|
||
});
|
||
}
|
||
|
||
/* 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/styles/disclosure_up.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/styles/disclosure_down.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);
|
||
|
||
/* ######################################################## */
|
||
/* ################# JAVADOC REFERENCE ################### */
|
||
/* ######################################################## */
|
||
|
||
|
||
|
||
var API_LEVEL_COOKIE = "api_level";
|
||
var minLevel = 1;
|
||
var maxLevel = 1;
|
||
|
||
function buildApiLevelSelector() {
|
||
maxLevel = API_LEVELS.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", "" + API_LEVELS[i]).append("" + API_LEVELS[i]);
|
||
// if (API_LEVELS[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 = API_LEVELS.length;
|
||
minLevel = parseInt($('#doc-api-level').attr('class'));
|
||
var selectedLevel = maxLevel;
|
||
|
||
selectedLevel = parseInt($("#apiLevelSelector option:selected").val());
|
||
toggleVisisbleApis(selectedLevel, "body");
|
||
|
||
writeCookie(API_LEVEL_COOKIE, selectedLevel, null);
|
||
|
||
if (selectedLevel < minLevel) {
|
||
// Show the API notice dialog, set number values and button event
|
||
$('#api-unavailable').trigger('modal-open');
|
||
$('#api-unavailable .selected-level').text(selectedLevel);
|
||
$('#api-unavailable .api-level').text(minLevel);
|
||
$('#api-unavailable button.ok').attr('onclick','$("#apiLevelSelector").val("' + minLevel + '");changeApiLevel();');
|
||
}
|
||
}
|
||
|
||
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 ################### */
|
||
/* 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();
|
||
}
|
||
});
|
||
}
|
||
|
||
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();
|
||
}
|
||
});
|
||
}
|
||
|
||
/* 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/styles/disclosure_up.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/styles/disclosure_down.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());
|
||
var $samplesLink = $li.find("a");
|
||
if ($samplesLink.text().endsWith('/')) {
|
||
$samplesLink.text($samplesLink.text().slice(0,-1));
|
||
}
|
||
$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() {
|
||
$(document).ready(function() {
|
||
// Need to initialize hero carousel before other sections for dedupe
|
||
// to work correctly.
|
||
$('[data-carousel-query]').dacCarouselQuery();
|
||
|
||
// Iterate over all instances and initialize a resource widget.
|
||
$('.resource-widget').resourceWidget();
|
||
});
|
||
|
||
$.fn.widgetOptions = function() {
|
||
return {
|
||
cardSizes: (this.data('cardsizes') || '').split(','),
|
||
maxResults: parseInt(this.data('maxresults'), 10) || Infinity,
|
||
initialResults: this.data('initialResults'),
|
||
itemsPerPage: this.data('itemsPerPage'),
|
||
sortOrder: this.data('sortorder'),
|
||
query: this.data('query'),
|
||
section: this.data('section'),
|
||
/* Added by LFL 6/6/14 */
|
||
resourceStyle: this.data('resourcestyle') || 'card',
|
||
stackSort: this.data('stacksort') || 'true',
|
||
// For filter based resources
|
||
allowDuplicates: this.data('allow-duplicates') || 'false'
|
||
};
|
||
};
|
||
|
||
$.fn.deprecateOldGridStyles = function() {
|
||
var m = this.get(0).className.match(/\bcol-(\d+)\b/);
|
||
if (m && !this.is('.cols > *')) {
|
||
this.removeClass('col-' + m[1]);
|
||
}
|
||
return this;
|
||
}
|
||
|
||
/*
|
||
* 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, resources, opts) {
|
||
var $widget = $(widget).deprecateOldGridStyles();
|
||
var isFlow = $widget.hasClass('resource-flow-layout');
|
||
var isCarousel = $widget.hasClass('resource-carousel-layout');
|
||
var isStack = $widget.hasClass('resource-stack-layout');
|
||
|
||
opts = opts || $widget.widgetOptions();
|
||
resources = resources || metadata.query(opts);
|
||
|
||
if (opts.maxResults !== undefined) {
|
||
resources = resources.slice(0, opts.maxResults);
|
||
}
|
||
|
||
if (isFlow) {
|
||
drawResourcesFlowWidget($widget, opts, resources);
|
||
} else if (isCarousel) {
|
||
drawResourcesCarouselWidget($widget, opts, resources);
|
||
} else if (isStack) {
|
||
opts.numStacks = $widget.data('numstacks');
|
||
drawResourcesStackWidget($widget, opts, resources);
|
||
}
|
||
}
|
||
|
||
$.fn.resourceWidget = function(resources, options) {
|
||
return this.each(function() {
|
||
initResourceWidget(this, resources, options);
|
||
});
|
||
};
|
||
|
||
/* 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 = [];
|
||
|
||
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 (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 (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 (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) {
|
||
// We'll be doing our own modifications to opts.
|
||
opts = $.extend({}, opts);
|
||
|
||
$widget.empty().addClass('cols');
|
||
if (opts.itemsPerPage) {
|
||
$('<div class="col-1of1 dac-section-links dac-text-center">')
|
||
.append(
|
||
$('<div class="dac-section-link dac-show-less" data-toggle="show-less">Less<i class="dac-sprite dac-auto-unfold-less"></i></div>'),
|
||
$('<div class="dac-section-link dac-show-more" data-toggle="show-more">More<i class="dac-sprite dac-auto-unfold-more"></i></div>')
|
||
)
|
||
.appendTo($widget);
|
||
}
|
||
|
||
$widget.data('options.resourceflow', opts);
|
||
$widget.data('resources.resourceflow', resources);
|
||
|
||
drawResourceFlowPage($widget, opts, resources);
|
||
}
|
||
|
||
function drawResourceFlowPage($widget, opts, resources) {
|
||
var cardSizes = opts.cardSizes || ['6x6']; // 2015-08-09: dynamic card sizes are deprecated
|
||
var i = opts.currentIndex || 0;
|
||
var j = 0;
|
||
var plusone = false; // stop showing plusone buttons on cards
|
||
var firstPage = i === 0;
|
||
var initialResults = opts.initialResults || opts.itemsPerPage || resources.length;
|
||
var max = firstPage ? initialResults : i + opts.itemsPerPage;
|
||
max = Math.min(resources.length, max);
|
||
|
||
var page = $('<div class="resource-flow-page">');
|
||
if (opts.itemsPerPage) {
|
||
$widget.find('.dac-section-links').before(page);
|
||
} else {
|
||
$widget.append(page);
|
||
}
|
||
|
||
while (i < max) {
|
||
var cardSize = cardSizes[j++ % cardSizes.length];
|
||
cardSize = cardSize.replace(/^\s+|\s+$/, '');
|
||
|
||
var column = createResponsiveFlowColumn(cardSize).appendTo(page);
|
||
|
||
// 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.toLowerCase());
|
||
|
||
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 < max && stackCount > 0);
|
||
|
||
// Record number of pages viewed in analytics.
|
||
if (!firstPage) {
|
||
var clicks = Math.ceil((i - initialResults) / opts.itemsPerPage);
|
||
devsite.analytics.trackAnalyticsEvent('event',
|
||
'Cards', 'Click More', clicks);
|
||
}
|
||
}
|
||
|
||
opts.currentIndex = i;
|
||
$widget.toggleClass('dac-has-more', i < resources.length);
|
||
$widget.toggleClass('dac-has-less', !firstPage);
|
||
|
||
$widget.trigger('dac:domchange');
|
||
if (opts.onRenderPage) {
|
||
opts.onRenderPage(page);
|
||
}
|
||
}
|
||
|
||
function drawResourceFlowReset($widget, opts, resources) {
|
||
$widget.find('.resource-flow-page')
|
||
.slice(1)
|
||
.remove();
|
||
$widget.toggleClass('dac-has-more', true);
|
||
$widget.toggleClass('dac-has-less', false);
|
||
|
||
opts.currentIndex = Math.min(opts.initialResults, resources.length);
|
||
devsite.analytics.trackAnalyticsEvent('event', 'Cards', 'Click Less');
|
||
}
|
||
|
||
/* A decorator for event functions which finds the surrounding widget and it's options */
|
||
function wrapWithWidget(func) {
|
||
return function(e) {
|
||
if (e) e.preventDefault();
|
||
|
||
var $widget = $(this).closest('.resource-flow-layout');
|
||
var opts = $widget.data('options.resourceflow');
|
||
var resources = $widget.data('resources.resourceflow');
|
||
func($widget, opts, resources);
|
||
};
|
||
}
|
||
|
||
/* 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 cleanUrl(url) {
|
||
if (url && url.indexOf('//') === -1) {
|
||
url = toRoot + url;
|
||
}
|
||
|
||
return url;
|
||
}
|
||
|
||
// Delegated events for resources.
|
||
$(document).on('click', '.resource-flow-layout [data-toggle="show-more"]', wrapWithWidget(drawResourceFlowPage));
|
||
$(document).on('click', '.resource-flow-layout [data-toggle="show-less"]', wrapWithWidget(drawResourceFlowReset));
|
||
})();
|
||
|
||
(function($) {
|
||
// A mapping from category and type values to new values or human presentable strings.
|
||
var SECTION_MAP = {
|
||
googleplay: 'google play'
|
||
};
|
||
|
||
/*
|
||
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.category || resource.type;
|
||
section = (SECTION_MAP[section] || section).toLowerCase();
|
||
var imgUrl = resource.image ||
|
||
'assets/images/resource-card-default-android.jpg';
|
||
|
||
if (imgUrl.indexOf('//') === -1) {
|
||
imgUrl = toRoot + imgUrl;
|
||
}
|
||
|
||
if (resource.type === 'youtube' || resource.type === 'video') {
|
||
$('<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' + (resource.title_highlighted ? ' highlighted' : ''))
|
||
.html(resource.title_highlighted || 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_highlighted || resource.title),
|
||
$('<p>').addClass('summary').html(resource.summary),
|
||
$('<a>').attr('href', linkUrl).addClass('cta').html('Learn More')
|
||
)
|
||
);
|
||
|
||
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({paddingBottom: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 = allNextUntil($hr[0], '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();
|
||
|
||
// extra div used for max-height calculation.
|
||
$contents.wrapAll('<div class="dac-toggle-content dac-expand"><div>');
|
||
|
||
// 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);
|
||
});
|
||
}
|
||
|
||
// Similar to $.fn.nextUntil() except we need all nodes, jQuery skips text nodes.
|
||
function allNextUntil(elem, until) {
|
||
var matched = [];
|
||
|
||
while ((elem = elem.nextSibling) && elem.nodeType !== 9) {
|
||
if (elem.nodeType === 1 && jQuery(elem).is(until)) {
|
||
break;
|
||
}
|
||
matched.push(elem);
|
||
}
|
||
return $(matched);
|
||
}
|
||
|
||
$(function() {
|
||
initWidget();
|
||
});
|
||
})(jQuery);
|
||
|
||
(function($, window) {
|
||
'use strict';
|
||
|
||
// Blogger API info
|
||
var apiUrl = 'https://www.googleapis.com/blogger/v3';
|
||
var apiKey = 'AIzaSyCFhbGnjW06dYwvRCU8h_zjdpS4PYYbEe8';
|
||
|
||
// Blog IDs can be found in the markup of the blog posts
|
||
var blogs = {
|
||
'android-developers': {
|
||
id: '6755709643044947179',
|
||
title: 'Android Developers Blog'
|
||
}
|
||
};
|
||
var monthNames = ['January', 'February', 'March', 'April', 'May', 'June',
|
||
'July', 'August', 'September', 'October', 'November', 'December'];
|
||
|
||
var BlogReader = (function() {
|
||
var reader;
|
||
|
||
function BlogReader() {
|
||
this.doneSetup = false;
|
||
}
|
||
|
||
/**
|
||
* Initialize the blog reader and modal.
|
||
*/
|
||
BlogReader.prototype.setup = function() {
|
||
$('#jd-content').append(
|
||
'<div id="blog-reader" data-modal="blog-reader" class="dac-modal dac-has-small-header">' +
|
||
'<div class="dac-modal-container">' +
|
||
'<div class="dac-modal-window">' +
|
||
'<header class="dac-modal-header">' +
|
||
'<div class="dac-modal-header-actions">' +
|
||
'<a href="" class="dac-modal-header-open" target="_blank">' +
|
||
'<i class="dac-sprite dac-open-in-new"></i>' +
|
||
'</a>' +
|
||
'<button class="dac-modal-header-close" data-modal-toggle>' +
|
||
'</button>' +
|
||
'</div>' +
|
||
'<h2 class="norule dac-modal-header-title"></h2>' +
|
||
'</header>' +
|
||
'<div class="dac-modal-content dac-blog-reader">' +
|
||
'<time class="dac-blog-reader-date" pubDate></time>' +
|
||
'<h3 class="dac-blog-reader-title"></h3>' +
|
||
'<div class="dac-blog-reader-text clearfix"></div>' +
|
||
'</div>' +
|
||
'</div>' +
|
||
'</div>' +
|
||
'</div>');
|
||
|
||
this.blogReader = $('#blog-reader').dacModal();
|
||
|
||
this.doneSetup = true;
|
||
};
|
||
|
||
BlogReader.prototype.openModal_ = function(blog, post) {
|
||
var published = new Date(post.published);
|
||
var formattedDate = monthNames[published.getMonth()] + ' ' + published.getDate() + ' ' + published.getFullYear();
|
||
this.blogReader.find('.dac-modal-header-open').attr('href', post.url);
|
||
this.blogReader.find('.dac-modal-header-title').text(blog.title);
|
||
this.blogReader.find('.dac-blog-reader-title').html(post.title);
|
||
this.blogReader.find('.dac-blog-reader-date').html(formattedDate);
|
||
this.blogReader.find('.dac-blog-reader-text').html(post.content);
|
||
this.blogReader.trigger('modal-open');
|
||
};
|
||
|
||
/**
|
||
* Show a blog post in a modal
|
||
* @param {string} blogName - The name of the Blogspot blog.
|
||
* @param {string} postPath - The path to the blog post.
|
||
* @param {bool} secondTry - Has it failed once?
|
||
*/
|
||
BlogReader.prototype.showPost = function(blogName, postPath, secondTry) {
|
||
var blog = blogs[blogName];
|
||
var postUrl = 'https://' + blogName + '.blogspot.com' + postPath;
|
||
|
||
var url = apiUrl + '/blogs/' + blog.id + '/posts/bypath?path=' + encodeURIComponent(postPath) + '&key=' + apiKey;
|
||
$.ajax(url, {timeout: 650}).done(this.openModal_.bind(this, blog)).fail(function(error) {
|
||
// Retry once if we get an error
|
||
if (error.status === 500 && !secondTry) {
|
||
this.showPost(blogName, postPath, true);
|
||
} else {
|
||
window.location.href = postUrl;
|
||
}
|
||
}.bind(this));
|
||
};
|
||
|
||
return {
|
||
getReader: function() {
|
||
if (!reader) {
|
||
reader = new BlogReader();
|
||
}
|
||
return reader;
|
||
}
|
||
};
|
||
})();
|
||
|
||
var blogReader = BlogReader.getReader();
|
||
|
||
function wrapLinkWithReader(e) {
|
||
var el = $(e.currentTarget);
|
||
if (el.hasClass('dac-modal-header-open')) {
|
||
return;
|
||
}
|
||
|
||
// Only catch links on blogspot.com
|
||
var matches = el.attr('href').match(/https?:\/\/([^\.]*).blogspot.com([^$]*)/);
|
||
if (matches && matches.length === 3) {
|
||
var blogName = matches[1];
|
||
var postPath = matches[2];
|
||
|
||
// Check if we have information about the blog
|
||
if (!blogs[blogName]) {
|
||
return;
|
||
}
|
||
|
||
// Setup the first time it's used
|
||
if (!blogReader.doneSetup) {
|
||
blogReader.setup();
|
||
}
|
||
|
||
e.preventDefault();
|
||
blogReader.showPost(blogName, postPath);
|
||
}
|
||
}
|
||
|
||
$(document).on('click.blog-reader', 'a.resource-card[href*="blogspot.com/"]',
|
||
wrapLinkWithReader);
|
||
})(jQuery, window);
|
||
|
||
(function($) {
|
||
$.fn.debounce = function(func, wait, immediate) {
|
||
var timeout;
|
||
|
||
return function() {
|
||
var context = this;
|
||
var args = arguments;
|
||
|
||
var later = function() {
|
||
timeout = null;
|
||
if (!immediate) {
|
||
func.apply(context, args);
|
||
}
|
||
};
|
||
|
||
var callNow = immediate && !timeout;
|
||
clearTimeout(timeout);
|
||
timeout = setTimeout(later, wait);
|
||
|
||
if (callNow) {
|
||
func.apply(context, args);
|
||
}
|
||
};
|
||
};
|
||
})(jQuery);
|
||
|
||
/* Calculate the vertical area remaining */
|
||
(function($) {
|
||
$.fn.ellipsisfade = function() {
|
||
// Only fetch line-height of first element to avoid recalculate style.
|
||
// Will be NaN if no elements match, which is ok.
|
||
var lineHeight = parseInt(this.css('line-height'), 10);
|
||
|
||
this.each(function() {
|
||
// get element text
|
||
var $this = $(this);
|
||
var remainingHeight = $this.parent().parent().height();
|
||
$this.parent().siblings().each(function() {
|
||
var elHeight;
|
||
if ($(this).is(':visible')) {
|
||
elHeight = $(this).outerHeight(true);
|
||
remainingHeight = remainingHeight - elHeight;
|
||
}
|
||
});
|
||
|
||
var adjustedRemainingHeight = ((remainingHeight) / lineHeight >> 0) * lineHeight;
|
||
$this.parent().css({height: adjustedRemainingHeight});
|
||
$this.css({height: 'auto'});
|
||
});
|
||
|
||
return 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. */
|
||
function updateEllipsis(context) {
|
||
if (!(context instanceof jQuery)) {
|
||
context = $('html');
|
||
}
|
||
|
||
context.find('.card-info .text').ellipsisfade();
|
||
}
|
||
|
||
$(window).on('resize', $.fn.debounce(updateEllipsis, 500));
|
||
$(updateEllipsis);
|
||
$('html').on('dac:domchange', function(e) { updateEllipsis($(e.target)); });
|
||
})(jQuery);
|
||
|
||
/* Filter */
|
||
(function($) {
|
||
'use strict';
|
||
|
||
/**
|
||
* A single filter item content.
|
||
* @type {string} - Element template.
|
||
* @private
|
||
*/
|
||
var ITEM_STR_ = '<input type="checkbox" value="{{value}}" class="dac-form-checkbox" id="{{id}}">' +
|
||
'<label for="{{id}}" class="dac-form-checkbox-button"></label>' +
|
||
'<label for="{{id}}" class="dac-form-label">{{name}}</label>';
|
||
|
||
/**
|
||
* Template for a chip element.
|
||
* @type {*|HTMLElement}
|
||
* @private
|
||
*/
|
||
var CHIP_BASE_ = $('<li class="dac-filter-chip">' +
|
||
'<button class="dac-filter-chip-close">' +
|
||
'<i class="dac-sprite dac-close-black dac-filter-chip-close-icon"></i>' +
|
||
'</button>' +
|
||
'</li>');
|
||
|
||
/**
|
||
* Component to handle narrowing down resources.
|
||
* @param {HTMLElement} el - The DOM element.
|
||
* @param {Object} options
|
||
* @constructor
|
||
*/
|
||
function Filter(el, options) {
|
||
this.el = $(el);
|
||
this.options = $.extend({}, Filter.DEFAULTS_, options);
|
||
this.init();
|
||
}
|
||
|
||
Filter.DEFAULTS_ = {
|
||
activeClass: 'dac-active',
|
||
chipsDataAttr: 'filter-chips',
|
||
nameDataAttr: 'filter-name',
|
||
countDataAttr: 'filter-count',
|
||
tabViewDataAttr: 'tab-view',
|
||
valueDataAttr: 'filter-value'
|
||
};
|
||
|
||
/**
|
||
* Draw resource cards.
|
||
* @param {Array} resources
|
||
* @private
|
||
*/
|
||
Filter.prototype.draw_ = function(resources) {
|
||
var that = this;
|
||
|
||
if (resources.length === 0) {
|
||
this.containerEl_.html('<p class="dac-filter-message">Nothing matches selected filters.</p>');
|
||
return;
|
||
}
|
||
|
||
// Draw resources.
|
||
that.containerEl_.resourceWidget(resources, that.data_.options);
|
||
};
|
||
|
||
/**
|
||
* Initialize a Filter component.
|
||
*/
|
||
Filter.prototype.init = function() {
|
||
this.containerEl_ = $(this.options.filter);
|
||
|
||
// Setup data settings
|
||
this.data_ = {};
|
||
this.data_.chips = {};
|
||
this.data_.options = this.containerEl_.widgetOptions();
|
||
this.data_.all = window.metadata.query(this.data_.options);
|
||
|
||
// Initialize filter UI
|
||
this.initUi();
|
||
};
|
||
|
||
/**
|
||
* Generate a chip for a given filter item.
|
||
* @param {Object} item - A single filter option (checkbox container).
|
||
* @returns {HTMLElement} A new Chip element.
|
||
*/
|
||
Filter.prototype.chipForItem = function(item) {
|
||
var chip = CHIP_BASE_.clone();
|
||
chip.prepend(this.data_.chips[item.data('filter-value')]);
|
||
chip.data('item.dac-filter', item);
|
||
item.data('chip.dac-filter', chip);
|
||
this.addToItemValue(item, 1);
|
||
return chip[0];
|
||
};
|
||
|
||
/**
|
||
* Update count of checked filter items.
|
||
* @param {Object} item - A single filter option (checkbox container).
|
||
* @param {Number} value - Either -1 or 1.
|
||
*/
|
||
Filter.prototype.addToItemValue = function(item, value) {
|
||
var tab = item.parent().data(this.options.tabViewDataAttr);
|
||
var countEl = this.countEl_.filter('[data-' + this.options.countDataAttr + '="' + tab + '"]');
|
||
var count = value + parseInt(countEl.text(), 10);
|
||
countEl.text(count);
|
||
countEl.toggleClass('dac-disabled', count === 0);
|
||
};
|
||
|
||
/**
|
||
* Set event listeners.
|
||
* @private
|
||
*/
|
||
Filter.prototype.setEventListeners_ = function() {
|
||
this.chipsEl_.on('click.dac-filter', '.dac-filter-chip-close', this.closeChipHandler_.bind(this));
|
||
this.tabViewEl_.on('change.dac-filter', ':checkbox', this.toggleCheckboxHandler_.bind(this));
|
||
};
|
||
|
||
/**
|
||
* Check filter items that are active by default.
|
||
*/
|
||
Filter.prototype.activateInitialFilters_ = function() {
|
||
var id = (new Date()).getTime();
|
||
var initiallyCheckedValues = this.data_.options.query.replace(/,\s*/g, '+').split('+');
|
||
var chips = document.createDocumentFragment();
|
||
var that = this;
|
||
|
||
this.items_.each(function(i) {
|
||
var item = $(this);
|
||
var opts = item.data();
|
||
that.data_.chips[opts.filterValue] = opts.filterName;
|
||
|
||
var checkbox = $(ITEM_STR_.replace(/\{\{name\}\}/g, opts.filterName)
|
||
.replace(/\{\{value\}\}/g, opts.filterValue)
|
||
.replace(/\{\{id\}\}/g, 'filter-' + id + '-' + (i + 1)));
|
||
|
||
if (initiallyCheckedValues.indexOf(opts.filterValue) > -1) {
|
||
checkbox[0].checked = true;
|
||
chips.appendChild(that.chipForItem(item));
|
||
}
|
||
|
||
item.append(checkbox);
|
||
});
|
||
|
||
this.chipsEl_.append(chips);
|
||
};
|
||
|
||
/**
|
||
* Initialize the Filter view
|
||
*/
|
||
Filter.prototype.initUi = function() {
|
||
// Cache DOM elements
|
||
this.chipsEl_ = this.el.find('[data-' + this.options.chipsDataAttr + ']');
|
||
this.countEl_ = this.el.find('[data-' + this.options.countDataAttr + ']');
|
||
this.tabViewEl_ = this.el.find('[data-' + this.options.tabViewDataAttr + ']');
|
||
this.items_ = this.el.find('[data-' + this.options.nameDataAttr + ']');
|
||
|
||
// Setup UI
|
||
this.draw_(this.data_.all);
|
||
this.activateInitialFilters_();
|
||
this.setEventListeners_();
|
||
};
|
||
|
||
/**
|
||
* @returns {[types|Array, tags|Array, category|Array]}
|
||
*/
|
||
Filter.prototype.getActiveClauses = function() {
|
||
var tags = [];
|
||
var types = [];
|
||
var categories = [];
|
||
|
||
this.items_.find(':checked').each(function(i, checkbox) {
|
||
// Currently, there is implicit business logic here that `tag` is AND'ed together
|
||
// while `type` is OR'ed. So , and + do the same thing here. It would be great to
|
||
// reuse the same query engine for filters, but it would need more powerful syntax.
|
||
// Probably parenthesis, to support "tag:dog + tag:cat + (type:video, type:blog)"
|
||
var expression = $(checkbox).val();
|
||
var regex = /(\w+):(\w+)/g;
|
||
var match;
|
||
|
||
while (match = regex.exec(expression)) {
|
||
switch (match[1]) {
|
||
case 'category':
|
||
categories.push(match[2]);
|
||
break;
|
||
case 'tag':
|
||
tags.push(match[2]);
|
||
break;
|
||
case 'type':
|
||
types.push(match[2]);
|
||
break;
|
||
}
|
||
}
|
||
});
|
||
|
||
return [types, tags, categories];
|
||
};
|
||
|
||
/**
|
||
* Actual filtering logic.
|
||
* @returns {Array}
|
||
*/
|
||
Filter.prototype.filteredResources = function() {
|
||
var data = this.getActiveClauses();
|
||
var types = data[0];
|
||
var tags = data[1];
|
||
var categories = data[2];
|
||
var resources = [];
|
||
var resource = {};
|
||
var tag = '';
|
||
var shouldAddResource = true;
|
||
|
||
for (var resourceIndex = 0; resourceIndex < this.data_.all.length; resourceIndex++) {
|
||
resource = this.data_.all[resourceIndex];
|
||
shouldAddResource = types.indexOf(resource.type) > -1;
|
||
|
||
if (categories && categories.length > 0) {
|
||
shouldAddResource = shouldAddResource && categories.indexOf(resource.category) > -1;
|
||
}
|
||
|
||
for (var tagIndex = 0; shouldAddResource && tagIndex < tags.length; tagIndex++) {
|
||
tag = tags[tagIndex];
|
||
shouldAddResource = resource.tags.indexOf(tag) > -1;
|
||
}
|
||
|
||
if (shouldAddResource) {
|
||
resources.push(resource);
|
||
}
|
||
}
|
||
|
||
return resources;
|
||
};
|
||
|
||
/**
|
||
* Close Chip Handler
|
||
* @param {Event} event - Click event
|
||
* @private
|
||
*/
|
||
Filter.prototype.closeChipHandler_ = function(event) {
|
||
var chip = $(event.currentTarget).parent();
|
||
var checkbox = chip.data('item.dac-filter').find(':first-child')[0];
|
||
checkbox.checked = false;
|
||
this.changeStateForCheckbox(checkbox);
|
||
};
|
||
|
||
/**
|
||
* Handle filter item state change.
|
||
* @param {Event} event - Change event
|
||
* @private
|
||
*/
|
||
Filter.prototype.toggleCheckboxHandler_ = function(event) {
|
||
this.changeStateForCheckbox(event.currentTarget);
|
||
};
|
||
|
||
/**
|
||
* Redraw resource view based on new state.
|
||
* @param checkbox
|
||
*/
|
||
Filter.prototype.changeStateForCheckbox = function(checkbox) {
|
||
var item = $(checkbox).parent();
|
||
|
||
if (checkbox.checked) {
|
||
this.chipsEl_.append(this.chipForItem(item));
|
||
devsite.analytics.trackAnalyticsEvent('event',
|
||
'Filters', 'Check', $(checkbox).val());
|
||
} else {
|
||
item.data('chip.dac-filter').remove();
|
||
this.addToItemValue(item, -1);
|
||
devsite.analytics.trackAnalyticsEvent('event',
|
||
'Filters', 'Uncheck', $(checkbox).val());
|
||
}
|
||
|
||
this.draw_(this.filteredResources());
|
||
};
|
||
|
||
/**
|
||
* jQuery plugin
|
||
*/
|
||
$.fn.dacFilter = function() {
|
||
return this.each(function() {
|
||
var el = $(this);
|
||
new Filter(el, el.data());
|
||
});
|
||
};
|
||
|
||
/**
|
||
* Data Attribute API
|
||
*/
|
||
$(function() {
|
||
$('[data-filter]').dacFilter();
|
||
});
|
||
})(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);
|
||
|
||
(function($) {
|
||
'use strict';
|
||
|
||
/**
|
||
* @param {HTMLElement} el - The DOM element.
|
||
* @param {Object} options
|
||
* @constructor
|
||
*/
|
||
function Crumbs(selected, options) {
|
||
this.options = $.extend({}, Crumbs.DEFAULTS_, options);
|
||
this.el = $(this.options.container);
|
||
|
||
// Do not build breadcrumbs for landing site.
|
||
if (!selected || location.pathname === '/index.html' || location.pathname === '/') {
|
||
return;
|
||
}
|
||
|
||
// Cache navigation resources
|
||
this.selected = $(selected);
|
||
this.selectedParent = this.selected.closest('.dac-nav-secondary').siblings('a');
|
||
|
||
// Build the breadcrumb list.
|
||
this.init();
|
||
}
|
||
|
||
Crumbs.DEFAULTS_ = {
|
||
container: '.dac-header-crumbs',
|
||
crumbItem: $('<li class="dac-header-crumbs-item">'),
|
||
linkClass: 'dac-header-crumbs-link'
|
||
};
|
||
|
||
Crumbs.prototype.init = function() {
|
||
Crumbs.buildCrumbForLink(this.selected.clone()).appendTo(this.el);
|
||
|
||
if (this.selectedParent.length) {
|
||
Crumbs.buildCrumbForLink(this.selectedParent.clone()).prependTo(this.el);
|
||
}
|
||
|
||
// Reveal the breadcrumbs
|
||
this.el.addClass('dac-has-content');
|
||
};
|
||
|
||
/**
|
||
* Build a HTML structure for a breadcrumb.
|
||
* @param {string} link
|
||
* @return {jQuery}
|
||
*/
|
||
Crumbs.buildCrumbForLink = function(link) {
|
||
link.find('br').replaceWith(' ');
|
||
|
||
var crumbLink = $('<a>')
|
||
.attr('class', Crumbs.DEFAULTS_.linkClass)
|
||
.attr('href', link.attr('href'))
|
||
.text(link.text());
|
||
|
||
return Crumbs.DEFAULTS_.crumbItem.clone().append(crumbLink);
|
||
};
|
||
|
||
/**
|
||
* jQuery plugin
|
||
*/
|
||
$.fn.dacCrumbs = function(options) {
|
||
return this.each(function() {
|
||
new Crumbs(this, options);
|
||
});
|
||
};
|
||
})(jQuery);
|
||
|
||
(function($) {
|
||
'use strict';
|
||
|
||
/**
|
||
* @param {HTMLElement} el - The DOM element.
|
||
* @param {Object} options
|
||
* @constructor
|
||
*/
|
||
function SearchInput(el, options) {
|
||
this.el = $(el);
|
||
this.options = $.extend({}, SearchInput.DEFAULTS_, options);
|
||
this.body = $('body');
|
||
this.input = this.el.find('input');
|
||
this.close = this.el.find(this.options.closeButton);
|
||
this.clear = this.el.find(this.options.clearButton);
|
||
this.icon = this.el.find('.' + this.options.iconClass);
|
||
this.init();
|
||
}
|
||
|
||
SearchInput.DEFAULTS_ = {
|
||
activeClass: 'dac-active',
|
||
activeIconClass: 'dac-search',
|
||
closeButton: '[data-search-close]',
|
||
clearButton: '[data-search-clear]',
|
||
hiddenClass: 'dac-hidden',
|
||
iconClass: 'dac-header-search-icon',
|
||
searchModeClass: 'dac-search-mode',
|
||
transitionDuration: 250
|
||
};
|
||
|
||
SearchInput.prototype.init = function() {
|
||
this.input.on('focus.dac-search', this.setActiveState.bind(this))
|
||
.on('input.dac-search', this.checkInputValue.bind(this));
|
||
this.close.on('click.dac-search', this.unsetActiveStateHandler_.bind(this));
|
||
this.clear.on('click.dac-search', this.clearInput.bind(this));
|
||
};
|
||
|
||
SearchInput.prototype.setActiveState = function() {
|
||
var that = this;
|
||
|
||
this.clear.addClass(this.options.hiddenClass);
|
||
this.body.addClass(this.options.searchModeClass);
|
||
this.checkInputValue();
|
||
|
||
// Set icon to black after background has faded to white.
|
||
setTimeout(function() {
|
||
that.icon.addClass(that.options.activeIconClass);
|
||
}, this.options.transitionDuration);
|
||
};
|
||
|
||
SearchInput.prototype.unsetActiveStateHandler_ = function(event) {
|
||
event.preventDefault();
|
||
this.unsetActiveState();
|
||
};
|
||
|
||
SearchInput.prototype.unsetActiveState = function() {
|
||
this.icon.removeClass(this.options.activeIconClass);
|
||
this.clear.addClass(this.options.hiddenClass);
|
||
this.body.removeClass(this.options.searchModeClass);
|
||
};
|
||
|
||
SearchInput.prototype.clearInput = function(event) {
|
||
event.preventDefault();
|
||
this.input.val('');
|
||
this.clear.addClass(this.options.hiddenClass);
|
||
};
|
||
|
||
SearchInput.prototype.checkInputValue = function() {
|
||
if (this.input.val().length) {
|
||
this.clear.removeClass(this.options.hiddenClass);
|
||
} else {
|
||
this.clear.addClass(this.options.hiddenClass);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* jQuery plugin
|
||
* @param {object} options - Override default options.
|
||
*/
|
||
$.fn.dacSearchInput = function() {
|
||
return this.each(function() {
|
||
var el = $(this);
|
||
el.data('search-input.dac', new SearchInput(el, el.data()));
|
||
});
|
||
};
|
||
|
||
/**
|
||
* Data Attribute API
|
||
*/
|
||
$(function() {
|
||
$('[data-search]').dacSearchInput();
|
||
});
|
||
})(jQuery);
|
||
|
||
/* global METADATA */
|
||
(function($) {
|
||
function DacCarouselQuery(el) {
|
||
el = $(el);
|
||
|
||
var opts = el.data();
|
||
opts.maxResults = parseInt(opts.maxResults || '100', 10);
|
||
opts.query = opts.carouselQuery;
|
||
var resources = window.metadata.query(opts);
|
||
|
||
el.empty();
|
||
$(resources).each(function() {
|
||
var resource = $.extend({}, this, METADATA.carousel[this.url]);
|
||
el.dacHero(resource);
|
||
});
|
||
|
||
// Pagination element.
|
||
el.append('<div class="dac-hero-carousel-pagination"><div class="wrap" data-carousel-pagination>');
|
||
|
||
el.dacCarousel();
|
||
}
|
||
|
||
// 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);
|
||
|
||
/* global toRoot */
|
||
|
||
(function($) {
|
||
// Ordering matters
|
||
var TAG_MAP = [
|
||
{from: 'developerstory', to: 'Android Developer Story'},
|
||
{from: 'googleplay', to: 'Google Play'}
|
||
];
|
||
|
||
function DacHero(el, resource, isSearch) {
|
||
var slide = $('<article>');
|
||
slide.addClass(isSearch ? 'dac-search-hero' : 'dac-expand dac-hero');
|
||
var image = cleanUrl(resource.heroImage || resource.image);
|
||
var fullBleed = image && !resource.heroColor;
|
||
|
||
if (!isSearch) {
|
||
// 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 || isSearch) && 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());
|
||
el.append(slide);
|
||
}
|
||
|
||
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.dacHero = function(resource, isSearch) {
|
||
return this.each(function() {
|
||
var el = $(this);
|
||
return new DacHero(el, resource, isSearch);
|
||
});
|
||
};
|
||
})(jQuery);
|
||
|
||
(function($) {
|
||
'use strict';
|
||
|
||
function highlightString(label, query) {
|
||
query = query || '';
|
||
//query = query.replace('<wbr>', '').replace('.', '\\.');
|
||
var queryRE = new RegExp('(' + query + ')', 'ig');
|
||
return label.replace(queryRE, '<em>$1</em>');
|
||
}
|
||
|
||
$.fn.highlightMatches = function(query) {
|
||
return this.each(function() {
|
||
var el = $(this);
|
||
var label = el.html();
|
||
var highlighted = highlightString(label, query);
|
||
el.html(highlighted);
|
||
el.addClass('highlighted');
|
||
});
|
||
};
|
||
})(jQuery);
|
||
|
||
/**
|
||
* History tracking.
|
||
* Track visited urls in localStorage.
|
||
*/
|
||
(function($) {
|
||
var PAGES_TO_STORE_ = 100;
|
||
var MIN_NUMBER_OF_PAGES_TO_DISPLAY_ = 6;
|
||
var CONTAINER_SELECTOR_ = '.dac-search-results-history-wrap';
|
||
|
||
/**
|
||
* Generate resource cards for visited pages.
|
||
* @param {HTMLElement} el
|
||
* @constructor
|
||
*/
|
||
function HistoryQuery(el) {
|
||
this.el = $(el);
|
||
|
||
// Only show history component if enough pages have been visited.
|
||
if (getVisitedPages().length < MIN_NUMBER_OF_PAGES_TO_DISPLAY_) {
|
||
this.el.closest(CONTAINER_SELECTOR_).addClass('dac-hidden');
|
||
return;
|
||
}
|
||
|
||
// Rename query
|
||
this.el.data('query', this.el.data('history-query'));
|
||
|
||
// jQuery method to populate cards.
|
||
this.el.resourceWidget();
|
||
}
|
||
|
||
/**
|
||
* Fetch from localStorage an array of visted pages
|
||
* @returns {Array}
|
||
*/
|
||
function getVisitedPages() {
|
||
var visited = localStorage.getItem('visited-pages');
|
||
return visited ? JSON.parse(visited) : [];
|
||
}
|
||
|
||
/**
|
||
* Return a page corresponding to cuurent pathname. If none exists, create one.
|
||
* @param {Array} pages
|
||
* @param {String} path
|
||
* @returns {Object} Page
|
||
*/
|
||
function getPageForPath(pages, path) {
|
||
var page;
|
||
|
||
// Backwards lookup for current page, last pages most likely to be visited again.
|
||
for (var i = pages.length - 1; i >= 0; i--) {
|
||
if (pages[i].path === path) {
|
||
page = pages[i];
|
||
|
||
// Remove page object from pages list to ensure correct ordering.
|
||
pages.splice(i, 1);
|
||
|
||
return page;
|
||
}
|
||
}
|
||
|
||
// If storage limit is exceeded, remove last visited path.
|
||
if (pages.length >= PAGES_TO_STORE_) {
|
||
pages.shift();
|
||
}
|
||
|
||
return {path: path};
|
||
}
|
||
|
||
/**
|
||
* Add current page to back of visited array, increase hit count by 1.
|
||
*/
|
||
function addCurrectPage() {
|
||
var path = location.pathname;
|
||
|
||
// Do not track frontpage visits.
|
||
if (path === '/' || path === '/index.html') {return;}
|
||
|
||
var pages = getVisitedPages();
|
||
var page = getPageForPath(pages, path);
|
||
|
||
// New page visits have no hit count.
|
||
page.hit = ~~page.hit + 1;
|
||
|
||
// Most recently visted pages are located at the end of the visited array.
|
||
pages.push(page);
|
||
|
||
localStorage.setItem('visited-pages', JSON.stringify(pages));
|
||
}
|
||
|
||
/**
|
||
* Hit count compare function.
|
||
* @param {Object} a - page
|
||
* @param {Object} b - page
|
||
* @returns {number}
|
||
*/
|
||
function byHit(a, b) {
|
||
if (a.hit > b.hit) {
|
||
return -1;
|
||
} else if (a.hit < b.hit) {
|
||
return 1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/**
|
||
* Return a list of visited urls in a given order.
|
||
* @param {String} order - (recent|most-visited)
|
||
* @returns {Array}
|
||
*/
|
||
$.dacGetVisitedUrls = function(order) {
|
||
var pages = getVisitedPages();
|
||
|
||
if (order === 'recent') {
|
||
pages.reverse();
|
||
} else {
|
||
pages.sort(byHit);
|
||
}
|
||
|
||
return pages.map(function(page) {
|
||
return page.path.replace(/^\//, '');
|
||
});
|
||
};
|
||
|
||
// jQuery plugin
|
||
$.fn.dacHistoryQuery = function() {
|
||
return this.each(function() {
|
||
var el = $(this);
|
||
var data = el.data('dac.recentlyVisited');
|
||
|
||
if (!data) {
|
||
el.data('dac.recentlyVisited', (data = new HistoryQuery(el)));
|
||
}
|
||
});
|
||
};
|
||
|
||
$(function() {
|
||
$('[data-history-query]').dacHistoryQuery();
|
||
// Do not block page rendering.
|
||
setTimeout(addCurrectPage, 0);
|
||
});
|
||
})(jQuery);
|
||
|
||
/* ############################################ */
|
||
/* ########## LOCALIZATION ############ */
|
||
/* ############################################ */
|
||
/**
|
||
* Global helpers.
|
||
*/
|
||
function getBaseUri(uri) {
|
||
var intlUrl = (uri.substring(0, 6) === '/intl/');
|
||
if (intlUrl) {
|
||
var base = uri.substring(uri.indexOf('intl/') + 5, uri.length);
|
||
base = base.substring(base.indexOf('/') + 1, base.length);
|
||
return '/' + base;
|
||
} else {
|
||
return uri;
|
||
}
|
||
}
|
||
|
||
function changeLangPref(targetLang, submit) {
|
||
window.writeCookie('pref_lang', targetLang, null);
|
||
$('#language').find('option[value="' + targetLang + '"]').attr('selected', true);
|
||
if (submit) {
|
||
$('#setlang').submit();
|
||
}
|
||
}
|
||
// Redundant usage to appease jshint.
|
||
window.changeLangPref = changeLangPref;
|
||
|
||
(function() {
|
||
/**
|
||
* Whitelisted locales. Should match choices in language dropdown. Repeated here
|
||
* as a lot of i18n logic happens before page load and dropdown is ready.
|
||
*/
|
||
var LANGUAGES = [
|
||
'en',
|
||
'es',
|
||
'id',
|
||
'ja',
|
||
'ko',
|
||
'pt-br',
|
||
'ru',
|
||
'vi',
|
||
'zh-cn',
|
||
'zh-tw'
|
||
];
|
||
|
||
/**
|
||
* Master list of translated strings for template files.
|
||
*/
|
||
var PHRASES = {
|
||
'newsletter': {
|
||
'title': 'Get the latest Android developer news and tips that will help you find success on Google Play.',
|
||
'requiredHint': '* Required Fields',
|
||
'name': 'Full name',
|
||
'email': 'Email address',
|
||
'company': 'Company / developer name',
|
||
'appUrl': 'One of your Play Store app URLs',
|
||
'business': {
|
||
'label': 'Which best describes your business:',
|
||
'apps': 'Apps',
|
||
'games': 'Games',
|
||
'both': 'Apps & Games'
|
||
},
|
||
'confirmMailingList': 'Add me to the mailing list for the monthly newsletter and occasional emails about ' +
|
||
'development and Google Play opportunities.',
|
||
'privacyPolicy': 'I acknowledge that the information provided in this form will be subject to Google\'s ' +
|
||
'<a href="https://www.google.com/policies/privacy/" target="_blank">privacy policy</a>.',
|
||
'languageVal': 'English',
|
||
'successTitle': 'Hooray!',
|
||
'successDetails': 'You have successfully signed up for the latest Android developer news and tips.',
|
||
'languageValTarget': {
|
||
'en': 'English',
|
||
'ar': 'Arabic (العربيّة)',
|
||
'id': 'Indonesian (Bahasa)',
|
||
'fr': 'French (français)',
|
||
'de': 'German (Deutsch)',
|
||
'ja': 'Japanese (日本語)',
|
||
'ko': 'Korean (한국어)',
|
||
'ru': 'Russian (Русский)',
|
||
'es': 'Spanish (español)',
|
||
'th': 'Thai (ภาษาไทย)',
|
||
'tr': 'Turkish (Türkçe)',
|
||
'vi': 'Vietnamese (tiếng Việt)',
|
||
'pt-br': 'Brazilian Portuguese (Português Brasileiro)',
|
||
'zh-cn': 'Simplified Chinese (简体中文)',
|
||
'zh-tw': 'Traditional Chinese (繁體中文)',
|
||
},
|
||
'resetLangTitle': "Browse this site in %{targetLang}?",
|
||
'resetLangTextIntro': 'You requested a page in %{targetLang}, but your language preference for this site is %{lang}.',
|
||
'resetLangTextCta': 'Would you like to change your language preference and browse this site in %{targetLang}? ' +
|
||
'If you want to change your language preference later, use the language menu at the bottom of each page.',
|
||
'resetLangButtonYes': 'Change Language',
|
||
'resetLangButtonNo': 'Not Now'
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Current locale.
|
||
*/
|
||
var locale = (function() {
|
||
var lang = window.readCookie('pref_lang');
|
||
if (lang === 0 || LANGUAGES.indexOf(lang) === -1) {
|
||
lang = 'en';
|
||
}
|
||
return lang;
|
||
})();
|
||
var localeTarget = (function() {
|
||
var lang = getQueryVariable('hl');
|
||
if (lang === false || LANGUAGES.indexOf(lang) === -1) {
|
||
lang = locale;
|
||
}
|
||
return lang;
|
||
})();
|
||
|
||
/**
|
||
* Global function shims for backwards compatibility
|
||
*/
|
||
window.changeNavLang = function() {
|
||
// Already done.
|
||
};
|
||
|
||
window.loadLangPref = function() {
|
||
// Languages pref already loaded.
|
||
};
|
||
|
||
window.getLangPref = function() {
|
||
return locale;
|
||
};
|
||
|
||
window.getLangTarget = function() {
|
||
return localeTarget;
|
||
};
|
||
|
||
// Expose polyglot instance for advanced localization.
|
||
var polyglot = window.polyglot = new window.Polyglot({
|
||
locale: locale,
|
||
phrases: PHRASES
|
||
});
|
||
|
||
// When DOM is ready.
|
||
$(function() {
|
||
// Mark current locale in language picker.
|
||
$('#language').find('option[value="' + locale + '"]').attr('selected', true);
|
||
|
||
$('html').dacTranslate().on('dac:domchange', function(e) {
|
||
$(e.target).dacTranslate();
|
||
});
|
||
});
|
||
|
||
$.fn.dacTranslate = function() {
|
||
// Translate strings in template markup:
|
||
|
||
// OLD
|
||
// Having all translations in HTML does not scale well and bloats every page.
|
||
// Need to migrate this to data-l JS translations below.
|
||
if (locale !== 'en') {
|
||
var $links = this.find('a[' + locale + '-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(locale + '-lang'));
|
||
});
|
||
}
|
||
|
||
// NEW
|
||
// A simple declarative api for JS translations. Feel free to extend as appropriate.
|
||
|
||
// Miscellaneous string compilations
|
||
// Build full strings from localized substrings:
|
||
var myLocaleTarget = window.getLangTarget();
|
||
var myTargetLang = window.polyglot.t("newsletter.languageValTarget." + myLocaleTarget);
|
||
var myLang = window.polyglot.t("newsletter.languageVal");
|
||
var myTargetLangTitleString = window.polyglot.t("newsletter.resetLangTitle", {targetLang: myTargetLang});
|
||
var myResetLangTextIntro = window.polyglot.t("newsletter.resetLangTextIntro", {targetLang: myTargetLang, lang: myLang});
|
||
var myResetLangTextCta = window.polyglot.t("newsletter.resetLangTextCta", {targetLang: myTargetLang});
|
||
//var myResetLangButtonYes = window.polyglot.t("newsletter.resetLangButtonYes", {targetLang: myTargetLang});
|
||
|
||
// Inject strings as text values in dialog components:
|
||
$("#langform .dac-modal-header-title").text(myTargetLangTitleString);
|
||
$("#langform #resetLangText").text(myResetLangTextIntro);
|
||
$("#langform #resetLangCta").text(myResetLangTextCta);
|
||
//$("#resetLangButtonYes").attr("data-t", window.polyglot.t(myResetLangButtonYes));
|
||
|
||
// Text: <div data-t="nav.home"></div>
|
||
// HTML: <div data-t="privacy" data-t-html></html>
|
||
this.find('[data-t]').each(function() {
|
||
var el = $(this);
|
||
var data = el.data();
|
||
if (data.t) {
|
||
el[data.tHtml === '' ? 'html' : 'text'](polyglot.t(data.t));
|
||
}
|
||
});
|
||
|
||
return this;
|
||
};
|
||
})();
|
||
/* ########## END LOCALIZATION ############ */
|
||
|
||
// Translations. These should eventually be moved into language-specific files and loaded on demand.
|
||
// jshint nonbsp:false
|
||
switch (window.getLangPref()) {
|
||
case 'ar':
|
||
window.polyglot.extend({
|
||
'newsletter': {
|
||
'title': 'Google Play. يمكنك الحصول على آخر الأخبار والنصائح من مطوّري تطبيقات Android، مما يساعدك ' +
|
||
'على تحقيق النجاح على',
|
||
'requiredHint': '* حقول مطلوبة',
|
||
'name': '. الاسم بالكامل ',
|
||
'email': '. عنوان البريد الإلكتروني ',
|
||
'company': '. اسم الشركة / اسم مطوّر البرامج',
|
||
'appUrl': '. أحد عناوين URL لتطبيقاتك في متجر Play',
|
||
'business': {
|
||
'label': '. ما العنصر الذي يوضح طبيعة نشاطك التجاري بدقة؟ ',
|
||
'apps': 'التطبيقات',
|
||
'games': 'الألعاب',
|
||
'both': 'التطبيقات والألعاب'
|
||
},
|
||
'confirmMailingList': 'إضافتي إلى القائمة البريدية للنشرة الإخبارية الشهرية والرسائل الإلكترونية التي يتم' +
|
||
' إرسالها من حين لآخر بشأن التطوير وفرص Google Play.',
|
||
'privacyPolicy': 'أقر بأن المعلومات المقدَّمة في هذا النموذج تخضع لسياسة خصوصية ' +
|
||
'<a href="https://www.google.com/intl/ar/policies/privacy/" target="_blank">Google</a>.',
|
||
'languageVal': 'Arabic (العربيّة)',
|
||
'successTitle': 'رائع!',
|
||
'successDetails': 'لقد اشتركت بنجاح للحصول على آخر الأخبار والنصائح من مطوّري برامج Android.'
|
||
}
|
||
});
|
||
break;
|
||
case 'zh-cn':
|
||
window.polyglot.extend({
|
||
'newsletter': {
|
||
'title': '获取最新的 Android 开发者资讯和提示,助您在 Google Play 上取得成功。',
|
||
'requiredHint': '* 必填字段',
|
||
'name': '全名',
|
||
'email': '电子邮件地址',
|
||
'company': '公司/开发者名称',
|
||
'appUrl': '您的某个 Play 商店应用网址',
|
||
'business': {
|
||
'label': '哪一项能够最准确地描述您的业务?',
|
||
'apps': '应用',
|
||
'games': '游戏',
|
||
'both': '应用和游戏'
|
||
},
|
||
'confirmMailingList': '将我添加到邮寄名单,以便接收每月简报以及不定期发送的关于开发和 Google Play 商机的电子邮件。',
|
||
'privacyPolicy': '我确认自己了解在此表单中提供的信息受 <a href="https://www.google.com/intl/zh-CN/' +
|
||
'policies/privacy/" target="_blank">Google</a> 隐私权政策的约束。',
|
||
'languageVal': 'Simplified Chinese (简体中文)',
|
||
'successTitle': '太棒了!',
|
||
'successDetails': '您已成功订阅最新的 Android 开发者资讯和提示。'
|
||
}
|
||
});
|
||
break;
|
||
case 'zh-tw':
|
||
window.polyglot.extend({
|
||
'newsletter': {
|
||
'title': '獲得 Android 開發人員的最新消息和各項秘訣,讓您在 Google Play 上輕鬆邁向成功之路。',
|
||
'requiredHint': '* 必要欄位',
|
||
'name': '全名',
|
||
'email': '電子郵件地址',
|
||
'company': '公司/開發人員名稱',
|
||
'appUrl': '您其中一個 Play 商店應用程式的網址',
|
||
'business': {
|
||
'label': '為您的商家選取最合適的產品類別。',
|
||
'apps': '應用程式',
|
||
'games': '遊戲',
|
||
'both': '應用程式和遊戲'
|
||
},
|
||
'confirmMailingList': '我想加入 Google Play 的郵寄清單,以便接收每月電子報和 Google Play 不定期寄送的電子郵件,' +
|
||
'瞭解關於開發和 Google Play 商機的資訊。',
|
||
'privacyPolicy': '我瞭解,我在這張表單中提供的資訊將受到 <a href="' +
|
||
'https://www.google.com/intl/zh-TW/policies/privacy/" target="_blank">Google</a> 隱私權政策.',
|
||
'languageVal': 'Traditional Chinese (繁體中文)',
|
||
'successTitle': '太棒了!',
|
||
'successDetails': '您已經成功訂閱 Android 開發人員的最新消息和各項秘訣。'
|
||
}
|
||
});
|
||
break;
|
||
case 'fr':
|
||
window.polyglot.extend({
|
||
'newsletter': {
|
||
'title': 'Recevez les dernières actualités destinées aux développeurs Android, ainsi que des conseils qui ' +
|
||
'vous mèneront vers le succès sur Google Play.',
|
||
'requiredHint': '* Champs obligatoires',
|
||
'name': 'Nom complet',
|
||
'email': 'Adresse e-mail',
|
||
'company': 'Nom de la société ou du développeur',
|
||
'appUrl': 'Une de vos URL Play Store',
|
||
'business': {
|
||
'label': 'Quelle option décrit le mieux votre activité ?',
|
||
'apps': 'Applications',
|
||
'games': 'Jeux',
|
||
'both': 'Applications et jeux'
|
||
},
|
||
'confirmMailingList': 'Ajoutez-moi à la liste de diffusion de la newsletter mensuelle et tenez-moi informé ' +
|
||
'par des e-mails occasionnels de l\'évolution et des opportunités de Google Play.',
|
||
'privacyPolicy': 'Je comprends que les renseignements fournis dans ce formulaire seront soumis aux <a href="' +
|
||
'https://www.google.com/intl/fr/policies/privacy/" target="_blank">règles de confidentialité</a> de Google.',
|
||
'languageVal': 'French (français)',
|
||
'successTitle': 'Super !',
|
||
'successDetails': 'Vous êtes bien inscrit pour recevoir les actualités et les conseils destinés aux ' +
|
||
'développeurs Android.'
|
||
}
|
||
});
|
||
break;
|
||
case 'de':
|
||
window.polyglot.extend({
|
||
'newsletter': {
|
||
'title': 'Abonniere aktuelle Informationen und Tipps für Android-Entwickler und werde noch erfolgreicher ' +
|
||
'bei Google Play.',
|
||
'requiredHint': '* Pflichtfelder',
|
||
'name': 'Vollständiger Name',
|
||
'email': 'E-Mail-Adresse',
|
||
'company': 'Unternehmens-/Entwicklername',
|
||
'appUrl': 'Eine der URLs deiner Play Store App',
|
||
'business': {
|
||
'label': 'Welche der folgenden Kategorien beschreibt dein Unternehmen am besten?',
|
||
'apps': 'Apps',
|
||
'games': 'Spiele',
|
||
'both': 'Apps und Spiele'
|
||
},
|
||
'confirmMailingList': 'Meine E-Mail-Adresse soll zur Mailingliste hinzugefügt werden, damit ich den ' +
|
||
'monatlichen Newsletter sowie gelegentlich E-Mails zu Entwicklungen und Optionen bei Google Play erhalte.',
|
||
'privacyPolicy': 'Ich bestätige, dass die in diesem Formular bereitgestellten Informationen gemäß der ' +
|
||
'<a href="https://www.google.com/intl/de/policies/privacy/" target="_blank">Datenschutzerklärung</a> von ' +
|
||
'Google verwendet werden dürfen.',
|
||
'languageVal': 'German (Deutsch)',
|
||
'successTitle': 'Super!',
|
||
'successDetails': 'Du hast dich erfolgreich angemeldet und erhältst jetzt aktuelle Informationen und Tipps ' +
|
||
'für Android-Entwickler.'
|
||
}
|
||
});
|
||
break;
|
||
case 'id':
|
||
window.polyglot.extend({
|
||
'newsletter': {
|
||
'title': 'Receba as dicas e as notícias mais recentes para os desenvolvedores Android e seja bem-sucedido ' +
|
||
'no Google Play.',
|
||
'requiredHint': '* Bidang Wajib Diisi',
|
||
'name': 'Nama lengkap',
|
||
'email': 'Alamat email',
|
||
'company': 'Nama pengembang / perusahaan',
|
||
'appUrl': 'Salah satu URL aplikasi Play Store Anda',
|
||
'business': {
|
||
'label': 'Dari berikut ini, mana yang paling cocok dengan bisnis Anda?',
|
||
'apps': 'Aplikasi',
|
||
'games': 'Game',
|
||
'both': 'Aplikasi dan Game'
|
||
},
|
||
'confirmMailingList': 'Tambahkan saya ke milis untuk mendapatkan buletin bulanan dan email sesekali mengenai ' +
|
||
'perkembangan dan kesempatan yang ada di Google Play.',
|
||
'privacyPolicy': 'Saya memahami bahwa informasi yang diberikan dalam formulir ini tunduk pada <a href="' +
|
||
'https://www.google.com/intl/in/policies/privacy/" target="_blank">kebijakan privasi</a> Google.',
|
||
'languageVal': 'Indonesian (Bahasa)',
|
||
'successTitle': 'Hore!',
|
||
'successDetails': 'Anda berhasil mendaftar untuk kiat dan berita pengembang Android terbaru.'
|
||
}
|
||
});
|
||
break;
|
||
case 'it':
|
||
//window.polyglot.extend({
|
||
// 'newsletter': {
|
||
// 'title': 'Receba as dicas e as notícias mais recentes para os desenvolvedores Android e seja bem-sucedido ' +
|
||
// 'no Google Play.',
|
||
// 'requiredHint': '* Campos obrigatórios',
|
||
// 'name': 'Nome completo',
|
||
// 'email': 'Endereço de Email',
|
||
// 'company': 'Nome da empresa / do desenvolvedor',
|
||
// 'appUrl': 'URL de um dos seus apps da Play Store',
|
||
// 'business': {
|
||
// 'label': 'Qual das seguintes opções melhor descreve sua empresa?',
|
||
// 'apps': 'Apps',
|
||
// 'games': 'Jogos',
|
||
// 'both': 'Apps e Jogos'
|
||
// },
|
||
// 'confirmMailingList': 'Inscreva-me na lista de e-mails para que eu receba o boletim informativo mensal, ' +
|
||
// 'bem como e-mails ocasionais sobre o desenvolvimento e as oportunidades do Google Play.',
|
||
// 'privacyPolicy': 'Reconheço que as informações fornecidas neste formulário estão sujeitas à <a href="' +
|
||
// 'https://www.google.com.br/policies/privacy/" target="_blank">Política de Privacidade</a> do Google.',
|
||
// 'languageVal': 'Italian (italiano)',
|
||
// 'successTitle': 'Uhu!',
|
||
// 'successDetails': 'Você se inscreveu para receber as notícias e as dicas mais recentes para os ' +
|
||
// 'desenvolvedores Android.',
|
||
// }
|
||
//});
|
||
break;
|
||
case 'ja':
|
||
window.polyglot.extend({
|
||
'newsletter': {
|
||
'title': 'Google Play での成功に役立つ Android デベロッパー向けの最新ニュースやおすすめの情報をお届けします。',
|
||
'requiredHint': '* 必須',
|
||
'name': '氏名',
|
||
'email': 'メールアドレス',
|
||
'company': '会社名 / デベロッパー名',
|
||
'appUrl': 'Play ストア アプリの URL(いずれか 1 つ)',
|
||
'business': {
|
||
'label': 'お客様のビジネスに最もよく当てはまるものをお選びください。',
|
||
'apps': 'アプリ',
|
||
'games': 'ゲーム',
|
||
'both': 'アプリとゲーム'
|
||
},
|
||
'confirmMailingList': '開発や Google Play の最新情報に関する毎月発行のニュースレターや不定期発行のメールを受け取る',
|
||
'privacyPolicy': 'このフォームに入力した情報に <a href="https://www.google.com/intl/ja/policies/privacy/" ' +
|
||
'target="_blank">Google</a> のプライバシー ポリシーが適用',
|
||
'languageVal': 'Japanese (日本語)',
|
||
'successTitle': '完了です!',
|
||
'successDetails': 'Android デベロッパー向けの最新ニュースやおすすめの情報の配信登録が完了しました。'
|
||
}
|
||
});
|
||
break;
|
||
case 'ko':
|
||
window.polyglot.extend({
|
||
'newsletter': {
|
||
'title': 'Google Play에서 성공을 거두는 데 도움이 되는 최신 Android 개발자 소식 및 도움말을 받아 보세요.',
|
||
'requiredHint': '* 필수 입력란',
|
||
'name': '이름',
|
||
'email': '이메일 주소',
|
||
'company': '회사/개발자 이름',
|
||
'appUrl': 'Play 스토어 앱 URL 중 1개',
|
||
'business': {
|
||
'label': '다음 중 내 비즈니스를 가장 잘 설명하는 단어는 무엇인가요?',
|
||
'apps': '앱',
|
||
'games': '게임',
|
||
'both': '앱 및 게임'
|
||
},
|
||
'confirmMailingList': '개발 및 Google Play 관련 소식에 관한 월별 뉴스레터 및 비정기 이메일을 받아보겠습니다.',
|
||
'privacyPolicy': '이 양식에 제공한 정보는 <a href="https://www.google.com/intl/ko/policies/privacy/" ' +
|
||
'target="_blank">Google의</a> 개인정보취급방침에 따라 사용됨을',
|
||
'languageVal':'Korean (한국어)',
|
||
'successTitle': '축하합니다!',
|
||
'successDetails': '최신 Android 개발자 뉴스 및 도움말을 받아볼 수 있도록 가입을 완료했습니다.'
|
||
}
|
||
});
|
||
break;
|
||
case 'pt-br':
|
||
window.polyglot.extend({
|
||
'newsletter': {
|
||
'title': 'Receba as dicas e as notícias mais recentes para os desenvolvedores Android e seja bem-sucedido ' +
|
||
'no Google Play.',
|
||
'requiredHint': '* Campos obrigatórios',
|
||
'name': 'Nome completo',
|
||
'email': 'Endereço de Email',
|
||
'company': 'Nome da empresa / do desenvolvedor',
|
||
'appUrl': 'URL de um dos seus apps da Play Store',
|
||
'business': {
|
||
'label': 'Qual das seguintes opções melhor descreve sua empresa?',
|
||
'apps': 'Apps',
|
||
'games': 'Jogos',
|
||
'both': 'Apps e Jogos'
|
||
},
|
||
'confirmMailingList': 'Inscreva-me na lista de e-mails para que eu receba o boletim informativo mensal, ' +
|
||
'bem como e-mails ocasionais sobre o desenvolvimento e as oportunidades do Google Play.',
|
||
'privacyPolicy': 'Reconheço que as informações fornecidas neste formulário estão sujeitas à <a href="' +
|
||
'https://www.google.com.br/policies/privacy/" target="_blank">Política de Privacidade</a> do Google.',
|
||
'languageVal': 'Brazilian Portuguese (Português Brasileiro)',
|
||
'successTitle': 'Uhu!',
|
||
'successDetails': 'Você se inscreveu para receber as notícias e as dicas mais recentes para os ' +
|
||
'desenvolvedores Android.'
|
||
}
|
||
});
|
||
break;
|
||
case 'ru':
|
||
window.polyglot.extend({
|
||
'newsletter': {
|
||
'title': 'Хотите получать последние новости и советы для разработчиков Google Play? Заполните эту форму.',
|
||
'requiredHint': '* Обязательные поля',
|
||
'name': 'Полное имя',
|
||
'email': 'Адрес электронной почты',
|
||
'company': 'Название компании или имя разработчика',
|
||
'appUrl': 'Ссылка на любое ваше приложение в Google Play',
|
||
'business': {
|
||
'label': 'Что вы создаете?',
|
||
'apps': 'Приложения',
|
||
'games': 'Игры',
|
||
'both': 'Игры и приложения'
|
||
},
|
||
'confirmMailingList': 'Я хочу получать ежемесячную рассылку для разработчиков и другие полезные новости ' +
|
||
'Google Play.',
|
||
'privacyPolicy': 'Я предоставляю эти данные в соответствии с <a href="' +
|
||
'https://www.google.com/intl/ru/policies/privacy/" target="_blank">Политикой конфиденциальности</a> Google.',
|
||
'languageVal': 'Russian (Русский)',
|
||
'successTitle': 'Поздравляем!',
|
||
'successDetails': 'Теперь вы подписаны на последние новости и советы для разработчиков Android.'
|
||
}
|
||
});
|
||
break;
|
||
case 'es':
|
||
window.polyglot.extend({
|
||
'newsletter': {
|
||
'title': 'Recibe las últimas noticias y sugerencias para programadores de Android y logra tener éxito en ' +
|
||
'Google Play.',
|
||
'requiredHint': '* Campos obligatorios',
|
||
'name': 'Dirección de correo electrónico',
|
||
'email': 'Endereço de Email',
|
||
'company': 'Nombre de la empresa o del programador',
|
||
'appUrl': 'URL de una de tus aplicaciones de Play Store',
|
||
'business': {
|
||
'label': '¿Qué describe mejor a tu empresa?',
|
||
'apps': 'Aplicaciones',
|
||
'games': 'Juegos',
|
||
'both': 'Juegos y aplicaciones'
|
||
},
|
||
'confirmMailingList': 'Deseo unirme a la lista de distribución para recibir el boletín informativo mensual ' +
|
||
'y correos electrónicos ocasionales sobre desarrollo y oportunidades de Google Play.',
|
||
'privacyPolicy': 'Acepto que la información que proporcioné en este formulario cumple con la <a href="' +
|
||
'https://www.google.com/intl/es/policies/privacy/" target="_blank">política de privacidad</a> de Google.',
|
||
'languageVal': 'Spanish (español)',
|
||
'successTitle': '¡Felicitaciones!',
|
||
'successDetails': 'El registro para recibir las últimas noticias y sugerencias para programadores de Android ' +
|
||
'se realizó correctamente.'
|
||
}
|
||
});
|
||
break;
|
||
case 'th':
|
||
window.polyglot.extend({
|
||
'newsletter': {
|
||
'title': 'รับข่าวสารล่าสุดสำหรับนักพัฒนาซอฟต์แวร์ Android ตลอดจนเคล็ดลับที่จะช่วยให้คุณประสบความสำเร็จบน ' +
|
||
'Google Play',
|
||
'requiredHint': '* ช่องที่ต้องกรอก',
|
||
'name': 'ชื่อและนามสกุล',
|
||
'email': 'ที่อยู่อีเมล',
|
||
'company': 'ชื่อบริษัท/นักพัฒนาซอฟต์แวร์',
|
||
'appUrl': 'URL แอปใดแอปหนึ่งของคุณใน Play สโตร์',
|
||
'business': {
|
||
'label': 'ข้อใดตรงกับธุรกิจของคุณมากที่สุด',
|
||
'apps': 'แอป',
|
||
'games': 'เกม',
|
||
'both': 'แอปและเกม'
|
||
},
|
||
'confirmMailingList': 'เพิ่มฉันลงในรายชื่ออีเมลเพื่อรับจดหมายข่าวรายเดือนและอีเมลเป็นครั้งคราวเกี่ยวกับก' +
|
||
'ารพัฒนาซอฟต์แวร์และโอกาสใน Google Play',
|
||
'privacyPolicy': 'ฉันรับทราบว่าข้อมูลที่ให้ไว้ในแบบฟอร์มนี้จะเป็นไปตามนโยบายส่วนบุคคลของ ' +
|
||
'<a href="https://www.google.com/intl/th/policies/privacy/" target="_blank">Google</a>',
|
||
'languageVal': 'Thai (ภาษาไทย)',
|
||
'successTitle': 'ไชโย!',
|
||
'successDetails': 'คุณลงชื่อสมัครรับข่าวสารและเคล็ดลับล่าสุดสำหรับนักพัฒนาซอฟต์แวร์ Android เสร็จเรียบร้อยแล้ว'
|
||
}
|
||
});
|
||
break;
|
||
case 'tr':
|
||
window.polyglot.extend({
|
||
'newsletter': {
|
||
'title': 'Google Play\'de başarılı olmanıza yardımcı olacak en son Android geliştirici haberleri ve ipuçları.',
|
||
'requiredHint': '* Zorunlu Alanlar',
|
||
'name': 'Tam ad',
|
||
'email': 'E-posta adresi',
|
||
'company': 'Şirket / geliştirici adı',
|
||
'appUrl': 'Play Store uygulama URL\'lerinizden biri',
|
||
'business': {
|
||
'label': 'İşletmenizi en iyi hangisi tanımlar?',
|
||
'apps': 'Uygulamalar',
|
||
'games': 'Oyunlar',
|
||
'both': 'Uygulamalar ve Oyunlar'
|
||
},
|
||
'confirmMailingList': 'Beni, geliştirme ve Google Play fırsatlarıyla ilgili ara sıra gönderilen e-posta ' +
|
||
'iletilerine ilişkin posta listesine ve aylık haber bültenine ekle.',
|
||
'privacyPolicy': 'Bu formda sağlanan bilgilerin Google\'ın ' +
|
||
'<a href="https://www.google.com/intl/tr/policies/privacy/" target="_blank">Gizlilik Politikası\'na</a> ' +
|
||
'tabi olacağını kabul ediyorum.',
|
||
'languageVal': 'Turkish (Türkçe)',
|
||
'successTitle': 'Yaşasın!',
|
||
'successDetails': 'En son Android geliştirici haberleri ve ipuçlarına başarıyla kaydoldunuz.'
|
||
}
|
||
});
|
||
break;
|
||
case 'vi':
|
||
window.polyglot.extend({
|
||
'newsletter': {
|
||
'title': 'Nhận tin tức và mẹo mới nhất dành cho nhà phát triển Android sẽ giúp bạn tìm thấy thành công trên ' +
|
||
'Google Play.',
|
||
'requiredHint': '* Các trường bắt buộc',
|
||
'name': 'Tên đầy đủ',
|
||
'email': 'Địa chỉ email',
|
||
'company': 'Tên công ty/nhà phát triển',
|
||
'appUrl': 'Một trong số các URL ứng dụng trên cửa hàng Play của bạn',
|
||
'business': {
|
||
'label': 'Lựa chọn nào sau đây mô tả chính xác nhất doanh nghiệp của bạn?',
|
||
'apps': 'Ứng dụng',
|
||
'games': 'Trò chơi',
|
||
'both': 'Ứng dụng và trò chơi'
|
||
},
|
||
'confirmMailingList': 'Thêm tôi vào danh sách gửi thư cho bản tin hàng tháng và email định kỳ về việc phát ' +
|
||
'triển và cơ hội của Google Play.',
|
||
'privacyPolicy': 'Tôi xác nhận rằng thông tin được cung cấp trong biểu mẫu này tuân thủ chính sách bảo mật ' +
|
||
'của <a href="https://www.google.com/intl/vi/policies/privacy/" target="_blank">Google</a>.',
|
||
'languageVal': 'Vietnamese (tiếng Việt)',
|
||
'successTitle': 'Thật tuyệt!',
|
||
'successDetails': 'Bạn đã đăng ký thành công nhận tin tức và mẹo mới nhất dành cho nhà phát triển của Android.'
|
||
}
|
||
});
|
||
break;
|
||
}
|
||
|
||
(function($) {
|
||
'use strict';
|
||
|
||
function Modal(el, options) {
|
||
this.el = $(el);
|
||
this.options = $.extend({}, options);
|
||
this.isOpen = false;
|
||
|
||
this.el.on('click', function(event) {
|
||
if (!$.contains(this.el.find('.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() {
|
||
// When closing the modal for Android Studio downloads, reload the page
|
||
// because otherwise we might get stuck with post-download dialog state
|
||
if ($("[data-modal='studio_tos'].dac-active").length) {
|
||
location.reload();
|
||
}
|
||
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 onClickToggleModal(event) {
|
||
event.preventDefault();
|
||
var toggle = $(event.currentTarget);
|
||
var options = toggle.data();
|
||
var modal = options.modalToggle ? $('[data-modal="' + options.modalToggle + '"]') :
|
||
toggle.closest('[data-modal]');
|
||
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());
|
||
});
|
||
|
||
$('html').on('click.modal', '[data-modal-toggle]', onClickToggleModal);
|
||
|
||
// Check if url anchor is targetting a toggle to open the modal.
|
||
if (location.hash) {
|
||
var $elem = $(document.getElementById(location.hash.substr(1)));
|
||
if ($elem.attr('data-modal-toggle')) {
|
||
$elem.trigger('click');
|
||
}
|
||
}
|
||
|
||
var isTargetLangValid = false;
|
||
$(ANDROID_LANGUAGES).each(function(index, langCode) {
|
||
if (langCode == window.getLangTarget()) {
|
||
isTargetLangValid = true;
|
||
return;
|
||
}
|
||
});
|
||
if (window.getLangTarget() !== window.getLangPref() && isTargetLangValid) {
|
||
$('#langform').trigger('modal-open');
|
||
$("#langform button.yes").attr("onclick","window.changeLangPref('" + window.getLangTarget() + "', true); return false;");
|
||
$("#langform button.no").attr("onclick","window.changeLangPref('" + window.getLangPref() + "', true); return false;");
|
||
}
|
||
|
||
/* Check the current API level, but only if we're in the reference */
|
||
if (location.pathname.indexOf('/reference') == 0) {
|
||
// init available apis based on user pref
|
||
changeApiLevel();
|
||
}
|
||
});
|
||
})(jQuery);
|
||
|
||
/* Fullscreen - Toggle fullscreen mode for reference pages */
|
||
(function($) {
|
||
'use strict';
|
||
|
||
/**
|
||
* @param {HTMLElement} el - The DOM element.
|
||
* @constructor
|
||
*/
|
||
function Fullscreen(el) {
|
||
this.el = $(el);
|
||
this.html = $('html');
|
||
this.icon = this.el.find('.dac-sprite');
|
||
this.isFullscreen = window.readCookie(Fullscreen.COOKIE_) === 'true';
|
||
this.activate_();
|
||
this.el.on('click.dac-fullscreen', this.toggleHandler_.bind(this));
|
||
}
|
||
|
||
/**
|
||
* Cookie name for storing the state
|
||
* @type {string}
|
||
* @private
|
||
*/
|
||
Fullscreen.COOKIE_ = 'fullscreen';
|
||
|
||
/**
|
||
* Classes to modify the DOM
|
||
* @type {{mode: string, fullscreen: string, fullscreenExit: string}}
|
||
* @private
|
||
*/
|
||
Fullscreen.CLASSES_ = {
|
||
mode: 'dac-fullscreen-mode',
|
||
fullscreen: 'dac-fullscreen',
|
||
fullscreenExit: 'dac-fullscreen-exit'
|
||
};
|
||
|
||
/**
|
||
* Event listener for toggling fullscreen mode
|
||
* @param {MouseEvent} event
|
||
* @private
|
||
*/
|
||
Fullscreen.prototype.toggleHandler_ = function(event) {
|
||
event.stopPropagation();
|
||
this.toggle(!this.isFullscreen, true);
|
||
};
|
||
|
||
/**
|
||
* Change the DOM based on current state.
|
||
* @private
|
||
*/
|
||
Fullscreen.prototype.activate_ = function() {
|
||
this.icon.toggleClass(Fullscreen.CLASSES_.fullscreen, !this.isFullscreen);
|
||
this.icon.toggleClass(Fullscreen.CLASSES_.fullscreenExit, this.isFullscreen);
|
||
this.html.toggleClass(Fullscreen.CLASSES_.mode, this.isFullscreen);
|
||
};
|
||
|
||
/**
|
||
* Toggle fullscreen mode and store the state in a cookie.
|
||
*/
|
||
Fullscreen.prototype.toggle = function() {
|
||
this.isFullscreen = !this.isFullscreen;
|
||
window.writeCookie(Fullscreen.COOKIE_, this.isFullscreen, null);
|
||
this.activate_();
|
||
};
|
||
|
||
/**
|
||
* jQuery plugin
|
||
*/
|
||
$.fn.dacFullscreen = function() {
|
||
return this.each(function() {
|
||
new Fullscreen($(this));
|
||
});
|
||
};
|
||
})(jQuery);
|
||
|
||
(function($) {
|
||
'use strict';
|
||
|
||
/**
|
||
* @param {HTMLElement} selected - The link that is selected in the nav.
|
||
* @constructor
|
||
*/
|
||
function HeaderTabs(selected) {
|
||
|
||
// Don't highlight any tabs on the index page
|
||
if (location.pathname === '/index.html' || location.pathname === '/') {
|
||
//return;
|
||
}
|
||
|
||
this.selected = $(selected);
|
||
this.selectedParent = this.selected.closest('.dac-nav-secondary').siblings('a');
|
||
this.links = $('.dac-header-tabs a');
|
||
|
||
this.selectActiveTab();
|
||
}
|
||
|
||
HeaderTabs.prototype.selectActiveTab = function() {
|
||
var section = null;
|
||
|
||
if (this.selectedParent.length) {
|
||
section = this.selectedParent.text();
|
||
} else {
|
||
section = this.selected.text();
|
||
}
|
||
|
||
if (section) {
|
||
this.links.removeClass('selected');
|
||
|
||
this.links.filter(function() {
|
||
return $(this).text() === $.trim(section);
|
||
}).addClass('selected');
|
||
}
|
||
};
|
||
|
||
/**
|
||
* jQuery plugin
|
||
*/
|
||
$.fn.dacHeaderTabs = function() {
|
||
return this.each(function() {
|
||
new HeaderTabs(this);
|
||
});
|
||
};
|
||
})(jQuery);
|
||
|
||
(function($) {
|
||
'use strict';
|
||
var icon = $('<i/>').addClass('dac-sprite dac-nav-forward');
|
||
var config = JSON.parse(window.localStorage.getItem('global-navigation') || '{}');
|
||
var forwardLink = $('<span/>')
|
||
.addClass('dac-nav-link-forward')
|
||
.html(icon)
|
||
.attr('tabindex', 0)
|
||
.on('click keypress', function(e) {
|
||
if (e.type == 'keypress' && e.which == 13 || e.type == 'click') {
|
||
swap_(e);
|
||
}
|
||
});
|
||
|
||
/**
|
||
* @constructor
|
||
*/
|
||
function Nav(navigation) {
|
||
$('.dac-nav-list').dacCurrentPage().dacHeaderTabs().dacSidebarToggle($('body'));
|
||
|
||
navigation.find('[data-reference-tree]').dacReferenceNav();
|
||
|
||
setupViews_(navigation.children().eq(0).children());
|
||
|
||
initCollapsedNavs(navigation.find('.dac-nav-sub-slider'));
|
||
|
||
$('#dac-main-navigation').scrollIntoView('.selected')
|
||
}
|
||
|
||
function updateStore(icon) {
|
||
var navClass = getCurrentLandingPage_(icon);
|
||
var isExpanded = icon.hasClass('dac-expand-less-black');
|
||
var expandedNavs = config.expanded || [];
|
||
if (isExpanded) {
|
||
expandedNavs.push(navClass);
|
||
} else {
|
||
expandedNavs = expandedNavs.filter(function(item) {
|
||
return item !== navClass;
|
||
});
|
||
}
|
||
config.expanded = expandedNavs;
|
||
window.localStorage.setItem('global-navigation', JSON.stringify(config));
|
||
}
|
||
|
||
function toggleSubNav_(icon) {
|
||
var isExpanded = icon.hasClass('dac-expand-less-black');
|
||
icon.toggleClass('dac-expand-less-black', !isExpanded);
|
||
icon.toggleClass('dac-expand-more-black', isExpanded);
|
||
icon.data('sub-navigation.dac').slideToggle(200);
|
||
|
||
updateStore(icon);
|
||
}
|
||
|
||
function handleSubNavToggle_(event) {
|
||
event.preventDefault();
|
||
var icon = $(event.target);
|
||
toggleSubNav_(icon);
|
||
}
|
||
|
||
function getCurrentLandingPage_(icon) {
|
||
return icon.closest('li')[0].className.replace('dac-nav-item ', '');
|
||
}
|
||
|
||
// Setup sub navigation collapse/expand
|
||
function initCollapsedNavs(toggleIcons) {
|
||
toggleIcons.each(setInitiallyActive_($('body')));
|
||
toggleIcons.on('click keypress', function(e) {
|
||
if (e.type == 'keypress' && e.which == 13 || e.type == 'click') {
|
||
handleSubNavToggle_(e);
|
||
}
|
||
});
|
||
}
|
||
|
||
function setInitiallyActive_(body) {
|
||
var expandedNavs = config.expanded || [];
|
||
return function(i, icon) {
|
||
icon = $(icon);
|
||
var subNav = icon.next();
|
||
|
||
if (!subNav.length) {
|
||
return;
|
||
}
|
||
|
||
var landingPageClass = getCurrentLandingPage_(icon);
|
||
var expanded = expandedNavs.indexOf(landingPageClass) >= 0;
|
||
landingPageClass = landingPageClass === 'home' ? 'about' : landingPageClass;
|
||
|
||
if (landingPageClass == 'about' && location.pathname == '/index.html') {
|
||
expanded = true;
|
||
}
|
||
|
||
// TODO: Should read from localStorage
|
||
var visible = body.hasClass(landingPageClass) || expanded;
|
||
|
||
icon.data('sub-navigation.dac', subNav);
|
||
icon.toggleClass('dac-expand-less-black', visible);
|
||
icon.toggleClass('dac-expand-more-black', !visible);
|
||
subNav.toggle(visible);
|
||
};
|
||
}
|
||
|
||
function setupViews_(views) {
|
||
if (views.length === 1) {
|
||
// Active tier 1 nav.
|
||
views.addClass('dac-active');
|
||
} else {
|
||
// Activate back button and tier 2 nav.
|
||
views.slice(0, 2).addClass('dac-active');
|
||
var selectedNav = views.eq(2).find('.selected').after(forwardLink);
|
||
var langAttr = selectedNav.attr(window.getLangPref() + '-lang');
|
||
//form the label from locale attr if possible, else set to selectedNav text value
|
||
if ((typeof langAttr !== typeof undefined && langAttr !== false) && (langAttr !== '')) {
|
||
$('.dac-nav-back-title').text(langAttr);
|
||
} else {
|
||
$('.dac-nav-back-title').text(selectedNav.text());
|
||
}
|
||
}
|
||
|
||
// Navigation should animate.
|
||
setTimeout(function() {
|
||
views.removeClass('dac-no-anim');
|
||
}, 10);
|
||
}
|
||
|
||
function swap_(event) {
|
||
event.preventDefault();
|
||
$(event.currentTarget).trigger('swap-content');
|
||
}
|
||
|
||
/**
|
||
* jQuery plugin
|
||
*/
|
||
$.fn.dacNav = function() {
|
||
return this.each(function() {
|
||
new Nav($(this));
|
||
});
|
||
};
|
||
})(jQuery);
|
||
|
||
/* global NAVTREE_DATA */
|
||
(function($) {
|
||
/**
|
||
* Build the reference navigation with namespace dropdowns.
|
||
* @param {jQuery} el - The DOM element.
|
||
*/
|
||
function buildReferenceNav(el) {
|
||
var supportLibraryPath = '/reference/android/support/';
|
||
var currPath = location.pathname;
|
||
|
||
if (currPath.indexOf(supportLibraryPath) > -1) {
|
||
updateSupportLibrariesNav(supportLibraryPath, currPath);
|
||
}
|
||
var namespaceList = el.find('[data-reference-namespaces]');
|
||
var resources = $('[data-reference-resources]').detach();
|
||
var selected = namespaceList.find('.selected');
|
||
resources.appendTo(el);
|
||
|
||
// Links should be toggleable.
|
||
namespaceList.find('a').addClass('dac-reference-nav-toggle dac-closed');
|
||
|
||
// Set the path for the navtree data to use.
|
||
var navtree_filepath = getNavtreeFilePath(supportLibraryPath, currPath);
|
||
|
||
// Load in all resources
|
||
$.getScript(navtree_filepath, function(data, textStatus, xhr) {
|
||
if (xhr.status === 200) {
|
||
namespaceList.on(
|
||
'click', 'a.dac-reference-nav-toggle', toggleResourcesHandler);
|
||
}
|
||
});
|
||
|
||
// No setup required if no resources are present
|
||
if (!resources.length) {
|
||
return;
|
||
}
|
||
|
||
// The resources should be a part of selected namespace.
|
||
var overview = addResourcesToView(resources, selected);
|
||
|
||
// Currently viewing Overview
|
||
if (location.href === overview.attr('href')) {
|
||
overview.parent().addClass('selected');
|
||
}
|
||
|
||
// Open currently selected resource
|
||
var listsToOpen = selected.children().eq(1);
|
||
listsToOpen = listsToOpen.add(
|
||
listsToOpen.find('.selected').parent()).show();
|
||
|
||
// Mark dropdowns as open
|
||
listsToOpen.prev().removeClass('dac-closed');
|
||
|
||
// Scroll into view
|
||
namespaceList.scrollIntoView(selected);
|
||
}
|
||
|
||
function getNavtreeFilePath(supportLibraryPath, currPath) {
|
||
var navtree_filepath = '';
|
||
var navtree_filename = 'navtree_data.js';
|
||
if (currPath.indexOf(supportLibraryPath + 'test') > -1) {
|
||
navtree_filepath = supportLibraryPath + 'test/' + navtree_filename;
|
||
} else if (currPath.indexOf(supportLibraryPath + 'wearable') > -1) {
|
||
navtree_filepath = supportLibraryPath + 'wearable/' + navtree_filename;
|
||
} else {
|
||
navtree_filepath = '/' + navtree_filename;
|
||
}
|
||
return navtree_filepath;
|
||
}
|
||
|
||
function updateSupportLibrariesNav(supportLibraryPath, currPath) {
|
||
var navTitle = '';
|
||
if (currPath.indexOf(supportLibraryPath + 'test') > -1) {
|
||
navTitle = 'Test Support APIs';
|
||
} else if (currPath.indexOf(supportLibraryPath + 'wearable') > -1) {
|
||
navTitle = 'Wearable Support APIs';
|
||
}
|
||
$('#api-nav-title').text(navTitle);
|
||
$('#api-level-toggle').hide();
|
||
}
|
||
|
||
/**
|
||
* Handles the toggling of resources.
|
||
* @param {Event} event
|
||
*/
|
||
function toggleResourcesHandler(event) {
|
||
event.preventDefault();
|
||
if (event.type == 'click' || event.type == 'keypress' && event.which == 13) {
|
||
var el = $(this);
|
||
// If resources for given namespace is not present, fetch correct data.
|
||
if (this.tagName === 'A' && !this.hasResources) {
|
||
addResourcesToView(buildResourcesViewForData(getDataForNamespace(el.text())), el.parent());
|
||
}
|
||
|
||
el.toggleClass('dac-closed').next().slideToggle(200);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param {String} namespace
|
||
* @returns {Array} namespace data
|
||
*/
|
||
function getDataForNamespace(namespace) {
|
||
var namespaceData = NAVTREE_DATA.filter(function(data) {
|
||
return data[0] === namespace;
|
||
});
|
||
|
||
return namespaceData.length ? namespaceData[0][2] : [];
|
||
}
|
||
|
||
/**
|
||
* Build a list item for a resource
|
||
* @param {Array} resource
|
||
* @returns {String}
|
||
*/
|
||
function buildResourceItem(resource) {
|
||
return '<li class="api apilevel-' + resource[3] + '"><a href="/' + resource[1] + '">' + resource[0] + '</a></li>';
|
||
}
|
||
|
||
/**
|
||
* Build resources list items.
|
||
* @param {Array} resources
|
||
* @returns {String}
|
||
*/
|
||
function buildResourceList(resources) {
|
||
return '<li><h2>' + resources[0] + '</h2><ul>' + resources[2].map(buildResourceItem).join('') + '</ul>';
|
||
}
|
||
|
||
/**
|
||
* Build a resources view
|
||
* @param {Array} data
|
||
* @returns {jQuery} resources in an unordered list.
|
||
*/
|
||
function buildResourcesViewForData(data) {
|
||
return $('<ul>' + data.map(buildResourceList).join('') + '</ul>');
|
||
}
|
||
|
||
/**
|
||
* Add resources to a containing view.
|
||
* @param {jQuery} resources
|
||
* @param {jQuery} view
|
||
* @returns {jQuery} the overview link.
|
||
*/
|
||
function addResourcesToView(resources, view) {
|
||
var namespace = view.children().eq(0);
|
||
var overview = $('<a href="' + namespace.attr('href') + '">Overview</a>');
|
||
|
||
// Mark namespace with content;
|
||
namespace[0].hasResources = true;
|
||
|
||
// Add correct classes / event listeners to resources.
|
||
resources.prepend($('<li>').html(overview))
|
||
.find('a')
|
||
.addClass('dac-reference-nav-resource')
|
||
.end()
|
||
.find('h2').attr('tabindex', 0)
|
||
.addClass('dac-reference-nav-toggle dac-closed')
|
||
.on('click keypress', toggleResourcesHandler)
|
||
.end()
|
||
.add(resources.find('ul'))
|
||
.addClass('dac-reference-nav-resources')
|
||
.end()
|
||
.appendTo(view);
|
||
|
||
return overview;
|
||
}
|
||
|
||
function setActiveReferencePackage(el) {
|
||
var packageLinkEls = el.find('[data-reference-namespaces] a');
|
||
var selected = null;
|
||
var highestMatchCount = 0;
|
||
packageLinkEls.each(function(index, linkEl) {
|
||
var matchCount = 0;
|
||
$(location.pathname.split('/')).each(function(index, subpath) {
|
||
if (linkEl.href.indexOf('/' + subpath + '/') > -1) {
|
||
matchCount++;
|
||
}
|
||
});
|
||
if (matchCount > highestMatchCount) {
|
||
selected = linkEl;
|
||
highestMatchCount = matchCount;
|
||
}
|
||
});
|
||
$(selected).parent().addClass('selected');
|
||
}
|
||
|
||
/**
|
||
* jQuery plugin
|
||
*/
|
||
$.fn.dacReferenceNav = function() {
|
||
return this.each(function() {
|
||
setActiveReferencePackage($(this));
|
||
buildReferenceNav($(this));
|
||
});
|
||
};
|
||
})(jQuery);
|
||
|
||
/** Scroll a container to make a target element visible
|
||
This is called when the page finished loading. */
|
||
$.fn.scrollIntoView = function(target) {
|
||
if ('string' === typeof target) {
|
||
target = this.find(target);
|
||
}
|
||
if (this.is(':visible')) {
|
||
if (target.length == 0) {
|
||
// If no selected item found, exit
|
||
return;
|
||
}
|
||
|
||
// get the target element's offset from its container nav by measuring the element's offset
|
||
// relative to the document then subtract the container nav's offset relative to the document
|
||
var targetOffset = target.offset().top - this.offset().top;
|
||
var containerHeight = this.height();
|
||
if (targetOffset > containerHeight * .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 height
|
||
this.scrollTop(targetOffset - (containerHeight * .8));
|
||
}
|
||
}
|
||
};
|
||
|
||
(function($) {
|
||
$.fn.dacCurrentPage = function() {
|
||
// Highlight the header tabs...
|
||
// highlight Design tab
|
||
var baseurl = getBaseUri(window.location.pathname);
|
||
var urlSegments = baseurl.split('/');
|
||
var navEl = this;
|
||
var body = $('body');
|
||
var subNavEl = navEl.find('.dac-nav-secondary');
|
||
var parentNavEl;
|
||
var selected;
|
||
// In NDK docs, highlight appropriate sub-nav
|
||
if (body.hasClass('dac-ndk')) {
|
||
if (body.hasClass('guide')) {
|
||
selected = navEl.find('> li.guides > a').addClass('selected');
|
||
} else if (body.hasClass('reference')) {
|
||
selected = navEl.find('> li.reference > a').addClass('selected');
|
||
} else if (body.hasClass('samples')) {
|
||
selected = navEl.find('> li.samples > a').addClass('selected');
|
||
} else if (body.hasClass('downloads')) {
|
||
selected = navEl.find('> li.downloads > a').addClass('selected');
|
||
}
|
||
} else if (body.hasClass('dac-studio')) {
|
||
if (body.hasClass('download')) {
|
||
selected = navEl.find('> li.download > a').addClass('selected');
|
||
} else if (body.hasClass('features')) {
|
||
selected = navEl.find('> li.features > a').addClass('selected');
|
||
} else if (body.hasClass('guide')) {
|
||
selected = navEl.find('> li.guide > a').addClass('selected');
|
||
} else if (body.hasClass('preview')) {
|
||
selected = navEl.find('> li.preview > a').addClass('selected');
|
||
}
|
||
} else if (body.hasClass('design')) {
|
||
selected = navEl.find('> li.design > a').addClass('selected');
|
||
// highlight Home nav
|
||
} else if (body.hasClass('about') || location.pathname == '/index.html') {
|
||
parentNavEl = navEl.find('> li.home > a');
|
||
parentNavEl.addClass('has-subnav');
|
||
// In Home docs, also highlight appropriate sub-nav
|
||
if (urlSegments[1] === 'wear' || urlSegments[1] === 'tv' ||
|
||
urlSegments[1] === 'auto') {
|
||
selected = subNavEl.find('li.' + urlSegments[1] + ' > a').addClass('selected');
|
||
} else if (urlSegments[1] === 'about') {
|
||
selected = subNavEl.find('li.versions > a').addClass('selected');
|
||
} else {
|
||
selected = parentNavEl.removeClass('has-subnav').addClass('selected');
|
||
}
|
||
// highlight Develop nav
|
||
} 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-nav
|
||
if (urlSegments[1] === 'training') {
|
||
selected = subNavEl.find('li.training > a').addClass('selected');
|
||
} else if (urlSegments[1] === 'guide') {
|
||
selected = 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')) {
|
||
selected = subNavEl.find('li.google > a').addClass('selected');
|
||
} else {
|
||
selected = subNavEl.find('li.reference > a').addClass('selected');
|
||
}
|
||
} else if (body.hasClass('google')) {
|
||
selected = subNavEl.find('li.google > a').addClass('selected');
|
||
} else if (body.hasClass('samples')) {
|
||
selected = subNavEl.find('li.samples > a').addClass('selected');
|
||
} else {
|
||
selected = parentNavEl.removeClass('has-subnav').addClass('selected');
|
||
}
|
||
// highlight Distribute nav
|
||
} else if (body.hasClass('distribute')) {
|
||
parentNavEl = navEl.find('> li.distribute > a');
|
||
parentNavEl.addClass('has-subnav');
|
||
// In Distribute docs, also highlight appropriate sub-nav
|
||
if (urlSegments[2] === 'users') {
|
||
selected = subNavEl.find('li.users > a').addClass('selected');
|
||
} else if (urlSegments[2] === 'engage') {
|
||
selected = subNavEl.find('li.engage > a').addClass('selected');
|
||
} else if (urlSegments[2] === 'monetize') {
|
||
selected = subNavEl.find('li.monetize > a').addClass('selected');
|
||
} else if (urlSegments[2] === 'analyze') {
|
||
selected = subNavEl.find('li.analyze > a').addClass('selected');
|
||
} else if (urlSegments[2] === 'tools') {
|
||
selected = subNavEl.find('li.disttools > a').addClass('selected');
|
||
} else if (urlSegments[2] === 'stories') {
|
||
selected = subNavEl.find('li.stories > a').addClass('selected');
|
||
} else if (urlSegments[2] === 'essentials') {
|
||
selected = subNavEl.find('li.essentials > a').addClass('selected');
|
||
} else if (urlSegments[2] === 'googleplay') {
|
||
selected = subNavEl.find('li.googleplay > a').addClass('selected');
|
||
} else {
|
||
selected = parentNavEl.removeClass('has-subnav').addClass('selected');
|
||
}
|
||
} else if (body.hasClass('preview')) {
|
||
selected = navEl.find('> li.preview > a').addClass('selected');
|
||
}
|
||
return $(selected);
|
||
};
|
||
})(jQuery);
|
||
|
||
(function($) {
|
||
'use strict';
|
||
|
||
/**
|
||
* Toggle the visabilty of the mobile navigation.
|
||
* @param {HTMLElement} el - The DOM element.
|
||
* @param {Object} options
|
||
* @constructor
|
||
*/
|
||
function ToggleNav(el, options) {
|
||
this.el = $(el);
|
||
this.options = $.extend({}, ToggleNav.DEFAULTS_, options);
|
||
this.body = $(document.body);
|
||
this.navigation_ = this.body.find(this.options.navigation);
|
||
this.el.on('click', this.clickHandler_.bind(this));
|
||
}
|
||
|
||
ToggleNav.BREAKPOINT_ = 980;
|
||
|
||
/**
|
||
* Open on correct sizes
|
||
*/
|
||
function toggleSidebarVisibility(body) {
|
||
var wasClosed = ('' + localStorage.getItem('navigation-open')) === 'false';
|
||
// Override the local storage setting for navigation-open for child sites
|
||
// with no-subnav class.
|
||
if (document.body.classList.contains('no-subnav')) {
|
||
wasClosed = false;
|
||
}
|
||
|
||
if (wasClosed) {
|
||
body.removeClass(ToggleNav.DEFAULTS_.activeClass);
|
||
} else if (window.innerWidth >= ToggleNav.BREAKPOINT_) {
|
||
body.addClass(ToggleNav.DEFAULTS_.activeClass);
|
||
} else {
|
||
body.removeClass(ToggleNav.DEFAULTS_.activeClass);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* ToggleNav Default Settings
|
||
* @type {{body: boolean, dimmer: string, navigation: string, activeClass: string}}
|
||
* @private
|
||
*/
|
||
ToggleNav.DEFAULTS_ = {
|
||
body: true,
|
||
dimmer: '.dac-nav-dimmer',
|
||
animatingClass: 'dac-nav-animating',
|
||
navigation: '[data-dac-nav]',
|
||
activeClass: 'dac-nav-open'
|
||
};
|
||
|
||
/**
|
||
* The actual toggle logic.
|
||
* @param {Event} event
|
||
* @private
|
||
*/
|
||
ToggleNav.prototype.clickHandler_ = function(event) {
|
||
event.preventDefault();
|
||
var animatingClass = this.options.animatingClass;
|
||
var body = this.body;
|
||
|
||
body.addClass(animatingClass);
|
||
body.toggleClass(this.options.activeClass);
|
||
|
||
setTimeout(function() {
|
||
body.removeClass(animatingClass);
|
||
}, this.navigation_.transitionDuration());
|
||
|
||
if (window.innerWidth >= ToggleNav.BREAKPOINT_) {
|
||
localStorage.setItem('navigation-open', body.hasClass(this.options.activeClass));
|
||
}
|
||
};
|
||
|
||
/**
|
||
* jQuery plugin
|
||
* @param {object} options - Override default options.
|
||
*/
|
||
$.fn.dacToggleMobileNav = function() {
|
||
return this.each(function() {
|
||
var el = $(this);
|
||
new ToggleNav(el, el.data());
|
||
});
|
||
};
|
||
|
||
$.fn.dacSidebarToggle = function(body) {
|
||
toggleSidebarVisibility(body);
|
||
$(window).on('resize', toggleSidebarVisibility.bind(null, body));
|
||
};
|
||
|
||
/**
|
||
* Data Attribute API
|
||
*/
|
||
$(function() {
|
||
$('[data-dac-toggle-nav]').dacToggleMobileNav();
|
||
});
|
||
})(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.el.find('[data-newsletter-language]').val(window.polyglot.t('newsletter.languageVal'));
|
||
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);
|
||
|
||
/* globals METADATA, YOUTUBE_RESOURCES, BLOGGER_RESOURCES */
|
||
window.metadata = {};
|
||
|
||
/**
|
||
* Prepare metadata and indices for querying.
|
||
*/
|
||
window.metadata.prepare = (function() {
|
||
// Helper functions.
|
||
function mergeArrays() {
|
||
return Array.prototype.concat.apply([], arguments);
|
||
}
|
||
|
||
/**
|
||
* Creates lookup maps for a resource index.
|
||
* I.e. where MAP['some tag'][resource.id] === true when that resource has 'some tag'.
|
||
* @param resourceDict
|
||
* @returns {{}}
|
||
*/
|
||
function buildResourceLookupMap(resourceDict) {
|
||
var map = {};
|
||
for (var key in resourceDict) {
|
||
var dictForKey = {};
|
||
var srcArr = resourceDict[key];
|
||
for (var i = 0; i < srcArr.length; i++) {
|
||
dictForKey[srcArr[i].index] = true;
|
||
}
|
||
map[key] = dictForKey;
|
||
}
|
||
return map;
|
||
}
|
||
|
||
/**
|
||
* Merges metadata maps for english and the current language into the global store.
|
||
*/
|
||
function mergeMetadataMap(name, locale) {
|
||
if (locale && locale !== 'en' && METADATA[locale]) {
|
||
METADATA[name] = $.extend(METADATA.en[name], METADATA[locale][name]);
|
||
} else {
|
||
METADATA[name] = METADATA.en[name];
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Index all resources by type, url, tag and category.
|
||
* @param resources
|
||
*/
|
||
function createIndices(resources) {
|
||
// URL, type, tag and category lookups
|
||
var byType = METADATA.byType = {};
|
||
var byUrl = METADATA.byUrl = {};
|
||
var byTag = METADATA.byTag = {};
|
||
var byCategory = METADATA.byCategory = {};
|
||
|
||
for (var i = 0; i < resources.length; i++) {
|
||
var res = resources[i];
|
||
|
||
// Store index.
|
||
res.index = i;
|
||
|
||
// Index by type.
|
||
var type = res.type;
|
||
if (type) {
|
||
byType[type] = byType[type] || [];
|
||
byType[type].push(res);
|
||
}
|
||
|
||
// Index by tag.
|
||
var tags = res.tags || [];
|
||
for (var j = 0; j < tags.length; j++) {
|
||
var tag = tags[j];
|
||
if (tag) {
|
||
byTag[tag] = byTag[tag] || [];
|
||
byTag[tag].push(res);
|
||
}
|
||
}
|
||
|
||
// Index by category.
|
||
var category = res.category;
|
||
if (category) {
|
||
byCategory[category] = byCategory[category] || [];
|
||
byCategory[category].push(res);
|
||
}
|
||
|
||
// Index by url.
|
||
var url = res.url;
|
||
if (url) {
|
||
res.baseUrl = url.replace(/^intl\/\w+[\/]/, '');
|
||
byUrl[res.baseUrl] = res;
|
||
}
|
||
}
|
||
METADATA.hasType = buildResourceLookupMap(byType);
|
||
METADATA.hasTag = buildResourceLookupMap(byTag);
|
||
METADATA.hasCategory = buildResourceLookupMap(byCategory);
|
||
}
|
||
|
||
return function() {
|
||
// Only once.
|
||
if (METADATA.all) { return; }
|
||
|
||
// Get current language.
|
||
var locale = getLangPref();
|
||
// Merge english resources.
|
||
if (useDevsiteMetadata) {
|
||
var all_keys = Object.keys(METADATA['en']);
|
||
METADATA.all = []
|
||
|
||
$(all_keys).each(function(index, category) {
|
||
if (RESERVED_METADATA_CATEGORY_NAMES.indexOf(category) == -1) {
|
||
METADATA.all = mergeArrays(
|
||
METADATA.all,
|
||
METADATA.en[category]
|
||
);
|
||
}
|
||
});
|
||
|
||
METADATA.all = mergeArrays(
|
||
METADATA.all,
|
||
YOUTUBE_RESOURCES,
|
||
BLOGGER_RESOURCES,
|
||
METADATA.en.extras
|
||
);
|
||
} else {
|
||
METADATA.all = mergeArrays(
|
||
METADATA.en.about,
|
||
METADATA.en.design,
|
||
METADATA.en.distribute,
|
||
METADATA.en.develop,
|
||
YOUTUBE_RESOURCES,
|
||
BLOGGER_RESOURCES,
|
||
METADATA.en.extras
|
||
);
|
||
}
|
||
|
||
// Merge local language resources.
|
||
if (locale !== 'en' && METADATA[locale]) {
|
||
if (useDevsiteMetadata) {
|
||
all_keys = Object.keys(METADATA[locale]);
|
||
$(all_keys).each(function(index, category) {
|
||
if (RESERVED_METADATA_CATEGORY_NAMES.indexOf(category) == -1) {
|
||
METADATA.all = mergeArrays(
|
||
METADATA.all,
|
||
METADATA.en[category]
|
||
);
|
||
}
|
||
});
|
||
|
||
METADATA.all = mergeArrays(
|
||
METADATA.all,
|
||
METADATA[locale].extras
|
||
);
|
||
} else {
|
||
METADATA.all = mergeArrays(
|
||
METADATA.all,
|
||
METADATA[locale].about,
|
||
METADATA[locale].design,
|
||
METADATA[locale].distribute,
|
||
METADATA[locale].develop,
|
||
METADATA[locale].extras
|
||
);
|
||
|
||
}
|
||
}
|
||
|
||
mergeMetadataMap('collections', locale);
|
||
mergeMetadataMap('searchHeroCollections', locale);
|
||
mergeMetadataMap('carousel', locale);
|
||
|
||
// Create query indicies for resources.
|
||
createIndices(METADATA.all, locale);
|
||
|
||
// Reference metadata.
|
||
METADATA.androidReference = mergeArrays(
|
||
window.DATA, window.SUPPORT_WEARABLE_DATA, window.SUPPORT_TEST_DATA);
|
||
METADATA.googleReference = mergeArrays(window.GMS_DATA, window.GCM_DATA);
|
||
};
|
||
})();
|
||
|
||
/* global METADATA, util */
|
||
window.metadata.query = (function($) {
|
||
var pageMap = {};
|
||
|
||
function buildResourceList(opts) {
|
||
window.metadata.prepare();
|
||
var expressions = parseResourceQuery(opts.query || '');
|
||
var instanceMap = {};
|
||
var results = [];
|
||
|
||
for (var i = 0; i < expressions.length; i++) {
|
||
var clauses = expressions[i];
|
||
|
||
// Get all resources for first clause
|
||
var resources = getResourcesForClause(clauses.shift());
|
||
|
||
// Concat to final results list
|
||
results = results.concat(resources.map(filterResources(clauses, i > 0, instanceMap)).filter(filterEmpty));
|
||
}
|
||
|
||
// Set correct order
|
||
if (opts.sortOrder && results.length) {
|
||
results = opts.sortOrder === 'random' ? util.shuffle(results) : results.sort(sortResultsByKey(opts.sortOrder));
|
||
}
|
||
|
||
// Slice max results.
|
||
if (opts.maxResults !== Infinity) {
|
||
results = results.slice(0, opts.maxResults);
|
||
}
|
||
|
||
// Remove page level duplicates
|
||
if (opts.allowDuplicates === undefined || opts.allowDuplicates === 'false') {
|
||
results = results.filter(removePageLevelDuplicates);
|
||
|
||
for (var index = 0; index < results.length; ++index) {
|
||
pageMap[results[index].index] = 1;
|
||
}
|
||
}
|
||
|
||
return results;
|
||
}
|
||
|
||
function filterResources(clauses, removeDuplicates, map) {
|
||
return function(resource) {
|
||
var resourceIsAllowed = true;
|
||
|
||
// References must be defined.
|
||
if (resource === undefined) {
|
||
return;
|
||
}
|
||
|
||
// Get canonical (localized) version of resource if possible.
|
||
resource = METADATA.byUrl[resource.baseUrl] || METADATA.byUrl[resource.url] || resource;
|
||
|
||
// Filter out resources already used
|
||
if (removeDuplicates) {
|
||
resourceIsAllowed = !map[resource.index];
|
||
}
|
||
|
||
// Must fulfill all criteria
|
||
if (clauses.length > 0) {
|
||
resourceIsAllowed = resourceIsAllowed && doesResourceMatchClauses(resource, clauses);
|
||
}
|
||
|
||
// Mark resource as used.
|
||
if (resourceIsAllowed) {
|
||
map[resource.index] = 1;
|
||
}
|
||
|
||
return resourceIsAllowed && resource;
|
||
};
|
||
}
|
||
|
||
function filterEmpty(resource) {
|
||
return resource;
|
||
}
|
||
|
||
function sortResultsByKey(key) {
|
||
var desc = key.charAt(0) === '-';
|
||
|
||
if (desc) {
|
||
key = key.substring(1);
|
||
}
|
||
|
||
return function(x, y) {
|
||
return (desc ? -1 : 1) * (parseInt(x[key], 10) - parseInt(y[key], 10));
|
||
};
|
||
}
|
||
|
||
function getResourcesForClause(clause) {
|
||
switch (clause.attr) {
|
||
case 'type':
|
||
return METADATA.byType[clause.value];
|
||
case 'tag':
|
||
return METADATA.byTag[clause.value];
|
||
case 'collection':
|
||
var resources = METADATA.collections[clause.value] || {};
|
||
return getResourcesByUrlCollection(resources.resources);
|
||
case 'history':
|
||
return getResourcesByUrlCollection($.dacGetVisitedUrls(clause.value));
|
||
case 'section':
|
||
return getResourcesByUrlCollection([clause.value].sections);
|
||
default:
|
||
return [];
|
||
}
|
||
}
|
||
|
||
function getResourcesByUrlCollection(resources) {
|
||
return (resources || []).map(function(url) {
|
||
return METADATA.byUrl[url];
|
||
});
|
||
}
|
||
|
||
function removePageLevelDuplicates(resource) {
|
||
return resource && !pageMap[resource.index];
|
||
}
|
||
|
||
function doesResourceMatchClauses(resource, clauses) {
|
||
for (var i = 0; i < clauses.length; i++) {
|
||
var map;
|
||
switch (clauses[i].attr) {
|
||
case 'type':
|
||
map = METADATA.hasType[clauses[i].value];
|
||
break;
|
||
case 'tag':
|
||
map = METADATA.hasTag[clauses[i].value];
|
||
break;
|
||
}
|
||
|
||
if (!map || (!!clauses[i].negative ? map[resource.index] : !map[resource.index])) {
|
||
return clauses[i].negative;
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
return buildResourceList;
|
||
})(jQuery);
|
||
|
||
/* global METADATA, getLangPref */
|
||
|
||
window.metadata.search = (function() {
|
||
'use strict';
|
||
|
||
var currentLang = getLangPref();
|
||
|
||
function search(query) {
|
||
window.metadata.prepare();
|
||
return {
|
||
android: findDocsMatches(query, METADATA.androidReference),
|
||
docs: findDocsMatches(query, METADATA.googleReference),
|
||
resources: findResourceMatches(query)
|
||
};
|
||
}
|
||
|
||
function findDocsMatches(query, data) {
|
||
var results = [];
|
||
|
||
for (var i = 0; i < data.length; i++) {
|
||
var s = data[i];
|
||
if (query.length !== 0 && s.label.toLowerCase().indexOf(query.toLowerCase()) !== -1) {
|
||
results.push(s);
|
||
}
|
||
}
|
||
|
||
rankAutocompleteApiResults(query, results);
|
||
|
||
return results;
|
||
}
|
||
|
||
function findResourceMatches(query) {
|
||
var results = [];
|
||
|
||
// Search for matching JD docs
|
||
if (query.length >= 2) {
|
||
/* In some langs, spaces may be optional between certain non-Ascii word-glyphs. For
|
||
* those langs, only match query at word boundaries if query includes Ascii chars only.
|
||
*/
|
||
var NO_BOUNDARY_LANGUAGES = ['ja','ko','vi','zh-cn','zh-tw'];
|
||
var isAsciiOnly = /^[\u0000-\u007f]*$/.test(query);
|
||
var noBoundaries = (NO_BOUNDARY_LANGUAGES.indexOf(window.getLangPref()) !== -1);
|
||
var exprBoundary = (!isAsciiOnly && noBoundaries) ? '' : '(?:^|\\s)';
|
||
var queryRegex = new RegExp(exprBoundary + query.toLowerCase(), 'g');
|
||
|
||
var all = METADATA.all;
|
||
for (var i = 0; i < all.length; i++) {
|
||
// current search comparison, with counters for tag and title,
|
||
// used later to improve ranking
|
||
var s = all[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
|
||
if (s.keywords) {
|
||
for (var j = s.keywords.length - 1; j >= 0; j--) {
|
||
// it matches a tag
|
||
if (s.keywords[j].toLowerCase().match(queryRegex)) {
|
||
matched = true;
|
||
s.matched_tag = j + 1; // add 1 to index position
|
||
}
|
||
}
|
||
}
|
||
|
||
// Check if query matches doc title
|
||
if (s.title.toLowerCase().match(queryRegex)) {
|
||
matched = true;
|
||
s.matched_title = 1;
|
||
}
|
||
|
||
// Remember the doc if it matches either
|
||
if (matched) {
|
||
results.push(s);
|
||
}
|
||
}
|
||
|
||
// Improve the current results
|
||
results = lookupBetterResult(results);
|
||
|
||
// Rank/sort all the matched pages
|
||
rankAutocompleteDocResults(results);
|
||
|
||
return results;
|
||
}
|
||
}
|
||
|
||
// Replaces a match with another resource by url, if it exists.
|
||
function lookupReplacementByUrl(match, url) {
|
||
var replacement = METADATA.byUrl[url];
|
||
|
||
// Replacement resource does not exists.
|
||
if (!replacement) { return; }
|
||
|
||
replacement.matched_title = Math.max(replacement.matched_title, match.matched_title);
|
||
replacement.matched_tag = Math.max(replacement.matched_tag, match.matched_tag);
|
||
|
||
return replacement;
|
||
}
|
||
|
||
// Find the localized version of a page if it exists.
|
||
function lookupLocalizedVersion(match) {
|
||
return METADATA.byUrl[match.baseUrl] || METADATA.byUrl[match.url];
|
||
}
|
||
|
||
// Find the main page for a tutorial when matching a subpage.
|
||
function lookupTutorialIndex(match) {
|
||
// Guard for non index tutorial pages.
|
||
if (match.type !== 'training' || match.url.indexOf('index.html') >= 0) { return; }
|
||
|
||
var indexUrl = match.url.replace(/[^\/]+$/, 'index.html');
|
||
return lookupReplacementByUrl(match, indexUrl);
|
||
}
|
||
|
||
// Find related results which are a better match for the user.
|
||
function lookupBetterResult(matches) {
|
||
var newMatches = [];
|
||
|
||
matches = matches.filter(function(match) {
|
||
var newMatch = match;
|
||
newMatch = lookupTutorialIndex(newMatch) || newMatch;
|
||
newMatch = lookupLocalizedVersion(newMatch) || newMatch;
|
||
|
||
if (newMatch !== match) {
|
||
newMatches.push(newMatch);
|
||
}
|
||
|
||
return newMatch === match;
|
||
});
|
||
|
||
return toUnique(newMatches.concat(matches));
|
||
}
|
||
|
||
/* Order the jd doc result list based on match quality */
|
||
function rankAutocompleteDocResults(matches) {
|
||
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;
|
||
}
|
||
|
||
if (match.lang === currentLang) {
|
||
score *= 5;
|
||
}
|
||
|
||
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.title < b.title) ? -1 : 1;
|
||
}
|
||
|
||
return n;
|
||
});
|
||
}
|
||
|
||
/* Order the result list based on match quality */
|
||
function rankAutocompleteApiResults(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;
|
||
var partsAfter;
|
||
t = _lastSearch(labelLower, partExactAlnumRE);
|
||
if (t >= 0) {
|
||
// exact part match
|
||
partsAfter = _countChar(labelLower.substr(t + 1), '.');
|
||
score *= 200 / (partsAfter + 1);
|
||
} else {
|
||
t = _lastSearch(labelLower, partPrefixAlnumRE);
|
||
if (t >= 0) {
|
||
// part prefix match
|
||
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;
|
||
});
|
||
}
|
||
|
||
// Destructive but fast toUnique.
|
||
// http://stackoverflow.com/a/25082874
|
||
function toUnique(array) {
|
||
var c;
|
||
var b = array.length || 1;
|
||
|
||
while (c = --b) {
|
||
while (c--) {
|
||
if (array[b] === array[c]) {
|
||
array.splice(c, 1);
|
||
}
|
||
}
|
||
}
|
||
return array;
|
||
}
|
||
|
||
return search;
|
||
})();
|
||
|
||
(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: '.dac-header',
|
||
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();
|
||
|
||
var position = this.getTargetPosition();
|
||
$(this.options.scrollContainer).animate({
|
||
scrollTop: position - this.options.offset
|
||
}, this.options);
|
||
};
|
||
|
||
ScrollButton.prototype.getTargetPosition = function() {
|
||
if (this.options.scrollContainer === ScrollButton.DEFAULTS_.scrollContainer) {
|
||
return this.target.offset().top;
|
||
}
|
||
var scrollContainer = $(this.options.scrollContainer)[0];
|
||
var currentEl = this.target[0];
|
||
var pos = 0;
|
||
while (currentEl !== scrollContainer && currentEl !== null) {
|
||
pos += currentEl.offsetTop;
|
||
currentEl = currentEl.offsetParent;
|
||
}
|
||
return pos;
|
||
};
|
||
|
||
/**
|
||
* 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);
|
||
|
||
/* global getLangPref */
|
||
(function($) {
|
||
var LANG;
|
||
|
||
function getSearchLang() {
|
||
if (!LANG) {
|
||
LANG = getLangPref();
|
||
|
||
// Fix zh-cn to be zh-CN.
|
||
LANG = LANG.replace(/-\w+/, function(m) { return m.toUpperCase(); });
|
||
}
|
||
return LANG;
|
||
}
|
||
|
||
function customSearch(query, start) {
|
||
var searchParams = {
|
||
// current cse instance:
|
||
//cx: '001482626316274216503:zu90b7s047u',
|
||
// new cse instance:
|
||
cx: '000521750095050289010:zpcpi1ea4s8',
|
||
key: 'AIzaSyCFhbGnjW06dYwvRCU8h_zjdpS4PYYbEe8',
|
||
q: query,
|
||
start: start || 1,
|
||
num: 9,
|
||
hl: getSearchLang(),
|
||
fields: 'queries,items(pagemap,link,title,htmlSnippet,formattedUrl)'
|
||
};
|
||
|
||
return $.get('https://content.googleapis.com/customsearch/v1?' + $.param(searchParams));
|
||
}
|
||
|
||
function renderResults(el, results, searchAppliance) {
|
||
var referenceResults = searchAppliance.getReferenceResults();
|
||
if (!results.items) {
|
||
el.append($('<div>').text('No results'));
|
||
return;
|
||
}
|
||
|
||
for (var i = 0; i < results.items.length; i++) {
|
||
var item = results.items[i];
|
||
var isDuplicate = false;
|
||
$(referenceResults.android).each(function(index, result) {
|
||
if (item.link.indexOf(result.link) > -1) {
|
||
isDuplicate = true;
|
||
return false;
|
||
}
|
||
});
|
||
|
||
if (!isDuplicate) {
|
||
var hasImage = item.pagemap && item.pagemap.cse_thumbnail;
|
||
var sectionMatch = item.link.match(/developer\.android\.com\/(\w*)/);
|
||
var section = (sectionMatch && sectionMatch[1]) || 'blog';
|
||
|
||
var entry = $('<div>').addClass('dac-custom-search-entry cols');
|
||
|
||
if (hasImage) {
|
||
var image = item.pagemap.cse_thumbnail[0];
|
||
entry.append($('<div>').addClass('dac-custom-search-image-wrapper')
|
||
.append($('<div>').addClass('dac-custom-search-image').css('background-image', 'url(' + image.src + ')')));
|
||
}
|
||
|
||
entry.append($('<div>').addClass('dac-custom-search-text-wrapper')
|
||
.append($('<p>').addClass('dac-custom-search-section').text(section))
|
||
.append(
|
||
$('<a>').text(item.title).attr('href', item.link).wrap('<h2>').parent().addClass('dac-custom-search-title')
|
||
)
|
||
.append($('<p>').addClass('dac-custom-search-snippet').html(item.htmlSnippet.replace(/<br>/g, '')))
|
||
.append($('<a>').addClass('dac-custom-search-link').text(item.formattedUrl).attr('href', item.link)));
|
||
|
||
el.append(entry);
|
||
}
|
||
}
|
||
|
||
if (results.queries.nextPage) {
|
||
var loadMoreButton = $('<button id="dac-custom-search-load-more">')
|
||
.addClass('dac-custom-search-load-more')
|
||
.text('Load more')
|
||
.click(function() {
|
||
loadMoreResults(el, results, searchAppliance);
|
||
});
|
||
|
||
el.append(loadMoreButton);
|
||
}
|
||
};
|
||
|
||
function loadMoreResults(el, results, searchAppliance) {
|
||
var query = results.queries.request[0].searchTerms;
|
||
var start = results.queries.nextPage[0].startIndex;
|
||
var loadMoreButton = el.find('#dac-custom-search-load-more');
|
||
|
||
loadMoreButton.text('Loading more...');
|
||
|
||
customSearch(query, start).then(function(results) {
|
||
loadMoreButton.remove();
|
||
renderResults(el, results, searchAppliance);
|
||
});
|
||
}
|
||
|
||
$.fn.customSearch = function(query, searchAppliance) {
|
||
var el = $(this);
|
||
|
||
customSearch(query).then(function(results) {
|
||
el.empty();
|
||
renderResults(el, results, searchAppliance);
|
||
});
|
||
};
|
||
})(jQuery);
|
||
|
||
/* global METADATA */
|
||
|
||
(function($) {
|
||
$.fn.dacSearchRenderHero = function(resources, query) {
|
||
var el = $(this);
|
||
el.empty();
|
||
|
||
var resource = METADATA.searchHeroCollections[query];
|
||
|
||
if (resource) {
|
||
el.dacHero(resource, true);
|
||
el.show();
|
||
|
||
return true;
|
||
} else {
|
||
el.hide();
|
||
}
|
||
};
|
||
})(jQuery);
|
||
|
||
(function($) {
|
||
$.fn.dacSearchRenderReferences = function(results, query) {
|
||
var referenceCard = $('.suggest-card.reference');
|
||
referenceCard.data('searchreferences.dac', {results: results, query: query});
|
||
renderResults(referenceCard, results, query, false);
|
||
};
|
||
|
||
var ROW_COUNT_COLLAPSED = 20;
|
||
var ROW_COUNT_EXPANDED = 40;
|
||
var ROW_COUNT_GOOGLE_COLLAPSED = 1;
|
||
var ROW_COUNT_GOOGLE_EXPANDED = 8;
|
||
|
||
function onSuggestionClick(e) {
|
||
devsite.analytics.trackAnalyticsEvent('event',
|
||
'Suggestion Click', 'clicked: ' + $(e.currentTarget).attr('href'),
|
||
'query: ' + $('#search_autocomplete').val().toLowerCase());
|
||
}
|
||
|
||
function buildLink(match) {
|
||
var link = $('<a>').attr('href', window.toRoot + match.link);
|
||
|
||
var label = match.label;
|
||
var classNameStart = label.match(/[A-Z]/) ? label.search(/[A-Z]/) : label.lastIndexOf('.') + 1;
|
||
var newLink = '<span class="namespace">' +
|
||
label.substr(0, classNameStart) +
|
||
'</span>' +
|
||
label.substr(classNameStart, label.length);
|
||
|
||
link.html(newLink);
|
||
return link;
|
||
}
|
||
|
||
function buildSuggestion(match, query) {
|
||
var li = $('<li>').addClass('dac-search-results-reference-entry');
|
||
|
||
var link = buildLink(match);
|
||
link.highlightMatches(query);
|
||
li.append(link);
|
||
return li[0];
|
||
}
|
||
|
||
function buildResults(results, query) {
|
||
return results.map(function(match) {
|
||
return buildSuggestion(match, query);
|
||
});
|
||
}
|
||
|
||
function renderAndroidResults(list, gMatches, query) {
|
||
list.empty();
|
||
|
||
var header = $('<li class="dac-search-results-reference-header">android APIs</li>');
|
||
list.append(header);
|
||
|
||
if (gMatches.length > 0) {
|
||
list.removeClass('no-results');
|
||
|
||
var resources = buildResults(gMatches, query);
|
||
list.append(resources);
|
||
return true;
|
||
} else {
|
||
list.append('<li class="dac-search-results-reference-entry-empty">No results</li>');
|
||
}
|
||
}
|
||
|
||
function renderGoogleDocsResults(list, gGoogleMatches, query) {
|
||
list = $('.suggest-card.reference ul');
|
||
|
||
if (gGoogleMatches.length > 0) {
|
||
list.append('<li class="dac-search-results-reference-header">in Google Services</li>');
|
||
|
||
var resources = buildResults(gGoogleMatches, query);
|
||
list.append(resources);
|
||
|
||
return true;
|
||
}
|
||
}
|
||
|
||
function renderResults(referenceCard, results, query, expanded) {
|
||
var list = referenceCard.find('ul');
|
||
list.toggleClass('is-expanded', !!expanded);
|
||
|
||
// Figure out how many results we can show in our fixed size box.
|
||
var total = expanded ? ROW_COUNT_EXPANDED : ROW_COUNT_COLLAPSED;
|
||
var googleCount = expanded ? ROW_COUNT_GOOGLE_EXPANDED : ROW_COUNT_GOOGLE_COLLAPSED;
|
||
googleCount = Math.max(googleCount, total - results.android.length);
|
||
googleCount = Math.min(googleCount, results.docs.length);
|
||
|
||
if (googleCount > 0) {
|
||
// If there are google results, reserve space for its header.
|
||
googleCount++;
|
||
}
|
||
|
||
var androidCount = Math.max(0, total - googleCount);
|
||
if (androidCount === 0) {
|
||
// Reserve space for "No reference results"
|
||
googleCount--;
|
||
}
|
||
|
||
renderAndroidResults(list, results.android.slice(0, androidCount), query);
|
||
renderGoogleDocsResults(list, results.docs.slice(0, googleCount - 1), query);
|
||
|
||
var totalResults = results.android.length + results.docs.length;
|
||
if (totalResults === 0) {
|
||
list.addClass('no-results');
|
||
}
|
||
|
||
// Tweak see more logic to account for references.
|
||
var hasMore = totalResults > ROW_COUNT_COLLAPSED && !util.matchesMedia('mobile');
|
||
if (hasMore) {
|
||
// We can't actually show all matches, only as many as the expanded list
|
||
// will fit, so we actually lie if the total results count is more
|
||
var moreCount = Math.min(totalResults, ROW_COUNT_EXPANDED + ROW_COUNT_GOOGLE_EXPANDED);
|
||
var $moreLink = $('<li class="dac-search-results-reference-entry-empty " data-toggle="show-more">see more matches</li>');
|
||
list.append($moreLink.on('click', onToggleMore));
|
||
}
|
||
var searchEl = $('#search-resources');
|
||
searchEl.toggleClass('dac-has-more', searchEl.hasClass('dac-has-more') || (hasMore && !expanded));
|
||
searchEl.toggleClass('dac-has-less', searchEl.hasClass('dac-has-less') || (hasMore && expanded));
|
||
}
|
||
|
||
function onToggleMore(e) {
|
||
var link = $(e.currentTarget);
|
||
var referenceCard = $('.suggest-card.reference');
|
||
var data = referenceCard.data('searchreferences.dac');
|
||
|
||
if (util.matchesMedia('mobile')) { return; }
|
||
|
||
renderResults(referenceCard, data.results, data.query, link.data('toggle') === 'show-more');
|
||
}
|
||
|
||
$(document).on('click', '.dac-search-results-resources [data-toggle="show-more"]', onToggleMore);
|
||
$(document).on('click', '.dac-search-results-resources [data-toggle="show-less"]', onToggleMore);
|
||
$(document).on('click', '.suggest-card.reference a', onSuggestionClick);
|
||
})(jQuery);
|
||
|
||
(function($) {
|
||
function highlightPage(query, page) {
|
||
page.find('.title').highlightMatches(query);
|
||
}
|
||
|
||
$.fn.dacSearchRenderResources = function(gDocsMatches, query) {
|
||
this.resourceWidget(gDocsMatches, {
|
||
itemsPerPage: 18,
|
||
initialResults: 6,
|
||
cardSizes: ['6x2'],
|
||
onRenderPage: highlightPage.bind(null, query)
|
||
});
|
||
|
||
return this;
|
||
};
|
||
})(jQuery);
|
||
|
||
/*global metadata */
|
||
|
||
(function($, metadata) {
|
||
'use strict';
|
||
|
||
function Search() {
|
||
this.body = $('body');
|
||
this.lastQuery = null;
|
||
this.searchResults = $('#search-results');
|
||
this.searchClose = $('[data-search-close]');
|
||
this.searchClear = $('[data-search-clear]');
|
||
this.searchInput = $('#search_autocomplete');
|
||
this.searchResultsContent = $('#dac-search-results-content');
|
||
this.searchResultsFor = $('#search-results-for');
|
||
this.searchResultsHistory = $('#dac-search-results-history');
|
||
this.searchResultsResources = $('#search-resources');
|
||
this.searchResultsHero = $('#dac-search-results-hero');
|
||
this.searchResultsReference = $('#dac-search-results-reference');
|
||
this.searchHeader = $('[data-search]').data('search-input.dac');
|
||
this.pageNav = $('a[name=navigation]');
|
||
this.currQueryReferenceResults = {};
|
||
this.isOpen = false;
|
||
}
|
||
|
||
Search.prototype.init = function() {
|
||
this.searchHistory = window.dacStore('search-history');
|
||
|
||
this.searchInput.focus(this.onSearchChanged.bind(this));
|
||
this.searchInput.keypress(this.handleKeyboardShortcut.bind(this));
|
||
this.pageNav.keyup(this.handleTabbedToNav.bind(this));
|
||
this.searchResults.keyup(this.handleKeyboardShortcut.bind(this));
|
||
this.searchInput.on('input', this.onSearchChanged.bind(this));
|
||
this.searchClear.click(this.clear.bind(this));
|
||
this.searchClose.click(this.close.bind(this));
|
||
|
||
this.customSearch = $.fn.debounce(function(query) {
|
||
$('#dac-custom-search-results').customSearch(query, this);
|
||
}.bind(this), 1000);
|
||
// Start search shortcut (/)
|
||
$('body').keyup(function(event) {
|
||
if (event.which === 191 && $(event.target).is(':not(:input)')) {
|
||
this.searchInput.focus();
|
||
}
|
||
}.bind(this));
|
||
|
||
$(window).on('popstate', this.onPopState.bind(this));
|
||
$(window).hashchange(this.onHashChange.bind(this));
|
||
this.onHashChange();
|
||
};
|
||
|
||
Search.prototype.checkRedirectToIndex = function() {
|
||
var query = this.getUrlQuery();
|
||
var target = window.getLangTarget();
|
||
var prefix = (target !== 'en') ? '/intl/' + target : '';
|
||
var pathname = location.pathname.slice(prefix.length);
|
||
if (query != null && pathname !== '/index.html') {
|
||
location.href = prefix + '/index.html' + location.hash;
|
||
return true;
|
||
}
|
||
};
|
||
|
||
Search.prototype.handleKeyboardShortcut = function(event) {
|
||
// Close (esc)
|
||
if (event.which === 27) {
|
||
this.searchClose.trigger('click');
|
||
event.preventDefault();
|
||
}
|
||
|
||
// Previous result (up arrow)
|
||
if (event.which === 38) {
|
||
this.previousResult();
|
||
event.preventDefault();
|
||
}
|
||
|
||
// Next result (down arrow)
|
||
if (event.which === 40) {
|
||
this.nextResult();
|
||
event.preventDefault();
|
||
}
|
||
|
||
// Navigate to result (enter)
|
||
if (event.which === 13) {
|
||
this.navigateToResult();
|
||
event.preventDefault();
|
||
}
|
||
};
|
||
|
||
Search.prototype.handleTabbedToNav = function(event) {
|
||
if (this.isOpen) {
|
||
this.searchClose.trigger('click');
|
||
}
|
||
}
|
||
|
||
Search.prototype.goToResult = function(relativeIndex) {
|
||
var links = this.searchResults.find('a').filter(':visible');
|
||
var selectedLink = this.searchResults.find('.dac-selected');
|
||
|
||
if (selectedLink.length) {
|
||
var found = $.inArray(selectedLink[0], links);
|
||
|
||
selectedLink.removeClass('dac-selected');
|
||
links.eq(found + relativeIndex).addClass('dac-selected');
|
||
return true;
|
||
} else {
|
||
if (relativeIndex > 0) {
|
||
links.first().addClass('dac-selected');
|
||
}
|
||
}
|
||
};
|
||
|
||
Search.prototype.previousResult = function() {
|
||
this.goToResult(-1);
|
||
};
|
||
|
||
Search.prototype.nextResult = function() {
|
||
this.goToResult(1);
|
||
};
|
||
|
||
Search.prototype.navigateToResult = function() {
|
||
var query = this.getQuery();
|
||
var selectedLink = this.searchResults.find('.dac-selected');
|
||
|
||
if (selectedLink.length) {
|
||
selectedLink[0].click();
|
||
} else {
|
||
this.searchHistory.push(query);
|
||
this.addQueryToUrl(query);
|
||
|
||
var isMobileOrTablet = typeof window.orientation !== 'undefined';
|
||
|
||
if (isMobileOrTablet) {
|
||
this.searchInput.blur();
|
||
}
|
||
}
|
||
};
|
||
|
||
Search.prototype.onHashChange = function() {
|
||
var query = this.getUrlQuery();
|
||
if (query != null && query !== this.getQuery()) {
|
||
this.searchInput.val(query);
|
||
this.onSearchChanged();
|
||
}
|
||
};
|
||
|
||
Search.prototype.clear = function() {
|
||
this.searchInput.val('');
|
||
window.location.hash = '';
|
||
this.onSearchChanged();
|
||
this.searchInput.focus();
|
||
};
|
||
|
||
Search.prototype.close = function() {
|
||
this.removeQueryFromUrl();
|
||
this.searchInput.blur();
|
||
this.hideOverlay();
|
||
this.pageNav.focus();
|
||
this.isOpen = false;
|
||
};
|
||
|
||
Search.prototype.getUrlQuery = function() {
|
||
var queryMatch = location.hash.match(/q=(.*)&?/);
|
||
return queryMatch && queryMatch[1] && decodeURI(queryMatch[1]);
|
||
};
|
||
|
||
Search.prototype.getQuery = function() {
|
||
return this.searchInput.val().replace(/(^ +)|( +$)/g, '');
|
||
};
|
||
|
||
Search.prototype.getReferenceResults = function() {
|
||
return this.currQueryReferenceResults;
|
||
};
|
||
|
||
Search.prototype.onSearchChanged = function() {
|
||
var query = this.getQuery();
|
||
|
||
this.showOverlay();
|
||
this.render(query);
|
||
};
|
||
|
||
Search.prototype.render = function(query) {
|
||
if (this.lastQuery === query) { return; }
|
||
|
||
if (query.length < 2) {
|
||
query = '';
|
||
}
|
||
|
||
this.lastQuery = query;
|
||
this.searchResultsFor.text(query);
|
||
|
||
// CSE results lag behind the metadata/reference results. We need to empty
|
||
// the CSE results and add 'Loading' text so user's aren't looking at two
|
||
// different sets of search results at one time.
|
||
var $loadingEl =
|
||
$('<div class="loadingCustomSearchResults">Loading Results...</div>');
|
||
$('#dac-custom-search-results').empty().prepend($loadingEl);
|
||
|
||
this.customSearch(query);
|
||
var metadataResults = metadata.search(query);
|
||
this.searchResultsResources.dacSearchRenderResources(metadataResults.resources, query);
|
||
this.searchResultsReference.dacSearchRenderReferences(metadataResults, query);
|
||
this.currQueryReferenceResults = metadataResults;
|
||
var hasHero = this.searchResultsHero.dacSearchRenderHero(metadataResults.resources, query);
|
||
var hasQuery = !!query;
|
||
|
||
this.searchResultsReference.toggle(!hasHero);
|
||
this.searchResultsContent.toggle(hasQuery);
|
||
this.searchResultsHistory.toggle(!hasQuery);
|
||
this.addQueryToUrl(query);
|
||
this.pushState();
|
||
};
|
||
|
||
Search.prototype.addQueryToUrl = function(query) {
|
||
var hash = 'q=' + encodeURI(query);
|
||
|
||
if (query) {
|
||
if (window.history.replaceState) {
|
||
window.history.replaceState(null, '', '#' + hash);
|
||
} else {
|
||
window.location.hash = hash;
|
||
}
|
||
}
|
||
};
|
||
|
||
Search.prototype.onPopState = function() {
|
||
if (!this.getUrlQuery()) {
|
||
this.hideOverlay();
|
||
this.searchHeader.unsetActiveState();
|
||
}
|
||
};
|
||
|
||
Search.prototype.removeQueryFromUrl = function() {
|
||
window.location.hash = '';
|
||
};
|
||
|
||
Search.prototype.pushState = function() {
|
||
if (window.history.pushState && !this.lastQuery.length) {
|
||
window.history.pushState(null, '');
|
||
}
|
||
};
|
||
|
||
Search.prototype.showOverlay = function() {
|
||
this.isOpen = true;
|
||
this.body.addClass('dac-modal-open dac-search-open');
|
||
};
|
||
|
||
Search.prototype.hideOverlay = function() {
|
||
this.body.removeClass('dac-modal-open dac-search-open');
|
||
};
|
||
|
||
$(document).on('ready.aranja', function() {
|
||
var search = new Search();
|
||
search.init();
|
||
});
|
||
})(jQuery, metadata);
|
||
|
||
window.dacStore = (function(window) {
|
||
/**
|
||
* Creates a new persistent store.
|
||
* If localStorage is unavailable, the items are stored in memory.
|
||
*
|
||
* @constructor
|
||
* @param {string} name The name of the store
|
||
* @param {number} maxSize The maximum number of items the store can hold.
|
||
*/
|
||
var Store = function(name, maxSize) {
|
||
var content = [];
|
||
|
||
var hasLocalStorage = !!window.localStorage;
|
||
|
||
if (hasLocalStorage) {
|
||
try {
|
||
content = JSON.parse(window.localStorage.getItem(name) || []);
|
||
} catch (e) {
|
||
// Store contains invalid data
|
||
window.localStorage.removeItem(name);
|
||
}
|
||
}
|
||
|
||
function push(item) {
|
||
if (content[0] === item) {
|
||
return;
|
||
}
|
||
|
||
content.unshift(item);
|
||
|
||
if (maxSize) {
|
||
content.splice(maxSize, content.length);
|
||
}
|
||
|
||
if (hasLocalStorage) {
|
||
window.localStorage.setItem(name, JSON.stringify(content));
|
||
}
|
||
}
|
||
|
||
function all() {
|
||
// Return a copy
|
||
return content.slice();
|
||
}
|
||
|
||
return {
|
||
push: push,
|
||
all: all
|
||
};
|
||
};
|
||
|
||
var stores = {
|
||
'search-history': new Store('search-history', 3)
|
||
};
|
||
|
||
/**
|
||
* Get a named persistent store.
|
||
* @param {string} name
|
||
* @return {Store}
|
||
*/
|
||
return function getStore(name) {
|
||
return stores[name];
|
||
};
|
||
})(window);
|
||
|
||
(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.options.dynamic = this.options.dynamic === 'true';
|
||
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));
|
||
this.el.find(this.options.swapButton).on('click keypress', function(e) {
|
||
if (e.type == 'keypress' && e.which == 13 || e.type == 'click') {
|
||
this.swap();
|
||
}
|
||
}.bind(this));
|
||
}
|
||
|
||
/**
|
||
* SwapContent's default settings.
|
||
* @type {{activeClass: string, container: string, transitionSpeed: number}}
|
||
* @private
|
||
*/
|
||
SwapContent.DEFAULTS_ = {
|
||
activeClass: 'dac-active',
|
||
container: '[data-swap-container]',
|
||
dynamic: 'true',
|
||
swapButton: '[data-swap-button]',
|
||
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() {
|
||
this.containers.each(function(index, container) {
|
||
container = $(container);
|
||
|
||
if (!this.options.dynamic) {
|
||
container.children().toggleClass(this.options.activeClass);
|
||
this.complete.bind(this);
|
||
$('.' + this.options.activeClass).focus();
|
||
return;
|
||
}
|
||
|
||
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);
|
||
|
||
/* Tabs */
|
||
(function($) {
|
||
'use strict';
|
||
|
||
/**
|
||
* @param {HTMLElement} el - The DOM element.
|
||
* @param {Object} options
|
||
* @constructor
|
||
*/
|
||
function Tabs(el, options) {
|
||
this.el = $(el);
|
||
this.options = $.extend({}, Tabs.DEFAULTS_, options);
|
||
this.init();
|
||
}
|
||
|
||
Tabs.DEFAULTS_ = {
|
||
activeClass: 'dac-active',
|
||
viewDataAttr: 'tab-view',
|
||
itemDataAttr: 'tab-item'
|
||
};
|
||
|
||
Tabs.prototype.init = function() {
|
||
var itemDataAttribute = '[data-' + this.options.itemDataAttr + ']';
|
||
this.tabEl_ = this.el.find(itemDataAttribute);
|
||
this.tabViewEl_ = this.el.find('[data-' + this.options.viewDataAttr + ']');
|
||
this.el.on('click.dac-tabs', itemDataAttribute, this.changeTabs.bind(this));
|
||
};
|
||
|
||
Tabs.prototype.changeTabs = function(event) {
|
||
var current = $(event.currentTarget);
|
||
var index = current.index();
|
||
|
||
if (current.hasClass(this.options.activeClass)) {
|
||
current.add(this.tabViewEl_.eq(index)).removeClass(this.options.activeClass);
|
||
} else {
|
||
this.tabEl_.add(this.tabViewEl_).removeClass(this.options.activeClass);
|
||
current.add(this.tabViewEl_.eq(index)).addClass(this.options.activeClass);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* jQuery plugin
|
||
*/
|
||
$.fn.dacTabs = function() {
|
||
return this.each(function() {
|
||
var el = $(this);
|
||
new Tabs(el, el.data());
|
||
});
|
||
};
|
||
|
||
/**
|
||
* Data Attribute API
|
||
*/
|
||
$(function() {
|
||
$('[data-tabs]').dacTabs();
|
||
});
|
||
})(jQuery);
|
||
|
||
/* Toast Component */
|
||
(function($) {
|
||
'use strict';
|
||
/**
|
||
* @constant
|
||
* @type {String}
|
||
*/
|
||
var LOCAL_STORAGE_KEY = 'toast-closed-index';
|
||
|
||
/**
|
||
* Dictionary from local storage.
|
||
*/
|
||
var toastDictionary = localStorage.getItem(LOCAL_STORAGE_KEY);
|
||
toastDictionary = toastDictionary ? JSON.parse(toastDictionary) : {};
|
||
|
||
/**
|
||
* Variable used for caching the body.
|
||
*/
|
||
var bodyCached;
|
||
|
||
/**
|
||
* @param {HTMLElement} el - The DOM element.
|
||
* @param {Object} options
|
||
* @constructor
|
||
*/
|
||
function Toast(el, options) {
|
||
this.el = $(el);
|
||
this.options = $.extend({}, Toast.DEFAULTS_, options);
|
||
this.init();
|
||
}
|
||
|
||
Toast.DEFAULTS_ = {
|
||
closeBtnClass: 'dac-toast-close-btn',
|
||
closeDuration: 200,
|
||
visibleClass: 'dac-visible',
|
||
wrapClass: 'dac-toast-wrap'
|
||
};
|
||
|
||
/**
|
||
* Generate a close button.
|
||
* @returns {*|HTMLElement}
|
||
*/
|
||
Toast.prototype.closeBtn = function() {
|
||
this.closeBtnEl = this.closeBtnEl || $('<button class="' + this.options.closeBtnClass + '">' +
|
||
'<span class="dac-button dac-raised dac-primary">OK</span>' +
|
||
'</button>');
|
||
return this.closeBtnEl;
|
||
};
|
||
|
||
/**
|
||
* Initialize a new toast element
|
||
*/
|
||
Toast.prototype.init = function() {
|
||
this.hash = this.el.text().replace(/[\s\n\t]/g, '').split('').slice(0, 128).join('');
|
||
|
||
if (toastDictionary[this.hash]) {
|
||
return;
|
||
}
|
||
|
||
this.closeBtn().on('click', this.onClickHandler.bind(this));
|
||
this.el.find('.' + this.options.wrapClass).append(this.closeBtn());
|
||
this.el.addClass(this.options.visibleClass);
|
||
this.dynamicPadding(this.el.outerHeight());
|
||
};
|
||
|
||
/**
|
||
* Add padding to make sure all page is visible.
|
||
*/
|
||
Toast.prototype.dynamicPadding = function(val) {
|
||
var currentPadding = parseInt(bodyCached.css('padding-bottom') || 0);
|
||
bodyCached.css('padding-bottom', val + currentPadding);
|
||
};
|
||
|
||
/**
|
||
* Remove a toast from the DOM
|
||
*/
|
||
Toast.prototype.remove = function() {
|
||
this.dynamicPadding(-this.el.outerHeight());
|
||
this.el.remove();
|
||
};
|
||
|
||
/**
|
||
* Handle removal of the toast.
|
||
*/
|
||
Toast.prototype.onClickHandler = function() {
|
||
// Only fadeout toasts from top of stack. Others are removed immediately.
|
||
var duration = this.el.index() === 0 ? this.options.closeDuration : 0;
|
||
this.el.fadeOut(duration, this.remove.bind(this));
|
||
|
||
// Save closed state.
|
||
toastDictionary[this.hash] = 1;
|
||
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(toastDictionary));
|
||
};
|
||
|
||
/**
|
||
* jQuery plugin
|
||
* @param {object} options - Override default options.
|
||
*/
|
||
$.fn.dacToast = function() {
|
||
return this.each(function() {
|
||
var el = $(this);
|
||
new Toast(el, el.data());
|
||
});
|
||
};
|
||
|
||
/**
|
||
* Data Attribute API
|
||
*/
|
||
$(function() {
|
||
bodyCached = $('#body-content');
|
||
$('[data-toast]').dacToast();
|
||
});
|
||
})(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({
|
||
transitionDuration: '0s',
|
||
maxHeight: contentHeight + 'px'
|
||
})
|
||
.resolveStyles()
|
||
.css('transitionDuration', '');
|
||
}
|
||
|
||
// Transition to new state
|
||
$el.css('maxHeight', targetHeight);
|
||
|
||
// Reset maxHeight to css value after transition.
|
||
setTimeout(function() {
|
||
$el.css({
|
||
transitionDuration: '0s',
|
||
maxHeight: ''
|
||
})
|
||
.resolveStyles()
|
||
.css('transitionDuration', '');
|
||
}, 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);
|
||
|
||
(function(window) {
|
||
/**
|
||
* Media query breakpoints. Should match CSS.
|
||
*/
|
||
var BREAKPOINTS = {
|
||
mobile: [0, 719],
|
||
tablet: [720, 959],
|
||
desktop: [960, 9999]
|
||
};
|
||
|
||
/**
|
||
* Fisher-Yates Shuffle (Knuth shuffle).
|
||
* @param {Array} input
|
||
* @returns {Array} shuffled array.
|
||
*/
|
||
function shuffle(input) {
|
||
for (var i = input.length; i >= 0; i--) {
|
||
var randomIndex = Math.floor(Math.random() * (i + 1));
|
||
var randomItem = input[randomIndex];
|
||
input[randomIndex] = input[i];
|
||
input[i] = randomItem;
|
||
}
|
||
|
||
return input;
|
||
}
|
||
|
||
/**
|
||
* Matches media breakpoints like in CSS.
|
||
* @param {string} form of either mobile, tablet or desktop.
|
||
*/
|
||
function matchesMedia(form) {
|
||
var breakpoint = BREAKPOINTS[form];
|
||
return window.innerWidth >= breakpoint[0] && window.innerWidth <= breakpoint[1];
|
||
}
|
||
|
||
window.util = {
|
||
shuffle: shuffle,
|
||
matchesMedia: matchesMedia
|
||
};
|
||
})(window);
|
||
|
||
(function($, window) {
|
||
'use strict';
|
||
|
||
var YouTubePlayer = (function() {
|
||
var player;
|
||
|
||
function VideoPlayer() {
|
||
this.mPlayerPaused = false;
|
||
this.doneSetup = false;
|
||
}
|
||
|
||
VideoPlayer.prototype.setup = function() {
|
||
// loads the IFrame Player API code asynchronously.
|
||
$.getScript('https://www.youtube.com/iframe_api');
|
||
|
||
// Add the shadowbox HTML to the body
|
||
$('body').prepend(
|
||
'<div id="video-player" class="Video">' +
|
||
'<div id="video-overlay" class="Video-overlay" />' +
|
||
'<div class="Video-container">' +
|
||
'<div class="Video-frame">' +
|
||
'<span class="Video-loading">Loading…</span>' +
|
||
'<div id="youTubePlayer"></div>' +
|
||
'</div>' +
|
||
'<div class="Video-controls">' +
|
||
'<button id="picture-in-picture" class="Video-button Video-button--picture-in-picture">' +
|
||
'<button id="close-video" class="Video-button Video-button--close" />' +
|
||
'</div>' +
|
||
'</div>' +
|
||
'</div>');
|
||
|
||
this.videoPlayer = $('#video-player');
|
||
|
||
var pictureInPictureButton = this.videoPlayer.find('#picture-in-picture');
|
||
pictureInPictureButton.on('click.aranja', this.toggleMinimizeVideo.bind(this));
|
||
|
||
var videoOverlay = this.videoPlayer.find('#video-overlay');
|
||
var closeButton = this.videoPlayer.find('#close-video');
|
||
var closeVideo = this.closeVideo.bind(this);
|
||
videoOverlay.on('click.aranja', closeVideo);
|
||
closeButton.on('click.aranja', closeVideo);
|
||
|
||
this.doneSetup = true;
|
||
};
|
||
|
||
VideoPlayer.prototype.startYouTubePlayer = function(videoId) {
|
||
this.videoPlayer.show();
|
||
|
||
if (!this.isLoaded) {
|
||
this.queueVideo = videoId;
|
||
return;
|
||
}
|
||
|
||
this.mPlayerPaused = false;
|
||
// check if we've already created this player
|
||
if (!this.youTubePlayer) {
|
||
// 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;
|
||
|
||
this.youTubePlayer = new YT.Player('youTubePlayer', {
|
||
height: 720,
|
||
width: 1280,
|
||
videoId: idAndHash[0],
|
||
// jscs:disable requireCamelCaseOrUpperCaseIdentifiers
|
||
playerVars: {start: startTime, hl: lang, cc_load_policy: captionsOn},
|
||
// jscs:enable
|
||
events: {
|
||
'onReady': this.onPlayerReady.bind(this),
|
||
'onStateChange': this.onPlayerStateChange.bind(this)
|
||
}
|
||
});
|
||
} else {
|
||
// if a video different from the one already playing was requested, cue it up
|
||
if (videoId !== this.getVideoId()) {
|
||
this.youTubePlayer.cueVideoById(videoId);
|
||
}
|
||
this.youTubePlayer.playVideo();
|
||
}
|
||
};
|
||
|
||
VideoPlayer.prototype.onPlayerReady = function(event) {
|
||
if (!isMobile) {
|
||
event.target.playVideo();
|
||
this.mPlayerPaused = false;
|
||
}
|
||
};
|
||
|
||
VideoPlayer.prototype.toggleMinimizeVideo = function(event) {
|
||
event.stopPropagation();
|
||
this.videoPlayer.toggleClass('Video--picture-in-picture');
|
||
};
|
||
|
||
VideoPlayer.prototype.closeVideo = function() {
|
||
try {
|
||
this.youTubePlayer.pauseVideo();
|
||
} catch (e) {
|
||
}
|
||
this.videoPlayer.fadeOut(200, function() {
|
||
this.videoPlayer.removeClass('Video--picture-in-picture');
|
||
}.bind(this));
|
||
};
|
||
|
||
VideoPlayer.prototype.getVideoId = function() {
|
||
// jscs:disable requireCamelCaseOrUpperCaseIdentifiers
|
||
return this.youTubePlayer && this.youTubePlayer.getVideoData().video_id;
|
||
// jscs:enable
|
||
};
|
||
|
||
/* Track youtube playback for analytics */
|
||
VideoPlayer.prototype.onPlayerStateChange = function(event) {
|
||
var videoId = this.getVideoId();
|
||
var currentTime = this.youTubePlayer && this.youTubePlayer.getCurrentTime();
|
||
|
||
// Video starts, send the video ID
|
||
if (event.data === YT.PlayerState.PLAYING) {
|
||
if (this.mPlayerPaused) {
|
||
devsite.analytics.trackAnalyticsEvent('event',
|
||
'Videos', 'Resume', videoId);
|
||
} else {
|
||
// track the start playing event so we know from which page the video was selected
|
||
devsite.analytics.trackAnalyticsEvent('event',
|
||
'Videos', 'Start: ' + videoId, 'on: ' + document.location.href);
|
||
}
|
||
this.mPlayerPaused = false;
|
||
}
|
||
|
||
// Video paused, send video ID and video elapsed time
|
||
if (event.data === YT.PlayerState.PAUSED) {
|
||
devsite.analytics.trackAnalyticsEvent('event',
|
||
'Videos', 'Paused: ' + videoId, 'on: ' + currentTime);
|
||
this.mPlayerPaused = true;
|
||
}
|
||
|
||
// Video finished, send video ID and video elapsed time
|
||
if (event.data === YT.PlayerState.ENDED) {
|
||
devsite.analytics.trackAnalyticsEvent('event',
|
||
'Videos', 'Finished: ' + videoId, 'on: ' + currentTime);
|
||
this.mPlayerPaused = true;
|
||
}
|
||
};
|
||
|
||
return {
|
||
getPlayer: function() {
|
||
if (!player) {
|
||
player = new VideoPlayer();
|
||
}
|
||
|
||
return player;
|
||
}
|
||
};
|
||
})();
|
||
|
||
var videoPlayer = YouTubePlayer.getPlayer();
|
||
|
||
window.onYouTubeIframeAPIReady = function() {
|
||
videoPlayer.isLoaded = true;
|
||
|
||
if (videoPlayer.queueVideo) {
|
||
videoPlayer.startYouTubePlayer(videoPlayer.queueVideo);
|
||
}
|
||
};
|
||
|
||
function wrapLinkInPlayer(e) {
|
||
e.preventDefault();
|
||
|
||
if (!videoPlayer.doneSetup) {
|
||
videoPlayer.setup();
|
||
}
|
||
|
||
var videoIdMatches = $(e.currentTarget).attr('href').match(/(?:youtu.be\/|v=)([^&]*)/);
|
||
var videoId = videoIdMatches && videoIdMatches[1];
|
||
|
||
if (videoId) {
|
||
videoPlayer.startYouTubePlayer(videoId);
|
||
}
|
||
}
|
||
|
||
$(document).on('click.video', 'a[href*="youtube.com/watch"], a[href*="youtu.be"]', wrapLinkInPlayer);
|
||
})(jQuery, window);
|
||
|
||
/**
|
||
* Wide table
|
||
*
|
||
* Wraps tables in a scrollable area so you can read them on mobile.
|
||
*/
|
||
(function($) {
|
||
function initWideTable() {
|
||
$('table.jd-sumtable').each(function(i, table) {
|
||
$(table).wrap('<div class="dac-expand wide-table">');
|
||
});
|
||
}
|
||
|
||
$(function() {
|
||
initWideTable();
|
||
});
|
||
})(jQuery);
|
||
|
||
/** Utilities */
|
||
|
||
/* 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,"<")
|
||
.replace(/>/g,">");
|
||
};
|
||
|
||
function getQueryVariable(variable) {
|
||
var query = window.location.search.substring(1);
|
||
var vars = query.split("&");
|
||
for (var i=0;i<vars.length;i++) {
|
||
var pair = vars[i].split("=");
|
||
if(pair[0] == variable){return pair[1];}
|
||
}
|
||
return(false);
|
||
};
|