2 * Angular Material Design
3 * https://github.com/angular/material
7 goog.provide('ngmaterial.components.toast');
8 goog.require('ngmaterial.components.button');
9 goog.require('ngmaterial.core');
12 * @name material.components.toast
16 MdToastDirective['$inject'] = ["$mdToast"];
17 MdToastProvider['$inject'] = ["$$interimElementProvider"];
18 angular.module('material.components.toast', [
20 'material.components.button'
22 .directive('mdToast', MdToastDirective)
23 .provider('$mdToast', MdToastProvider);
26 function MdToastDirective($mdToast) {
29 link: function postLink(scope, element) {
30 element.addClass('_md'); // private md component indicator for styling
32 // When navigation force destroys an interimElement, then
33 // listen and $destroy() that interim instance...
34 scope.$on('$destroy', function() {
44 * @module material.components.toast
47 * `$mdToast` is a service to build a toast notification on any position
48 * on the screen with an optional duration, and provides a simple promise API.
50 * The toast will be always positioned at the `bottom`, when the screen size is
51 * between `600px` and `959px` (`sm` breakpoint)
53 * ## Restrictions on custom toasts
54 * - The toast's template must have an outer `<md-toast>` element.
55 * - For a toast action, use element with class `md-action`.
56 * - Add the class `md-capsule` for curved corners.
59 * Developers are also able to create their own preset, which can be easily used without repeating
60 * their options each time.
63 * $mdToastProvider.addPreset('testPreset', {
64 * options: function() {
68 * '<div class="md-toast-content">' +
69 * 'This is a custom preset' +
72 * controllerAs: 'toast',
73 * bindToController: true
79 * After you created your preset at config phase, you can easily access it.
83 * $mdToast.testPreset()
87 * ## Parent container notes
89 * The toast is positioned using absolute positioning relative to its first non-static parent
90 * container. Thus, if the requested parent container uses static positioning, we will temporarily
91 * set its positioning to `relative` while the toast is visible and reset it when the toast is
94 * Because of this, it is usually best to ensure that the parent container has a fixed height and
95 * prevents scrolling by setting the `overflow: hidden;` style. Since the position is based off of
96 * the parent's height, the toast may be mispositioned if you allow the parent to scroll.
98 * You can, however, have a scrollable element inside of the container; just make sure the
99 * container itself does not scroll.
102 * <div layout-fill id="toast-container">
104 * I can have lots of content and scroll!
111 * <div ng-controller="MyController">
112 * <md-button ng-click="openToast()">
119 * var app = angular.module('app', ['ngMaterial']);
120 * app.controller('MyController', function($scope, $mdToast) {
121 * $scope.openToast = function($event) {
122 * $mdToast.show($mdToast.simple().textContent('Hello!'));
123 * // Could also do $mdToast.showSimple('Hello');
131 * @name $mdToast#showSimple
133 * @param {string} message The message to display inside the toast
135 * Convenience method which builds and shows a simple toast.
137 * @returns {promise} A promise that can be resolved with `$mdToast.hide()` or
138 * rejected with `$mdToast.cancel()`.
144 * @name $mdToast#simple
147 * Builds a preconfigured toast.
149 * @returns {obj} a `$mdToastPreset` with the following chainable configuration methods.
151 * _**Note:** These configuration methods are provided in addition to the methods provided by
152 * the `build()` and `show()` methods below._
154 * <table class="md-api-table methods">
158 * <th>Description</th>
163 * <td>`.textContent(string)`</td>
164 * <td>Sets the toast content to the specified string</td>
167 * <td>`.action(string)`</td>
169 * Adds an action button. <br/>
170 * If clicked, the promise (returned from `show()`)
171 * will resolve with the value `'ok'`; otherwise, it is resolved with `true` after a `hideDelay`
176 * <td>`.highlightAction(boolean)`</td>
178 * Whether or not the action button will have an additional highlight class.<br/>
179 * By default the `accent` color will be applied to the action button.
183 * <td>`.highlightClass(string)`</td>
185 * If set, the given class will be applied to the highlighted action button.<br/>
186 * This allows you to specify the highlight color easily. Highlight classes are `md-primary`, `md-warn`
191 * <td>`.capsule(boolean)`</td>
192 * <td>Whether or not to add the `md-capsule` class to the toast to provide rounded corners</td>
195 * <td>`.theme(string)`</td>
196 * <td>Sets the theme on the toast to the requested theme. Default is `$mdThemingProvider`'s default.</td>
199 * <td>`.toastClass(string)`</td>
200 * <td>Sets a class on the toast element</td>
209 * @name $mdToast#updateTextContent
212 * Updates the content of an existing toast. Useful for updating things like counts, etc.
218 * @name $mdToast#build
221 * Creates a custom `$mdToastPreset` that you can configure.
223 * @returns {obj} a `$mdToastPreset` with the chainable configuration methods for shows' options (see below).
228 * @name $mdToast#show
230 * @description Shows the toast.
232 * @param {object} optionsOrPreset Either provide an `$mdToastPreset` returned from `simple()`
233 * and `build()`, or an options object with the following properties:
235 * - `templateUrl` - `{string=}`: The url of an html template file that will
236 * be used as the content of the toast. Restrictions: the template must
237 * have an outer `md-toast` element.
238 * - `template` - `{string=}`: Same as templateUrl, except this is an actual
240 * - `autoWrap` - `{boolean=}`: Whether or not to automatically wrap the template content with a
241 * `<div class="md-toast-content">` if one is not provided. Defaults to true. Can be disabled if you provide a
242 * custom toast directive.
243 * - `scope` - `{object=}`: the scope to link the template / controller to. If none is specified, it will create a new child scope.
244 * This scope will be destroyed when the toast is removed unless `preserveScope` is set to true.
245 * - `preserveScope` - `{boolean=}`: whether to preserve the scope when the element is removed. Default is false
246 * - `hideDelay` - `{number=}`: How many milliseconds the toast should stay
247 * active before automatically closing. Set to 0 or false to have the toast stay open until
248 * closed manually. Default: 3000.
249 * - `position` - `{string=}`: Sets the position of the toast. <br/>
250 * Available: any combination of `'bottom'`, `'left'`, `'top'`, `'right'`, `'end'` and `'start'`.
251 * The properties `'end'` and `'start'` are dynamic and can be used for RTL support.<br/>
252 * Default combination: `'bottom left'`.
253 * - `toastClass` - `{string=}`: A class to set on the toast element.
254 * - `controller` - `{string=}`: The controller to associate with this toast.
255 * The controller will be injected the local `$mdToast.hide( )`, which is a function
256 * used to hide the toast.
257 * - `locals` - `{string=}`: An object containing key/value pairs. The keys will
258 * be used as names of values to inject into the controller. For example,
259 * `locals: {three: 3}` would inject `three` into the controller with the value
261 * - `bindToController` - `bool`: bind the locals to the controller, instead of passing them in.
262 * - `resolve` - `{object=}`: Similar to locals, except it takes promises as values
263 * and the toast will not open until the promises resolve.
264 * - `controllerAs` - `{string=}`: An alias to assign the controller to on the scope.
265 * - `parent` - `{element=}`: The element to append the toast to. Defaults to appending
266 * to the root element of the application.
268 * @returns {promise} A promise that can be resolved with `$mdToast.hide()` or
269 * rejected with `$mdToast.cancel()`. `$mdToast.hide()` will resolve either with a Boolean
270 * value == 'true' or the value passed as an argument to `$mdToast.hide()`.
271 * And `$mdToast.cancel()` will resolve the promise with a Boolean value == 'false'
276 * @name $mdToast#hide
279 * Hide an existing toast and resolve the promise returned from `$mdToast.show()`.
281 * @param {*=} response An argument for the resolved promise.
283 * @returns {promise} a promise that is called when the existing element is removed from the DOM.
284 * The promise is resolved with either a Boolean value == 'true' or the value passed as the
285 * argument to `.hide()`.
291 * @name $mdToast#cancel
294 * `DEPRECATED` - The promise returned from opening a toast is used only to notify about the closing of the toast.
295 * As such, there isn't any reason to also allow that promise to be rejected,
296 * since it's not clear what the difference between resolve and reject would be.
298 * Hide the existing toast and reject the promise returned from
301 * @param {*=} response An argument for the rejected promise.
303 * @returns {promise} a promise that is called when the existing element is removed from the DOM
304 * The promise is resolved with a Boolean value == 'false'.
308 function MdToastProvider($$interimElementProvider) {
309 // Differentiate promise resolves: hide timeout (value == true) and hide action clicks (value == ok).
310 toastDefaultOptions['$inject'] = ["$animate", "$mdToast", "$mdUtil", "$mdMedia"];
311 var ACTION_RESOLVE = 'ok';
313 var activeToastContent;
314 var $mdToast = $$interimElementProvider('$mdToast')
316 methods: ['position', 'hideDelay', 'capsule', 'parent', 'position', 'toastClass'],
317 options: toastDefaultOptions
319 .addPreset('simple', {
320 argOption: 'textContent',
321 methods: ['textContent', 'content', 'action', 'highlightAction', 'highlightClass', 'theme', 'parent' ],
322 options: /* ngInject */ ["$mdToast", "$mdTheming", function($mdToast, $mdTheming) {
325 '<md-toast md-theme="{{ toast.theme }}" ng-class="{\'md-capsule\': toast.capsule}">' +
326 ' <div class="md-toast-content">' +
327 ' <span class="md-toast-text" role="alert" aria-relevant="all" aria-atomic="true">' +
328 ' {{ toast.content }}' +
330 ' <md-button class="md-action" ng-if="toast.action" ng-click="toast.resolve()" ' +
331 ' ng-class="highlightClasses">' +
332 ' {{ toast.action }}' +
336 controller: /* ngInject */ ["$scope", function mdToastCtrl($scope) {
339 if (self.highlightAction) {
340 $scope.highlightClasses = [
346 $scope.$watch(function() { return activeToastContent; }, function() {
347 self.content = activeToastContent;
350 this.resolve = function() {
351 $mdToast.hide( ACTION_RESOLVE );
354 theme: $mdTheming.defaultTheme(),
355 controllerAs: 'toast',
356 bindToController: true
360 .addMethod('updateTextContent', updateTextContent)
361 .addMethod('updateContent', updateTextContent);
363 function updateTextContent(newContent) {
364 activeToastContent = newContent;
370 function toastDefaultOptions($animate, $mdToast, $mdUtil, $mdMedia) {
371 var SWIPE_EVENTS = '$md.swipeleft $md.swiperight $md.swipeup $md.swipedown';
376 position: 'bottom left',
380 transformTemplate: function(template, options) {
381 var shouldAddWrapper = options.autoWrap && template && !/md-toast-content/g.test(template);
383 if (shouldAddWrapper) {
384 // Root element of template will be <md-toast>. We need to wrap all of its content inside of
385 // of <div class="md-toast-content">. All templates provided here should be static, developer-controlled
386 // content (meaning we're not attempting to guard against XSS).
387 var templateRoot = document.createElement('md-template');
388 templateRoot.innerHTML = template;
390 // Iterate through all root children, to detect possible md-toast directives.
391 for (var i = 0; i < templateRoot.children.length; i++) {
392 if (templateRoot.children[i].nodeName === 'MD-TOAST') {
393 var wrapper = angular.element('<div class="md-toast-content">');
395 // Wrap the children of the `md-toast` directive in jqLite, to be able to append multiple
396 // nodes with the same execution.
397 wrapper.append(angular.element(templateRoot.children[i].childNodes));
399 // Append the new wrapped element to the `md-toast` directive.
400 templateRoot.children[i].appendChild(wrapper[0]);
404 // We have to return the innerHTMl, because we do not want to have the `md-template` element to be
405 // the root element of our interimElement.
406 return templateRoot.innerHTML;
409 return template || '';
413 function onShow(scope, element, options) {
414 activeToastContent = options.textContent || options.content; // support deprecated #content method
416 var isSmScreen = !$mdMedia('gt-sm');
418 element = $mdUtil.extractElementByName(element, 'md-toast', true);
419 options.element = element;
421 options.onSwipe = function(ev, gesture) {
422 //Add the relevant swipe class to the element so it can animate correctly
423 var swipe = ev.type.replace('$md.','');
424 var direction = swipe.replace('swipe', '');
426 // If the swipe direction is down/up but the toast came from top/bottom don't fade away
427 // Unless the screen is small, then the toast always on bottom
428 if ((direction === 'down' && options.position.indexOf('top') != -1 && !isSmScreen) ||
429 (direction === 'up' && (options.position.indexOf('bottom') != -1 || isSmScreen))) {
433 if ((direction === 'left' || direction === 'right') && isSmScreen) {
437 element.addClass('md-' + swipe);
438 $mdUtil.nextTick($mdToast.cancel);
440 options.openClass = toastOpenClass(options.position);
442 element.addClass(options.toastClass);
444 // 'top left' -> 'md-top md-left'
445 options.parent.addClass(options.openClass);
447 // static is the default position
448 if ($mdUtil.hasComputedStyle(options.parent, 'position', 'static')) {
449 options.parent.css('position', 'relative');
452 element.on(SWIPE_EVENTS, options.onSwipe);
453 element.addClass(isSmScreen ? 'md-bottom' : options.position.split(' ').map(function(pos) {
457 if (options.parent) options.parent.addClass('md-toast-animating');
458 return $animate.enter(element, options.parent).then(function() {
459 if (options.parent) options.parent.removeClass('md-toast-animating');
463 function onRemove(scope, element, options) {
464 element.off(SWIPE_EVENTS, options.onSwipe);
465 if (options.parent) options.parent.addClass('md-toast-animating');
466 if (options.openClass) options.parent.removeClass(options.openClass);
468 return ((options.$destroy == true) ? element.remove() : $animate.leave(element))
470 if (options.parent) options.parent.removeClass('md-toast-animating');
471 if ($mdUtil.hasComputedStyle(options.parent, 'position', 'static')) {
472 options.parent.css('position', '');
477 function toastOpenClass(position) {
478 // For mobile, always open full-width on bottom
479 if (!$mdMedia('gt-xs')) {
480 return 'md-toast-open-bottom';
483 return 'md-toast-open-' +
484 (position.indexOf('top') > -1 ? 'top' : 'bottom');
490 ngmaterial.components.toast = angular.module("material.components.toast");