blob: 0d79416600b94365222b3684db682dea5a7e7d8f [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview
* 'settings-animated-pages' is a container for a page and animated subpages.
* It provides a set of common behaviors and animations.
*
* Example:
*
* <settings-animated-pages section="privacy">
* <!-- Insert your section controls here -->
* </settings-animated-pages>
*/
Polymer({
is: 'settings-animated-pages',
behaviors: [settings.RouteObserverBehavior],
properties: {
/**
* Routes with this section activate this element. For instance, if this
* property is 'search', and currentRoute.section is also set to 'search',
* this element will display the subpage in currentRoute.subpage.
*
* The section name must match the name specified in route.js.
*/
section: String,
/**
* A Map specifying which element should be focused when exiting a subpage.
* The key of the map holds a settings.Route path, and the value holds
* either a query selector that identifies the desired element, an element
* or a function to be run when a neon-animation-finish event is handled.
* @type {?Map<string, (string|Element|Function)>}
*/
focusConfig: Object,
},
/**
* The last "previous" route reported by the router.
* @private {?settings.Route}
*/
previousRoute_: null,
/** @override */
created: function() {
// Observe the light DOM so we know when it's ready.
this.lightDomObserver_ =
Polymer.dom(this).observeNodes(this.lightDomChanged_.bind(this));
},
/**
* @param {!Event} e
* @private
*/
onIronSelect_: function(e) {
// Call initialFocus() on the selected subpage, only if:
// 1) Not a direct navigation (such that the search box stays focused), and
// 2) Not a "back" navigation, in which case the anchor element should be
// focused (further below in this function).
if (!!this.previousRoute_ && !settings.lastRouteChangeWasPopstate()) {
const subpage = this.querySelector('settings-subpage.iron-selected');
if (!!subpage) {
subpage.initialFocus();
return;
}
}
// Don't attempt to focus any anchor element, unless last navigation was a
// 'pop' (backwards) navigation.
if (!settings.lastRouteChangeWasPopstate()) {
return;
}
if (!this.focusConfig || !this.previousRoute_) {
return;
}
const subpagePaths = [];
if (settings.routes.SITE_SETTINGS_COOKIES)
subpagePaths.push(settings.routes.SITE_SETTINGS_COOKIES.path);
if (settings.routes.SITE_SETTINGS_SITE_DATA)
subpagePaths.push(settings.routes.SITE_SETTINGS_SITE_DATA.path);
if (settings.routes.SITE_SETTINGS_ALL)
subpagePaths.push(settings.routes.SITE_SETTINGS_ALL.path);
// <if expr="chromeos">
if (settings.routes.INTERNET_NETWORKS)
subpagePaths.push(settings.routes.INTERNET_NETWORKS.path);
// </if>
// Only handle iron-select events from div elements and the
// given whitelist of settings-subpage instances.
const whitelist = ['settings-subpage#site-settings', 'div[route-path]'];
whitelist.push.apply(
whitelist,
subpagePaths.map(path => `settings-subpage[route-path="${path}"]`));
const query = whitelist.join(', ');
if (!e.detail.item.matches(query))
return;
let pathConfig = this.focusConfig.get(this.previousRoute_.path);
if (pathConfig) {
let handler;
if (typeof pathConfig == 'function') {
handler = pathConfig;
} else {
handler = () => {
if (typeof pathConfig == 'string')
pathConfig = assert(this.querySelector(pathConfig));
cr.ui.focusWithoutInk(/** @type {!Element} */ (pathConfig));
};
}
handler();
}
},
/**
* Called initially once the effective children are ready.
* @private
*/
lightDomChanged_: function() {
if (this.lightDomReady_)
return;
this.lightDomReady_ = true;
Polymer.dom(this).unobserveNodes(this.lightDomObserver_);
this.runQueuedRouteChange_();
},
/**
* Calls currentRouteChanged with the deferred route change info.
* @private
*/
runQueuedRouteChange_: function() {
if (!this.queuedRouteChange_)
return;
this.async(this.currentRouteChanged.bind(
this, this.queuedRouteChange_.newRoute,
this.queuedRouteChange_.oldRoute));
},
/** @protected */
currentRouteChanged: function(newRoute, oldRoute) {
this.previousRoute_ = oldRoute;
if (newRoute.section == this.section && newRoute.isSubpage()) {
this.switchToSubpage_(newRoute, oldRoute);
} else {
this.$.animatedPages.selected = 'default';
}
},
/**
* Selects the subpage specified by |newRoute|.
* @param {!settings.Route} newRoute
* @param {!settings.Route} oldRoute
* @private
*/
switchToSubpage_: function(newRoute, oldRoute) {
// Don't manipulate the light DOM until it's ready.
if (!this.lightDomReady_) {
this.queuedRouteChange_ = this.queuedRouteChange_ || {oldRoute: oldRoute};
this.queuedRouteChange_.newRoute = newRoute;
return;
}
this.ensureSubpageInstance_();
this.$.animatedPages.selected = newRoute.path;
},
/**
* Ensures that the template enclosing the subpage is stamped.
* @private
*/
ensureSubpageInstance_: function() {
const routePath = settings.getCurrentRoute().path;
/* TODO(dpapad): Remove conditional logic once migration to Polymer 2 is
* completed. */
const domIf = this.querySelector(
(Polymer.DomIf ? 'dom-if' : 'template') +
`[route-path='${routePath}']`);
// Nothing to do if the subpage isn't wrapped in a <dom-if>
// (or <template is="dom-if" for Poylmer 1) or the template is already
// stamped.
if (!domIf || domIf.if)
return;
// Set the subpage's id for use by neon-animated-pages.
// TODO(dpapad): Remove conditional logic once migration to Polymer 2 is
// completed.
const content = Polymer.DomIf ?
Polymer.DomIf._contentForTemplate(domIf.firstElementChild) :
/** @type {{_content: DocumentFragment}} */ (domIf)._content;
const subpage = content.querySelector('settings-subpage');
subpage.setAttribute('route-path', routePath);
// Carry over the
// 1)'no-search' attribute or
// 2) 'noSearch' Polymer property
// template to the stamped instance (both cases are mapped to a 'no-search'
// attribute intentionally), such that the stamped instance will also be
// ignored by the searching algorithm.
//
// In the case were no-search is dynamically calculated use the following
// pattern:
//
// <template is="dom-if" route-path="/myPath"
// no-search="[[shouldSkipSearch_(foo, bar)">
// <settings-subpage
// no-search$="[[shouldSkipSearch_(foo, bar)">
// ...
// </settings-subpage>
// </template>
//
// Note that the dom-if should always use the property and settings-subpage
// should always use the attribute.
if (domIf.hasAttribute('no-search') || domIf.noSearch)
subpage.setAttribute('no-search', '');
// Render synchronously so neon-animated-pages can select the subpage.
domIf.if = true;
domIf.render();
},
});