Source: deliteful/Toaster.js

/** @module deliteful/Toaster */
define([
	"dcl/dcl",
	"delite/Widget",
	"delite/register",
	"decor/sniff",
	"delite/handlebars!./Toaster/Toaster.html",
	"./ToasterMessage",
	"delite/theme!./Toaster/themes/{{theme}}/Toaster.css"
], function (dcl, Widget, register, has, template, ToasterMessage) {

		/* helpers */
		function isRemovable(m) {return m._toBeRemoved && (! m._isRemoved); }

		/**
		 * Toaster widget.  Displays instances of `ToasterMessage`.
		 *
		 * Messages are posted through `postMessage()`, which takes either
		 * a full `ToasterMessage` instance or a message as a string.
		 *
		 * `ToasterMessage` instances are displayed for a finite or infinite duration.
		 * (see the `duration` property of `ToasterMessage`).
		 *
		 * @class module:deliteful/Toaster
		 * @augments module:delite/Widget
		 * @example
		 *   <d-toaster id="t"></d-toaster>
		 *   <d-button onclick="t.postMessage('button clicked', {duration: 1000})">...</d-button>
		 */
		return register("d-toaster", [HTMLElement, Widget], /** @lends module:deliteful/Toaster */ {

			_wrapper: null,

			baseClass: "d-toaster",

			/**
			 * The name of the CSS class that specifies the placement of
			 * toaster's messages.
			 *
			 * The toaster comes with 7 classes which can be used out-of-the-box.
			 *  - `d-toaster-placement-default` for the default position
			 *  - `d-toaster-placement-tl` for top-left
			 *  - `d-toaster-placement-tc` for top-center
			 *  - `d-toaster-placement-tr` for top-right
			 *  - `d-toaster-placement-bl` for bottom-left
			 *  - `d-toaster-placement-bc` for bottom-center
			 *  - `d-toaster-placement-br` for bottom-right
			 *
			 * This property can be set to any string as long as it references
			 * the name of a CSS class properly defined.
			 *
			 * @member {string}
			 * @default `d-toaster-placement-default`
			 */
			placementClass: "d-toaster-placement-default",

			/**
			 * A list containing all `ToasterMessage` instances posted.
			 * @member {module:deliteful/ToasterMessage[]}
			 * @default null
			 */
			messages: null,

			/**
			 * If `true`, the messages are displayed bottom to top.
			 * @member {boolean}
			 * @default false
			 */
			invertOrder: false,

			/**
			 * A CSS class which is added to each message inserted in the DOM without being visible yet.
			 *
			 * It is toggled to `animationEnterClass`.
			 * This class is useful when you want to set an initial state for `animationEnterClass`.
			 *
			 * @member {string}
			 * @default "d-toaster-initial"
			 */
			animationInitialClass: "d-toaster-initial",

			/**
			 * A CSS class which is inserted to make the message visible in DOM (ex: a fade-in CSS transition).
			 * It is removed as soon as the animation has completed.
			 *
			 * If no `transitionend` or `animationend` event is heard, this class is never removed.
			 *
			 * @member {string}
			 * @default "d-toaster-fadein"
			 */
			animationEnterClass: "d-toaster-fadein",

			/**
			 * A CSS class which is inserted to make the message invisible in DOM (ex: a fade-out CSS transition).
			 * It is toggled to `animationQuitClass` when the animation has completed.
			 *
			 * If no `transitionend` or `animationend` event is heard, this class is never removed.
			 *
			 * @member {string}
			 * @default "d-toaster-fadeout"
			 */
			animationQuitClass: "d-toaster-fadeout",

			/**
			 * A CSS class which is inserted after `animationQuitClass` has completed.
			 *
			 * If no `transitionend` or `animationend` event is heard, this class is never inserted.
			 *
			 * @member {string}
			 * @default "d-toaster-fadefinish"
			 */
			animationEndClass: "d-toaster-fadefinish",

			_emitExpiration: function (m) {
				this.emit("messageExpired", {message: m}); // TODO: shouldn't a event be named lowercase?
			},
			_emitInsertion: function (m) {
				this.emit("messageInserted", {message: m});
			},
			_emitRemoval: function (m) {
				this.emit("messageRemoved", {message: m});
			},
			_getRemovableMsg: function () {
				return this.messages.filter(isRemovable);
			},
			_allExpAreRemovable: function () {
				for (var i = 0, l = this.messages.length; i < l; i++) {
					var m = this.messages[i];
					if (m.isExpirable()) {
						if (!isRemovable(m)) { return false; }
					}
				}
				return true;
			},

			template: template,

			createdCallback: function () {
				this.messages = [];

				// NOTE: the following a11y attributes are needed for JAWS but
				// break VoiceOver
				if (!has("ios")) {
					this.setAttribute("aria-atomic", "true");
					this.setAttribute("role", "alert");
				}
			},

			refreshRendering: function (props) {
				if ("messages" in props) {
					this.messages.forEach(function (m) {
						if (!m._isInserted) {
							m._insertInDom(this, true);
							m._showInDom(this, true);
							this._emitInsertion(m);
						} else if (m.isExpirable() && m._hasExpired && (!m._toBeRemoved)) {
							m._hideInDom(this, true);
							this._emitExpiration(m);
						}
					}, this);
					if (this._allExpAreRemovable()) {
						this._getRemovableMsg().forEach(function (m) {
							m._removeFromDom(this, true);
							m.destroy();
							this.messages.splice(this.messages.indexOf(m), 1);
							this._emitRemoval(m);
						}, this);
					}
				}
			},

			/**
			 * Posts a message in the toaster.
			 *
			 * The message can be either a full `ToasterMessage` instance or
			 * a simple string, in which case the proprieties of the message
			 * are specified through the `props` argument which is passed to the
			 * `ToasterMessage` constructor.
			 *
			 * @param {string|module:deliteful/ToasterMessage} message Either the content of the
			 * message as a string or an instance of `deliteful/ToasterMessage`.
			 * @param {Object} [props] A hash used to initialize a message instance (only relevant 
			 * when `message` passed is a string).
			 *
			 * @returns {module:deliteful/ToasterMessage} The message instance passed as a parameter
			 * or created from the string.
			 */
			postMessage: function (message, props) {
				var m;
				if (typeof(message) === "string") {
					var args = {message: message};
					dcl.mix(args, props);
					m = new ToasterMessage(args);
				} else {
					m = message;
				}
				return this._addMessage(m);
			},
			_addMessage: function (m) {
				this.messages.push(m);
				this.notifyCurrentValue("messages");
				return m;
			}
		});
	});