blob: 19debcbf0cb4bc0dde154f41e5e7ccd6a9a1c029 [file] [log] [blame]
// Copyright 2016 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.
/**
* Javascript for Snackbar controls, served from chrome://bluetooth-internals/.
*/
cr.define('snackbar', function() {
/** @typedef {{
* message: string,
* type: string,
* actionText: (string|undefined),
* action: (function()|undefined)
* }}
*/
var SnackbarOptions;
/** @const {number} */ var SHOW_DURATION = 5000;
/** @const {number} */ var TRANSITION_DURATION = 225;
/**
* Enum of Snackbar types. Used by Snackbar to determine the styling for the
* Snackbar.
* @enum {string}
*/
var SnackbarType = {
INFO: 'info',
SUCCESS: 'success',
WARNING: 'warning',
ERROR: 'error',
};
/**
* Notification bar for displaying a simple message with an action link.
* This element should not be instantiated directly. Instead, users should
* use the Snackbar.show and Snackbar.dismiss functions to ensure proper
* queuing of messages.
* @constructor
* @extends {HTMLDivElement}
*/
var Snackbar = cr.ui.define('div');
Snackbar.prototype = {
__proto__: HTMLDivElement.prototype,
/**
* Decorates an element as a UI element class. Creates the message div and
* action link for the new Snackbar.
*/
decorate: function() {
this.classList.add('snackbar');
this.messageDiv_ = document.createElement('div');
this.appendChild(this.messageDiv_);
this.actionLink_ = document.createElement('a', 'action-link');
this.appendChild(this.actionLink_);
this.boundStartTimeout_ = this.startTimeout_.bind(this);
this.boundStopTimeout_ = this.stopTimeout_.bind(this);
this.addEventListener('mouseleave', this.boundStartTimeout_);
this.addEventListener('mouseenter', this.boundStopTimeout_);
this.timeoutId_ = null;
},
/**
* Initializes the content of the Snackbar with the given |options|
* including the message, action link text, and click action of the link.
* @param {!SnackbarOptions} options
*/
initialize: function(options) {
this.messageDiv_.textContent = options.message;
this.classList.add(options.type);
this.actionLink_.textContent = options.actionText || 'Dismiss';
this.actionLink_.addEventListener('click', function() {
if (options.action) {
options.action();
}
this.dismiss();
}.bind(this));
},
/**
* Shows the Snackbar and dispatches the 'showed' event.
*/
show: function() {
this.classList.add('open');
if (Snackbar.hasContentFocus_) {
this.startTimeout_();
} else {
this.stopTimeout_();
}
document.addEventListener('contentfocus', this.boundStartTimeout_);
document.addEventListener('contentblur', this.boundStopTimeout_);
this.dispatchEvent(new CustomEvent('showed'));
},
/**
* Dismisses the Snackbar. Once the Snackbar is completely hidden, the
* 'dismissed' event is fired and the returned Promise is resolved. If the
* snackbar is already hidden, a resolved Promise is returned.
* @return {!Promise}
*/
dismiss: function() {
this.stopTimeout_();
if (!this.classList.contains('open')) {
return Promise.resolve();
}
return new Promise(function(resolve) {
listenOnce(this, 'transitionend', function() {
this.dispatchEvent(new CustomEvent('dismissed'));
resolve();
}.bind(this));
ensureTransitionEndEvent(this, TRANSITION_DURATION);
this.classList.remove('open');
document.removeEventListener('contentfocus', this.boundStartTimeout_);
document.removeEventListener('contentblur', this.boundStopTimeout_);
}.bind(this));
},
/**
* Starts the timeout for dismissing the Snackbar.
* @private
*/
startTimeout_: function() {
this.timeoutId_ = setTimeout(function() {
this.dismiss();
}.bind(this), SHOW_DURATION);
},
/**
* Stops the timeout for dismissing the Snackbar. Only clears the timeout
* when the Snackbar is open.
* @private
*/
stopTimeout_: function() {
if (this.classList.contains('open')) {
clearTimeout(this.timeoutId_);
this.timeoutId_ = null;
}
},
};
/** @private {?snackbar.Snackbar} */
Snackbar.current_ = null;
/** @private {!Array<!snackbar.Snackbar>} */
Snackbar.queue_ = [];
/** @private {boolean} */
Snackbar.hasContentFocus_ = true;
// There is a chance where the snackbar is shown but the content doesn't have
// focus. In this case, the current focus state must be tracked so the
// snackbar can pause the dismiss timeout.
document.addEventListener('contentfocus', function() {
Snackbar.hasContentFocus_ = true;
});
document.addEventListener('contentblur', function() {
Snackbar.hasContentFocus_ = false;
});
/**
* TODO(crbug.com/675299): Add ability to specify parent element to Snackbar.
* Creates a Snackbar and shows it if one is not showing already. If a
* Snackbar is already active, the next Snackbar is queued.
* @param {string} message The message to display in the Snackbar.
* @param {string=} opt_type A string determining the Snackbar type: info,
* success, warning, error. If not provided, info type is used.
* @param {string=} opt_actionText The text to display for the action link.
* @param {function()=} opt_action A function to be called when the user
* presses the action link.
* @return {!snackbar.Snackbar}
*/
Snackbar.show = function(message, opt_type, opt_actionText, opt_action) {
var options = {
message: message,
type: opt_type || SnackbarType.INFO,
actionText: opt_actionText,
action: opt_action,
};
var newSnackbar = new Snackbar();
newSnackbar.initialize(options);
if (Snackbar.current_) {
Snackbar.queue_.push(newSnackbar);
} else {
Snackbar.show_(newSnackbar);
}
return newSnackbar;
};
/**
* TODO(crbug.com/675299): Add ability to specify parent element to Snackbar.
* Creates a Snackbar and sets events for queuing the next Snackbar to show.
* @param {!snackbar.Snackbar} newSnackbar
* @private
*/
Snackbar.show_ = function(newSnackbar) {
$('snackbar-container').appendChild(newSnackbar);
newSnackbar.addEventListener('dismissed', function() {
$('snackbar-container').removeChild(Snackbar.current_);
var newSnackbar = Snackbar.queue_.shift();
if (newSnackbar) {
Snackbar.show_(newSnackbar);
return;
}
Snackbar.current_ = null;
});
Snackbar.current_ = newSnackbar;
// Show the Snackbar after a slight delay to allow for a layout reflow.
setTimeout(function() {
newSnackbar.show();
}, 10);
};
/**
* Dismisses the Snackbar currently showing.
* @param {boolean} clearQueue If true, clears the Snackbar queue before
* dismissing.
*/
Snackbar.dismiss = function(clearQueue) {
if (clearQueue) {
Snackbar.queue_ = [];
}
if (Snackbar.current_) {
return Snackbar.current_.dismiss();
}
return Promise.resolve();
};
return {
Snackbar: Snackbar,
SnackbarType: SnackbarType,
};
});