├── .gitignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── bower.json ├── config └── testem.json ├── dist ├── angular-toastr.css ├── angular-toastr.js ├── angular-toastr.min.css ├── angular-toastr.min.js ├── angular-toastr.tpls.js └── angular-toastr.tpls.min.js ├── gulpfile.js ├── index.js ├── package.json ├── src ├── directives │ ├── progressbar │ │ ├── progressbar.directive.js │ │ └── progressbar.html │ └── toast │ │ ├── toast.controller.js │ │ ├── toast.directive.js │ │ └── toast.html ├── toastr.config.js ├── toastr.js └── toastr.less └── test └── toastr_spec.js /.gitignore: -------------------------------------------------------------------------------- 1 | gen/ 2 | node_modules/ 3 | playground/ 4 | .idea/ -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "newcap": true, 6 | "noarg": true, 7 | "sub": true, 8 | "boss": true, 9 | "eqnull": true, 10 | "quotmark": "single", 11 | "trailing": true, 12 | "globals": { 13 | "angular": true 14 | } 15 | } -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | config/ 3 | .idea/ 4 | gen/ 5 | playground/ 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 5.5 4 | before_install: 5 | - mkdir travis-phantomjs 6 | - wget https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-x86_64.tar.bz2 -O $PWD/travis-phantomjs/phantomjs-2.1.1-linux-x86_64.tar.bz2 7 | - tar -xvf $PWD/travis-phantomjs/phantomjs-2.1.1-linux-x86_64.tar.bz2 -C $PWD/travis-phantomjs 8 | - export PATH=$PWD/travis-phantomjs/phantomjs-2.1.1-linux-x86_64/bin:$PATH 9 | - npm install -g gulp testem 10 | script: "gulp travis && testem ci -f config/testem.json" 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Version 2.1.1 4 | 5 | - Add /dist again for bower. 6 | 7 | ## Version 2.1.0 8 | 9 | - Fix wrong z-index in production build. 10 | - Add refreshTimer method to refresh the timeout of shown toasts. 11 | 12 | ## Version 2.0.0 13 | Not the promised version, but should help with some unwanted bugs. 14 | 15 | - `replace: true` has been removed from the directives. 16 | - Preserve `margin-bottom` of each toasts. 17 | 18 | **BREAKING CHANGE:** 19 | 20 | `replace: true` has been removed. If you use a custom template, you will probably need to do 21 | some CSS changes. On the good part, it should help with some bugs. 22 | 23 | ## Version 1.7.0 24 | 25 | - `toastr` service has an `active()` method to get all the opened toasts. 26 | 27 | ## Version 1.6.0 28 | 29 | - onTap callback receives the whole toast as the first parameter. 30 | - onShown callback receives the whole toast as the first parameter. 31 | - onHidden callback receives the whole toast as the second parameter. 32 | 33 | ## Version 1.5.0 34 | 35 | - Fix an issue where `maxOpened` with 2 or more was conflicting with `autoDismiss`. 36 | - Fix that when you try to close an undefined toast it won't close them all. 37 | - Toasts should be now more accessible. 38 | - You can now pass custom data to templates, useful for custom templates 39 | - New callback, `onTap` which is called when you click a toast (doesn't have to be closed for it to work). 40 | - Fix `onHidden` to have the `wasClicked` parameter to true when using a toast close button. 41 | 42 | ## Version 1.4.1 43 | 44 | - Fix a typo on the toastr.less file that prevented some automated tools to work. 45 | - Add the license to bower.json. 46 | 47 | ## Version 1.4.0 48 | 49 | - With `preventOpenDuplicates` you can prevent duplicates of opened toasts. 50 | - Webpack / Browserify support. 51 | - Now the bower package won't try to fetch the latest angular version. 52 | 53 | ## Version 1.3.1 54 | 55 | - Add compatibility with `Angular 1.4.x`. 56 | 57 | ## Version 1.3.0 58 | 59 | - An `autoDismiss` option to be used with `maxOpened` to dismiss the oldest toast. 60 | - Every toast has now an `isOpened` property to see whether they are opened or not. 61 | 62 | ## Version 1.2.1 63 | 64 | - Remove a nasty console.log from the progress bar (yikes!). 65 | 66 | ## Version 1.2.0 67 | 68 | - Support for a progress bar. 69 | - A config option to change the path of the templates. 70 | 71 | **BREAKING CHANGE:** 72 | 73 | If you were using a custom template using the default path, it changed from: 74 | 75 | `templates/toastr/toastr.html` 76 | 77 | to 78 | 79 | `directives/toast/toast.html` 80 | 81 | ## Version 1.1.0 82 | 83 | - Now you can prevent the last toast from being duplicated setting `preventDuplicates` to true. 84 | - Fix toasts options not working if the title parameter is set to null. 85 | - Prevent toasts to override global options. 86 | 87 | ## Version 1.0.2 88 | 89 | - Fixed an issue where it wouldn't work anymore without `ngAnimate`. 90 | 91 | ## Version 1.0.1 92 | 93 | - Hotfix for npm package. 94 | 95 | ## Version 1.0.0 96 | 97 | - No changes since last beta 98 | 99 | ## Version 1.0.0-beta.3 100 | 101 | - Be able to specify a concrete target for container. 102 | - Using $injector internally to avoid circular dependencies. 103 | - onHidden receives a parameter to see whether a toast was closed by timeout or click. 104 | - Fix an issue with toasts not closing up. 105 | 106 | ## Version 1.0.0-beta.2 107 | 108 | - Fix maxOpened. Now toasts are queued when the max is reached. 109 | 110 | ## Version 1.0.0-beta.1 111 | 112 | - Maximum opened toasts can be limited now. 113 | - Allows to attach an `onShown` and `onHidden` callback. 114 | - Allows toasts to override options without title [9013c4d](https://github.com/Foxandxss/angular-toastr/commit/9013c4d1c7562d2ba5047c1e969a0316eb4e6c1d) 115 | 116 | ## Version 0.5.2 117 | 118 | - Removed the support for IE 8 (in terms of CSS) 119 | - Changed `$timeout` to `$interval` so protractor tests won't fail. 120 | 121 | ## Version 0.5.1 122 | 123 | - newestOnTop, with that you can choose whether to add new toasts on the top or bottom. Top by default. 124 | 125 | ## Version 0.5.0 126 | 127 | - Angular 1.3.x support 128 | 129 | ## Version 0.4.0 130 | 131 | - You can add HTML on the toastr titles. 132 | - You can now override the toast's template. 133 | - Fix issue using toastr with ionic. 134 | 135 | ## Version 0.3.0 136 | 137 | - Now the toasts supports a close button. 138 | - Be able to disable to close on click. 139 | 140 | ## Version 0.2.4 141 | 142 | - Fixes #2 where a toast could remain open for all eternity. 143 | 144 | ## Version 0.2.0 145 | 146 | - You can make an sticky toast if you set the `timeOut` to 0. If you also set `extendedTimeOut` to 0 the sticky won't go away until you click on them. 147 | - Toasts accept custom HTML into them! 148 | 149 | ## Version 0.1.2 150 | 151 | - Animations are now optional 152 | - Removed the possibility to add the toast container where you want to (that will be back in a future) 153 | 154 | ## Version 0.1.1 155 | 156 | - The `close` method has been renamed to `clear` to match the original API 157 | 158 | ## Version 0.1.0 159 | 160 | - Initial release 161 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | For contributing in this project, you need to create a pull request containing both your code and tests. 2 | 3 | To create a proper patch I suggest: 4 | 5 | ``` 6 | $ npm install -g gulp testem 7 | $ gulp 8 | ``` 9 | 10 | And in another terminal / tab: 11 | 12 | ``` 13 | $ testem -f config/testem.json 14 | ``` 15 | 16 | Then you can see if you have your new tests passing. 17 | 18 | Please, don't include the files at `/dist`. They can sometimes conflict and it is better that I generate then by hand after merging your PR. 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2016 Jesús Rodríguez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular Toastr 2 | 3 | **This project needs new maintainers. I cannot maintain it anymore, I don't do more AngularJS and I don't have the time for it anymore, please send an email or open an issue if you wish to maintain it** 4 | 5 | [![Code Climate](https://codeclimate.com/github/Foxandxss/angular-toastr.png)](https://codeclimate.com/github/Foxandxss/angular-toastr) [![Build Status](https://travis-ci.org/Foxandxss/angular-toastr.svg?branch=master)](https://travis-ci.org/Foxandxss/angular-toastr) [![devDependency Status](https://david-dm.org/Foxandxss/angular-toastr/dev-status.svg)](https://david-dm.org/Foxandxss/angular-toastr#info=devDependencies) 6 | 7 | **NOTE:** For angular 1.2.x support check `angular-1.2` branch or download the `0.4.x` release of `angular-toastr`. 8 | 9 | **angular-toastr** was originally a port of [CodeSeven/toastr](https://github.com/CodeSeven/toastr). It could now show some differences with it. 10 | 11 | The goal is to provide the same API than the original one but without jQuery and using all the angular power. 12 | 13 | ## Demo 14 | 15 | [Demo](http://foxandxss.github.io/angular-toastr/) 16 | 17 | ## Installation 18 | 19 | Use npm: 20 | 21 | ``` 22 | $ npm install angular-toastr 23 | ``` 24 | 25 | If you are not using npm (you should), you can use bower: 26 | 27 | ``` 28 | $ bower install angular-toastr 29 | ``` 30 | 31 | To use a CDN, you can include one of these options: 32 | 33 | ```html 34 | 35 | 36 | 37 | 38 | 39 | ``` 40 | 41 | Or you can grab the latest [release](https://github.com/Foxandxss/angular-toastr/releases) and add both the `css` and `javascript` file: 42 | 43 | ```html 44 | 45 | 46 | ``` 47 | 48 | **Note:** If you add a script tag for angular-toastr, keep in mind that you need the `tpls` version **or** the other depending if you want the default template or not (see below). 49 | 50 | If you want animations, don't forget to add `angular-animate`. 51 | 52 | Then add `toastr` to your modules dependencies: 53 | 54 | ```javascript 55 | angular.module('app', ['ngAnimate', 'toastr']) 56 | ``` 57 | 58 | ## Usage 59 | 60 | Toastr usage is very simple, by default it comes with four types of notification messages: 61 | 62 | Success: 63 | 64 | ```javascript 65 | app.controller('foo', function($scope, toastr) { 66 | toastr.success('Hello world!', 'Toastr fun!'); 67 | }); 68 | ``` 69 | 70 | ![Success Image](http://i.imgur.com/5LTPLFK.png) 71 | 72 | Info: 73 | 74 | ```javascript 75 | app.controller('foo', function($scope, toastr) { 76 | toastr.info('We are open today from 10 to 22', 'Information'); 77 | }); 78 | ``` 79 | 80 | ![Info Image](http://i.imgur.com/7coIu7q.png) 81 | 82 | Error: 83 | 84 | ```javascript 85 | app.controller('foo', function($scope, toastr) { 86 | toastr.error('Your credentials are gone', 'Error'); 87 | }); 88 | ``` 89 | 90 | ![Error Image](http://i.imgur.com/sXdKsDK.png) 91 | 92 | Warning: 93 | 94 | ```javascript 95 | app.controller('foo', function($scope, toastr) { 96 | toastr.warning('Your computer is about to explode!', 'Warning'); 97 | }); 98 | ``` 99 | 100 | ![Warning Image](http://i.imgur.com/k4g8vMz.png) 101 | 102 | Apart from that you can customize your basic toasts: 103 | 104 | No title: 105 | 106 | ```javascript 107 | app.controller('foo', function($scope, toastr) { 108 | toastr.success('I don\'t need a title to live'); 109 | }); 110 | ``` 111 | 112 | ![No Title](http://i.imgur.com/GnwWFo4.png) 113 | 114 | #### Closing toasts programmatically: 115 | 116 | ```javascript 117 | app.controller('foo', function($scope, toastr) { 118 | toastr.clear([toast]); 119 | }); 120 | ``` 121 | 122 | If no toast is passed in, all toasts will be closed. 123 | 124 | #### Getting active (open) toasts: 125 | 126 | ```javascript 127 | app.controller('foo', function($scope, toastr) { 128 | toastr.active(); 129 | }); 130 | ``` 131 | 132 | #### Refreshing an opened toast: 133 | 134 | ```javascript 135 | app.controller('foo', function($scope, toastr) { 136 | var toast = toastr.error('You are not allowed to do this!'); 137 | // after doing something... 138 | toastr.refreshTimer(toast, 5000); 139 | }); 140 | ``` 141 | 142 | The second parameter is optional and will fallback to the configured timeOut. 143 | 144 | It return the number of active toasts in screen. 145 | 146 | #### Other options 147 | 148 | A toast has a `isOpened` flag to see whether it is opened or not. 149 | 150 | ### Toastr customization 151 | 152 | This library has two parts, a `container` and the `toasts` you put in it. 153 | 154 | To configure the `container` you need to modify the `toastrConfig`, for example: 155 | 156 | ```javascript 157 | app.config(function(toastrConfig) { 158 | angular.extend(toastrConfig, { 159 | autoDismiss: false, 160 | containerId: 'toast-container', 161 | maxOpened: 0, 162 | newestOnTop: true, 163 | positionClass: 'toast-top-right', 164 | preventDuplicates: false, 165 | preventOpenDuplicates: false, 166 | target: 'body' 167 | }); 168 | }); 169 | ``` 170 | 171 | Those are the default values, you can pick what you need from it and override with your values. 172 | 173 | * **autoDismiss** If set, show only the most recent `maxOpened` toast(s) 174 | * **containerId**: The name of the container where you want to append your toasts (the container will be created for you). 175 | * **maxOpened**: Maximum number of toasts displayed at once. 176 | * **newestOnTop**: Add new toasts on top of the old one. Put on false to put them on the bottom. 177 | * **positionClass**: The position where the toasts are added. 178 | * **preventDuplicates**: Prevent duplicates of the last toast. 179 | * **preventOpenDuplicates**: Prevent duplicates of open toasts. 180 | * **target**: The element to put the toastr container. 181 | 182 | To customize a `toast` you have two options. First, you can set a default option to be applied globally to all `toasts` in the same way you modified the `container`: 183 | 184 | ```javascript 185 | app.config(function(toastrConfig) { 186 | angular.extend(toastrConfig, { 187 | allowHtml: false, 188 | closeButton: false, 189 | closeHtml: '', 190 | extendedTimeOut: 1000, 191 | iconClasses: { 192 | error: 'toast-error', 193 | info: 'toast-info', 194 | success: 'toast-success', 195 | warning: 'toast-warning' 196 | }, 197 | messageClass: 'toast-message', 198 | onHidden: null, 199 | onShown: null, 200 | onTap: null, 201 | progressBar: false, 202 | tapToDismiss: true, 203 | templates: { 204 | toast: 'directives/toast/toast.html', 205 | progressbar: 'directives/progressbar/progressbar.html' 206 | }, 207 | timeOut: 5000, 208 | titleClass: 'toast-title', 209 | toastClass: 'toast' 210 | }); 211 | }); 212 | ``` 213 | 214 | * **allowHtml**: Your toast can use custom HTML here (See [Issue 3](https://github.com/Foxandxss/angular-toastr/issues/3)) 215 | * **closeButton**: Whether to display an "X" close button on the toast. 216 | * **closeHtml**: Html element to be used as a close button. 217 | * **extendedTimeOut**: The timeout after you hover a toast. 218 | * **extraData**: If you override the template, you can pass global extra data to your toasts. 219 | * **iconClasses**: The default type classes for the different toasts. 220 | * **messageClass**: The class for the toast's message. 221 | * **progressBar**: A progress bar to see the timeout in real time. 222 | * **tapToDismiss**: Whether the toast should be dismissed when it is clicked. 223 | * **templates**: To override the default path of the templates. 224 | * **timeOut**: The timeout before the toasts disappear. 225 | * **titleClass**: The class for the toast's title. 226 | * **toastClass**: Base class for toasts. 227 | 228 | Toasts have 3 different callbacks: 229 | 230 | * **onHidden**: A callback function called when a toast gets hidden. 231 | * First parameter: A boolean to see whether or not the toast was closed via click. 232 | * Second parameter: The whole toast that got hidden. 233 | * **onShown**: A callback function called when a toast is shown. 234 | * First parameter: The whole toast that got shown. 235 | * **onTap**: A callback function called when it is clicked. 236 | * First parameter: The whole toast that got clicked. 237 | 238 | The second option is to pass a third parameter (or second if you don't need a **title**). Let see some examples: 239 | 240 | Toast with custom HTML (available in both title and message): 241 | 242 | ```javascript 243 | toastr.info(' Success!', 'With HTML', { 244 | allowHtml: true 245 | }); 246 | ``` 247 | 248 | ![Html Image](http://i.imgur.com/mvz6wcW.png) 249 | 250 | Toast with a close button: 251 | 252 | ```javascript 253 | toastr.success('What a nice button', 'Button spree', { 254 | closeButton: true 255 | }); 256 | ``` 257 | 258 | ![Html Image](http://i.imgur.com/yupvey4.png) 259 | 260 | Toast with a custom button for apple fans: 261 | 262 | ```javascript 263 | toastr.info('What a nice apple button', 'Button spree', { 264 | closeButton: true, 265 | closeHtml: '' 266 | }); 267 | ``` 268 | 269 | ![Html Image](http://i.imgur.com/TaCm9HG.png) 270 | 271 | A pinky custom style (you can also create here new types with `$decorate`): 272 | 273 | ```javascript 274 | toastr.info('I am totally custom!', 'Happy toast', { 275 | iconClass: 'toast-pink' 276 | }); 277 | ``` 278 | 279 | `toast-pink` is a custom class created for the occasion: 280 | 281 | ```css 282 | .toast-pink { 283 | background-image: url(...) !important; 284 | background-color: #fa39c3; 285 | } 286 | ``` 287 | 288 | ![Pink image](http://i.imgur.com/jur31Zd.png) 289 | 290 | ### Toast template 291 | 292 | If you want to use the built-in template, you can use the `angular-toastr.tpls.js` file. 293 | 294 | If you decide that you don't want to use the built-in one, you can always use `angular-toastr.js` file and then providing your own template like this: 295 | 296 | ```javascript 297 | angular.module('yourApp').run(['$templateCache', function($templateCache) { 298 | $templateCache.put('directives/toast/toast.html', 299 | "
Your template here
" 300 | ); 301 | $templateCache.put('directives/progressbar/progressbar.html', 302 | "
Your progressbar here
" 303 | ); 304 | }]); 305 | ``` 306 | 307 | The important part here is to have a key named `templates/toastr/toastr.html`. The module you run it is not important, you just need to do it after you load `toastr`. 308 | 309 | **NOTE**: Due some limitations in Angular, you need to have your custom template cached before trying to use it. 310 | 311 | 312 | ## Building 313 | 314 | If you want to build from master, you need to: 315 | 316 | ``` 317 | $ npm install -g gulp 318 | $ npm install 319 | $ gulp production 320 | ``` 321 | 322 | Grab the compressed files under `/dist` and the dev files at `/gen`. 323 | 324 | ---------- 325 | 326 | ## FAQ 327 | 328 | **Q:** Why can't I override the `positionClass` in a toast? It gets ignored. 329 | **A:** The toasts don't have a position, they are attached to a container and is that container who has the position set on the page. This will be changed in a future version. 330 | 331 | ## Libraries using `angular-toastr` 332 | 333 | * [CodeScaleInc/angular-toastr-flash](https://github.com/CodeScaleInc/angular-toastr-flash) - A library to show flash messages using toasts. 334 | 335 | ## Credits 336 | 337 | All the credits for the guys at [CodeSeven/toastr](https://github.com/CodeSeven/toastr) for creating the original implementation. 338 | 339 | ## License 340 | 341 | Mit License: [http://www.opensource.org/licenses/mit-license.php](http://www.opensource.org/licenses/mit-license.php) 342 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-toastr", 3 | "version": "2.1.1", 4 | "authors": [ 5 | "Jesus Rodriguez " 6 | ], 7 | "license": "MIT", 8 | "keywords": [ 9 | "angular", 10 | "angularjs", 11 | "toast", 12 | "toastr" 13 | ], 14 | "main": [ 15 | "./dist/angular-toastr.tpls.js", 16 | "./dist/angular-toastr.css" 17 | ], 18 | "ignore": [ 19 | "config", 20 | "node_modules", 21 | "playground", 22 | "src", 23 | "test", 24 | ".travis.yml", 25 | ".gitignore", 26 | ".jshintrc", 27 | "CHANGELOG.md", 28 | "Gruntfile.js", 29 | "package.json" 30 | ], 31 | "dependencies": { 32 | "angular": ">=1.3.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /config/testem.json: -------------------------------------------------------------------------------- 1 | { 2 | "framework" : "jasmine2", 3 | "launch_in_dev" : ["Chrome"], 4 | "launch_in_ci" : ["PhantomJS"], 5 | "src_files" : [ 6 | "node_modules/jquery/dist/jquery.js", 7 | "node_modules/angular/angular.js", 8 | "node_modules/angular-mocks/angular-mocks.js", 9 | "gen/toastr.js", 10 | "gen/toastr.tpl.js", 11 | "test/toastr_spec.js" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /dist/angular-toastr.css: -------------------------------------------------------------------------------- 1 | .toast-title { 2 | font-weight: bold; 3 | } 4 | .toast-message { 5 | word-wrap: break-word; 6 | } 7 | .toast-message a, 8 | .toast-message label { 9 | color: #FFFFFF; 10 | } 11 | .toast-message a:hover { 12 | color: #CCCCCC; 13 | text-decoration: none; 14 | } 15 | .toast-close-button { 16 | position: relative; 17 | right: -0.3em; 18 | top: -0.3em; 19 | float: right; 20 | font-size: 20px; 21 | font-weight: bold; 22 | color: #FFFFFF; 23 | -webkit-text-shadow: 0 1px 0 #ffffff; 24 | text-shadow: 0 1px 0 #ffffff; 25 | opacity: 0.8; 26 | } 27 | .toast-close-button:hover, 28 | .toast-close-button:focus { 29 | color: #000000; 30 | text-decoration: none; 31 | cursor: pointer; 32 | opacity: 0.4; 33 | } 34 | /*Additional properties for button version 35 | iOS requires the button element instead of an anchor tag. 36 | If you want the anchor version, it requires `href="#"`.*/ 37 | button.toast-close-button { 38 | padding: 0; 39 | cursor: pointer; 40 | background: transparent; 41 | border: 0; 42 | -webkit-appearance: none; 43 | } 44 | .toast-top-center { 45 | top: 0; 46 | right: 0; 47 | width: 100%; 48 | } 49 | .toast-bottom-center { 50 | bottom: 0; 51 | right: 0; 52 | width: 100%; 53 | } 54 | .toast-top-full-width { 55 | top: 0; 56 | right: 0; 57 | width: 100%; 58 | } 59 | .toast-bottom-full-width { 60 | bottom: 0; 61 | right: 0; 62 | width: 100%; 63 | } 64 | .toast-top-left { 65 | top: 12px; 66 | left: 12px; 67 | } 68 | .toast-top-right { 69 | top: 12px; 70 | right: 12px; 71 | } 72 | .toast-bottom-right { 73 | right: 12px; 74 | bottom: 12px; 75 | } 76 | .toast-bottom-left { 77 | bottom: 12px; 78 | left: 12px; 79 | } 80 | #toast-container { 81 | position: fixed; 82 | z-index: 999999; 83 | /*overrides*/ 84 | } 85 | #toast-container * { 86 | -moz-box-sizing: border-box; 87 | -webkit-box-sizing: border-box; 88 | box-sizing: border-box; 89 | } 90 | #toast-container .toast { 91 | position: relative; 92 | overflow: hidden; 93 | margin: 0 0 6px; 94 | padding: 15px 15px 15px 50px; 95 | width: 300px; 96 | -moz-border-radius: 3px 3px 3px 3px; 97 | -webkit-border-radius: 3px 3px 3px 3px; 98 | border-radius: 3px 3px 3px 3px; 99 | background-position: 15px center; 100 | background-repeat: no-repeat; 101 | -moz-box-shadow: 0 0 12px #999999; 102 | -webkit-box-shadow: 0 0 12px #999999; 103 | box-shadow: 0 0 12px #999999; 104 | color: #FFFFFF; 105 | opacity: 0.8; 106 | } 107 | #toast-container .toast:hover { 108 | -moz-box-shadow: 0 0 12px #000000; 109 | -webkit-box-shadow: 0 0 12px #000000; 110 | box-shadow: 0 0 12px #000000; 111 | opacity: 1; 112 | cursor: pointer; 113 | } 114 | #toast-container .toast.toast-info { 115 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=") !important; 116 | } 117 | #toast-container .toast.toast-error { 118 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=") !important; 119 | } 120 | #toast-container .toast.toast-success { 121 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==") !important; 122 | } 123 | #toast-container .toast.toast-warning { 124 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=") !important; 125 | } 126 | #toast-container.toast-top-center .toast, 127 | #toast-container.toast-bottom-center .toast { 128 | width: 300px; 129 | margin-left: auto; 130 | margin-right: auto; 131 | } 132 | #toast-container.toast-top-full-width .toast, 133 | #toast-container.toast-bottom-full-width .toast { 134 | width: 96%; 135 | margin-left: auto; 136 | margin-right: auto; 137 | } 138 | .toast { 139 | background-color: #030303; 140 | } 141 | .toast-success { 142 | background-color: #51A351; 143 | } 144 | .toast-error { 145 | background-color: #BD362F; 146 | } 147 | .toast-info { 148 | background-color: #2F96B4; 149 | } 150 | .toast-warning { 151 | background-color: #F89406; 152 | } 153 | progress-bar { 154 | position: absolute; 155 | left: 0; 156 | bottom: 0; 157 | height: 4px; 158 | background-color: #000000; 159 | opacity: 0.4; 160 | } 161 | /*Animations*/ 162 | div[toast] { 163 | opacity: 1 !important; 164 | } 165 | div[toast].ng-enter { 166 | opacity: 0 !important; 167 | transition: opacity .3s linear; 168 | } 169 | div[toast].ng-enter.ng-enter-active { 170 | opacity: 1 !important; 171 | } 172 | div[toast].ng-leave { 173 | opacity: 1; 174 | transition: opacity .3s linear; 175 | } 176 | div[toast].ng-leave.ng-leave-active { 177 | opacity: 0 !important; 178 | } 179 | /*Responsive Design*/ 180 | @media all and (max-width: 240px) { 181 | #toast-container .toast.div { 182 | padding: 8px 8px 8px 50px; 183 | width: 11em; 184 | } 185 | #toast-container .toast-close-button { 186 | right: -0.2em; 187 | top: -0.2em; 188 | } 189 | } 190 | @media all and (min-width: 241px) and (max-width: 480px) { 191 | #toast-container .toast.div { 192 | padding: 8px 8px 8px 50px; 193 | width: 18em; 194 | } 195 | #toast-container .toast-close-button { 196 | right: -0.2em; 197 | top: -0.2em; 198 | } 199 | } 200 | @media all and (min-width: 481px) and (max-width: 768px) { 201 | #toast-container .toast.div { 202 | padding: 15px 15px 15px 50px; 203 | width: 25em; 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /dist/angular-toastr.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('toastr', []) 5 | .factory('toastr', toastr); 6 | 7 | toastr.$inject = ['$animate', '$injector', '$rootScope', '$sce', 'toastrConfig', '$q']; 8 | 9 | function toastr($animate, $injector, $rootScope, $sce, toastrConfig, $q) { 10 | var container; 11 | var index = 0; 12 | var toasts = []; 13 | 14 | var previousToastMessage = ''; 15 | var openToasts = {}; 16 | 17 | var containerDefer = $q.defer(); 18 | 19 | var toast = { 20 | active: active, 21 | clear: clear, 22 | error: error, 23 | info: info, 24 | remove: remove, 25 | success: success, 26 | warning: warning, 27 | refreshTimer: refreshTimer 28 | }; 29 | 30 | return toast; 31 | 32 | /* Public API */ 33 | function active() { 34 | return toasts.length; 35 | } 36 | 37 | function clear(toast) { 38 | // Bit of a hack, I will remove this soon with a BC 39 | if (arguments.length === 1 && !toast) { return; } 40 | 41 | if (toast) { 42 | remove(toast.toastId); 43 | } else { 44 | for (var i = 0; i < toasts.length; i++) { 45 | remove(toasts[i].toastId); 46 | } 47 | } 48 | } 49 | 50 | function error(message, title, optionsOverride) { 51 | var type = _getOptions().iconClasses.error; 52 | return _buildNotification(type, message, title, optionsOverride); 53 | } 54 | 55 | function info(message, title, optionsOverride) { 56 | var type = _getOptions().iconClasses.info; 57 | return _buildNotification(type, message, title, optionsOverride); 58 | } 59 | 60 | function success(message, title, optionsOverride) { 61 | var type = _getOptions().iconClasses.success; 62 | return _buildNotification(type, message, title, optionsOverride); 63 | } 64 | 65 | function warning(message, title, optionsOverride) { 66 | var type = _getOptions().iconClasses.warning; 67 | return _buildNotification(type, message, title, optionsOverride); 68 | } 69 | 70 | function refreshTimer(toast, newTime) { 71 | if (toast && toast.isOpened && toasts.indexOf(toast) >= 0) { 72 | toast.scope.refreshTimer(newTime); 73 | } 74 | } 75 | 76 | function remove(toastId, wasClicked) { 77 | var toast = findToast(toastId); 78 | 79 | if (toast && ! toast.deleting) { // Avoid clicking when fading out 80 | toast.deleting = true; 81 | toast.isOpened = false; 82 | $animate.leave(toast.el).then(function() { 83 | if (toast.scope.options.onHidden) { 84 | toast.scope.options.onHidden(!!wasClicked, toast); 85 | } 86 | toast.scope.$destroy(); 87 | var index = toasts.indexOf(toast); 88 | delete openToasts[toast.scope.message]; 89 | toasts.splice(index, 1); 90 | var maxOpened = toastrConfig.maxOpened; 91 | if (maxOpened && toasts.length >= maxOpened) { 92 | toasts[maxOpened - 1].open.resolve(); 93 | } 94 | if (lastToast()) { 95 | container.remove(); 96 | container = null; 97 | containerDefer = $q.defer(); 98 | } 99 | }); 100 | } 101 | 102 | function findToast(toastId) { 103 | for (var i = 0; i < toasts.length; i++) { 104 | if (toasts[i].toastId === toastId) { 105 | return toasts[i]; 106 | } 107 | } 108 | } 109 | 110 | function lastToast() { 111 | return !toasts.length; 112 | } 113 | } 114 | 115 | /* Internal functions */ 116 | function _buildNotification(type, message, title, optionsOverride) { 117 | if (angular.isObject(title)) { 118 | optionsOverride = title; 119 | title = null; 120 | } 121 | 122 | return _notify({ 123 | iconClass: type, 124 | message: message, 125 | optionsOverride: optionsOverride, 126 | title: title 127 | }); 128 | } 129 | 130 | function _getOptions() { 131 | return angular.extend({}, toastrConfig); 132 | } 133 | 134 | function _createOrGetContainer(options) { 135 | if(container) { return containerDefer.promise; } 136 | 137 | container = angular.element('
'); 138 | container.attr('id', options.containerId); 139 | container.addClass(options.positionClass); 140 | container.css({'pointer-events': 'auto'}); 141 | 142 | var target = angular.element(document.querySelector(options.target)); 143 | 144 | if ( ! target || ! target.length) { 145 | throw 'Target for toasts doesn\'t exist'; 146 | } 147 | 148 | $animate.enter(container, target).then(function() { 149 | containerDefer.resolve(); 150 | }); 151 | 152 | return containerDefer.promise; 153 | } 154 | 155 | function _notify(map) { 156 | var options = _getOptions(); 157 | 158 | if (shouldExit()) { return; } 159 | 160 | var newToast = createToast(); 161 | 162 | toasts.push(newToast); 163 | 164 | if (ifMaxOpenedAndAutoDismiss()) { 165 | var oldToasts = toasts.slice(0, (toasts.length - options.maxOpened)); 166 | for (var i = 0, len = oldToasts.length; i < len; i++) { 167 | remove(oldToasts[i].toastId); 168 | } 169 | } 170 | 171 | if (maxOpenedNotReached()) { 172 | newToast.open.resolve(); 173 | } 174 | 175 | newToast.open.promise.then(function() { 176 | _createOrGetContainer(options).then(function() { 177 | newToast.isOpened = true; 178 | if (options.newestOnTop) { 179 | $animate.enter(newToast.el, container).then(function() { 180 | newToast.scope.init(); 181 | }); 182 | } else { 183 | var sibling = container[0].lastChild ? angular.element(container[0].lastChild) : null; 184 | $animate.enter(newToast.el, container, sibling).then(function() { 185 | newToast.scope.init(); 186 | }); 187 | } 188 | }); 189 | }); 190 | 191 | return newToast; 192 | 193 | function ifMaxOpenedAndAutoDismiss() { 194 | return options.autoDismiss && options.maxOpened && toasts.length > options.maxOpened; 195 | } 196 | 197 | function createScope(toast, map, options) { 198 | if (options.allowHtml) { 199 | toast.scope.allowHtml = true; 200 | toast.scope.title = $sce.trustAsHtml(map.title); 201 | toast.scope.message = $sce.trustAsHtml(map.message); 202 | } else { 203 | toast.scope.title = map.title; 204 | toast.scope.message = map.message; 205 | } 206 | 207 | toast.scope.toastType = toast.iconClass; 208 | toast.scope.toastId = toast.toastId; 209 | toast.scope.extraData = options.extraData; 210 | 211 | toast.scope.options = { 212 | extendedTimeOut: options.extendedTimeOut, 213 | messageClass: options.messageClass, 214 | onHidden: options.onHidden, 215 | onShown: generateEvent('onShown'), 216 | onTap: generateEvent('onTap'), 217 | progressBar: options.progressBar, 218 | tapToDismiss: options.tapToDismiss, 219 | timeOut: options.timeOut, 220 | titleClass: options.titleClass, 221 | toastClass: options.toastClass 222 | }; 223 | 224 | if (options.closeButton) { 225 | toast.scope.options.closeHtml = options.closeHtml; 226 | } 227 | 228 | function generateEvent(event) { 229 | if (options[event]) { 230 | return function() { 231 | options[event](toast); 232 | }; 233 | } 234 | } 235 | } 236 | 237 | function createToast() { 238 | var newToast = { 239 | toastId: index++, 240 | isOpened: false, 241 | scope: $rootScope.$new(), 242 | open: $q.defer() 243 | }; 244 | newToast.iconClass = map.iconClass; 245 | if (map.optionsOverride) { 246 | angular.extend(options, cleanOptionsOverride(map.optionsOverride)); 247 | newToast.iconClass = map.optionsOverride.iconClass || newToast.iconClass; 248 | } 249 | 250 | createScope(newToast, map, options); 251 | 252 | newToast.el = createToastEl(newToast.scope); 253 | 254 | return newToast; 255 | 256 | function cleanOptionsOverride(options) { 257 | var badOptions = ['containerId', 'iconClasses', 'maxOpened', 'newestOnTop', 258 | 'positionClass', 'preventDuplicates', 'preventOpenDuplicates', 'templates']; 259 | for (var i = 0, l = badOptions.length; i < l; i++) { 260 | delete options[badOptions[i]]; 261 | } 262 | 263 | return options; 264 | } 265 | } 266 | 267 | function createToastEl(scope) { 268 | var angularDomEl = angular.element('
'), 269 | $compile = $injector.get('$compile'); 270 | return $compile(angularDomEl)(scope); 271 | } 272 | 273 | function maxOpenedNotReached() { 274 | return options.maxOpened && toasts.length <= options.maxOpened || !options.maxOpened; 275 | } 276 | 277 | function shouldExit() { 278 | var isDuplicateOfLast = options.preventDuplicates && map.message === previousToastMessage; 279 | var isDuplicateOpen = options.preventOpenDuplicates && openToasts[map.message]; 280 | 281 | if (isDuplicateOfLast || isDuplicateOpen) { 282 | return true; 283 | } 284 | 285 | previousToastMessage = map.message; 286 | openToasts[map.message] = true; 287 | 288 | return false; 289 | } 290 | } 291 | } 292 | }()); 293 | 294 | (function() { 295 | 'use strict'; 296 | 297 | angular.module('toastr') 298 | .constant('toastrConfig', { 299 | allowHtml: false, 300 | autoDismiss: false, 301 | closeButton: false, 302 | closeHtml: '', 303 | containerId: 'toast-container', 304 | extendedTimeOut: 1000, 305 | iconClasses: { 306 | error: 'toast-error', 307 | info: 'toast-info', 308 | success: 'toast-success', 309 | warning: 'toast-warning' 310 | }, 311 | maxOpened: 0, 312 | messageClass: 'toast-message', 313 | newestOnTop: true, 314 | onHidden: null, 315 | onShown: null, 316 | onTap: null, 317 | positionClass: 'toast-top-right', 318 | preventDuplicates: false, 319 | preventOpenDuplicates: false, 320 | progressBar: false, 321 | tapToDismiss: true, 322 | target: 'body', 323 | templates: { 324 | toast: 'directives/toast/toast.html', 325 | progressbar: 'directives/progressbar/progressbar.html' 326 | }, 327 | timeOut: 5000, 328 | titleClass: 'toast-title', 329 | toastClass: 'toast' 330 | }); 331 | }()); 332 | 333 | (function() { 334 | 'use strict'; 335 | 336 | angular.module('toastr') 337 | .directive('progressBar', progressBar); 338 | 339 | progressBar.$inject = ['toastrConfig']; 340 | 341 | function progressBar(toastrConfig) { 342 | return { 343 | require: '^toast', 344 | templateUrl: function() { 345 | return toastrConfig.templates.progressbar; 346 | }, 347 | link: linkFunction 348 | }; 349 | 350 | function linkFunction(scope, element, attrs, toastCtrl) { 351 | var intervalId, currentTimeOut, hideTime; 352 | 353 | toastCtrl.progressBar = scope; 354 | 355 | scope.start = function(duration) { 356 | if (intervalId) { 357 | clearInterval(intervalId); 358 | } 359 | 360 | currentTimeOut = parseFloat(duration); 361 | hideTime = new Date().getTime() + currentTimeOut; 362 | intervalId = setInterval(updateProgress, 10); 363 | }; 364 | 365 | scope.stop = function() { 366 | if (intervalId) { 367 | clearInterval(intervalId); 368 | } 369 | }; 370 | 371 | function updateProgress() { 372 | var percentage = ((hideTime - (new Date().getTime())) / currentTimeOut) * 100; 373 | element.css('width', percentage + '%'); 374 | } 375 | 376 | scope.$on('$destroy', function() { 377 | // Failsafe stop 378 | clearInterval(intervalId); 379 | }); 380 | } 381 | } 382 | }()); 383 | 384 | (function() { 385 | 'use strict'; 386 | 387 | angular.module('toastr') 388 | .controller('ToastController', ToastController); 389 | 390 | function ToastController() { 391 | this.progressBar = null; 392 | 393 | this.startProgressBar = function(duration) { 394 | if (this.progressBar) { 395 | this.progressBar.start(duration); 396 | } 397 | }; 398 | 399 | this.stopProgressBar = function() { 400 | if (this.progressBar) { 401 | this.progressBar.stop(); 402 | } 403 | }; 404 | } 405 | }()); 406 | 407 | (function() { 408 | 'use strict'; 409 | 410 | angular.module('toastr') 411 | .directive('toast', toast); 412 | 413 | toast.$inject = ['$injector', '$interval', 'toastrConfig', 'toastr']; 414 | 415 | function toast($injector, $interval, toastrConfig, toastr) { 416 | return { 417 | templateUrl: function() { 418 | return toastrConfig.templates.toast; 419 | }, 420 | controller: 'ToastController', 421 | link: toastLinkFunction 422 | }; 423 | 424 | function toastLinkFunction(scope, element, attrs, toastCtrl) { 425 | var timeout; 426 | 427 | scope.toastClass = scope.options.toastClass; 428 | scope.titleClass = scope.options.titleClass; 429 | scope.messageClass = scope.options.messageClass; 430 | scope.progressBar = scope.options.progressBar; 431 | 432 | if (wantsCloseButton()) { 433 | var button = angular.element(scope.options.closeHtml), 434 | $compile = $injector.get('$compile'); 435 | button.addClass('toast-close-button'); 436 | button.attr('ng-click', 'close(true, $event)'); 437 | $compile(button)(scope); 438 | element.children().prepend(button); 439 | } 440 | 441 | scope.init = function() { 442 | if (scope.options.timeOut) { 443 | timeout = createTimeout(scope.options.timeOut); 444 | } 445 | if (scope.options.onShown) { 446 | scope.options.onShown(); 447 | } 448 | }; 449 | 450 | element.on('mouseenter', function() { 451 | hideAndStopProgressBar(); 452 | if (timeout) { 453 | $interval.cancel(timeout); 454 | } 455 | }); 456 | 457 | scope.tapToast = function () { 458 | if (angular.isFunction(scope.options.onTap)) { 459 | scope.options.onTap(); 460 | } 461 | if (scope.options.tapToDismiss) { 462 | scope.close(true); 463 | } 464 | }; 465 | 466 | scope.close = function (wasClicked, $event) { 467 | if ($event && angular.isFunction($event.stopPropagation)) { 468 | $event.stopPropagation(); 469 | } 470 | toastr.remove(scope.toastId, wasClicked); 471 | }; 472 | 473 | scope.refreshTimer = function(newTime) { 474 | if (timeout) { 475 | $interval.cancel(timeout); 476 | timeout = createTimeout(newTime || scope.options.timeOut); 477 | } 478 | }; 479 | 480 | element.on('mouseleave', function() { 481 | if (scope.options.timeOut === 0 && scope.options.extendedTimeOut === 0) { return; } 482 | scope.$apply(function() { 483 | scope.progressBar = scope.options.progressBar; 484 | }); 485 | timeout = createTimeout(scope.options.extendedTimeOut); 486 | }); 487 | 488 | function createTimeout(time) { 489 | toastCtrl.startProgressBar(time); 490 | return $interval(function() { 491 | toastCtrl.stopProgressBar(); 492 | toastr.remove(scope.toastId); 493 | }, time, 1); 494 | } 495 | 496 | function hideAndStopProgressBar() { 497 | scope.progressBar = false; 498 | toastCtrl.stopProgressBar(); 499 | } 500 | 501 | function wantsCloseButton() { 502 | return scope.options.closeHtml; 503 | } 504 | } 505 | } 506 | }()); 507 | -------------------------------------------------------------------------------- /dist/angular-toastr.min.css: -------------------------------------------------------------------------------- 1 | .toast-title{font-weight:700}.toast-message{word-wrap:break-word}.toast-message a,.toast-message label{color:#fff}.toast-message a:hover{color:#ccc;text-decoration:none}.toast-close-button{position:relative;right:-.3em;top:-.3em;float:right;font-size:20px;font-weight:700;color:#fff;-webkit-text-shadow:0 1px 0 #fff;text-shadow:0 1px 0 #fff;opacity:.8}.toast-close-button:focus,.toast-close-button:hover{color:#000;text-decoration:none;cursor:pointer;opacity:.4}button.toast-close-button{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.toast-top-center{top:0;right:0;width:100%}.toast-bottom-center{bottom:0;right:0;width:100%}.toast-top-full-width{top:0;right:0;width:100%}.toast-bottom-full-width{bottom:0;right:0;width:100%}.toast-top-left{top:12px;left:12px}.toast-top-right{top:12px;right:12px}.toast-bottom-right{right:12px;bottom:12px}.toast-bottom-left{bottom:12px;left:12px}#toast-container{position:fixed;z-index:999999}#toast-container *{box-sizing:border-box}#toast-container .toast{position:relative;overflow:hidden;margin:0 0 6px;padding:15px 15px 15px 50px;width:300px;border-radius:3px 3px 3px 3px;background-position:15px;background-repeat:no-repeat;box-shadow:0 0 12px #999;color:#fff;opacity:.8}#toast-container .toast:hover{box-shadow:0 0 12px #000;opacity:1;cursor:pointer}#toast-container .toast.toast-info{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=")!important}#toast-container .toast.toast-error{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=")!important}#toast-container .toast.toast-success{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==")!important}#toast-container .toast.toast-warning{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=")!important}#toast-container.toast-bottom-center .toast,#toast-container.toast-top-center .toast{width:300px;margin-left:auto;margin-right:auto}#toast-container.toast-bottom-full-width .toast,#toast-container.toast-top-full-width .toast{width:96%;margin-left:auto;margin-right:auto}.toast{background-color:#030303}.toast-success{background-color:#51a351}.toast-error{background-color:#bd362f}.toast-info{background-color:#2f96b4}.toast-warning{background-color:#f89406}progress-bar{position:absolute;left:0;bottom:0;height:4px;background-color:#000;opacity:.4}div[toast]{opacity:1!important}div[toast].ng-enter{opacity:0!important;transition:opacity .3s linear}div[toast].ng-enter.ng-enter-active{opacity:1!important}div[toast].ng-leave{opacity:1;transition:opacity .3s linear}div[toast].ng-leave.ng-leave-active{opacity:0!important}@media all and (max-width:240px){#toast-container .toast.div{padding:8px 8px 8px 50px;width:11em}#toast-container .toast-close-button{right:-.2em;top:-.2em}}@media all and (min-width:241px) and (max-width:480px){#toast-container .toast.div{padding:8px 8px 8px 50px;width:18em}#toast-container .toast-close-button{right:-.2em;top:-.2em}}@media all and (min-width:481px) and (max-width:768px){#toast-container .toast.div{padding:15px 15px 15px 50px;width:25em}} -------------------------------------------------------------------------------- /dist/angular-toastr.min.js: -------------------------------------------------------------------------------- 1 | !function(){"use strict";function t(t,e,n,s,o,r,a){function i(){return B.length}function l(t){if(1!==arguments.length||t)if(t)d(t.toastId);else for(var e=0;e=0&&t.scope.refreshTimer(e)}function d(e,n){function s(t){for(var e=0;e=e&&B[e-1].open.resolve(),o()&&(O.remove(),O=null,$=a.defer())}))}function g(t,e,n,s){return angular.isObject(n)&&(s=n,n=null),C({iconClass:t,message:e,optionsOverride:s,title:n})}function v(){return angular.extend({},r)}function h(e){if(O)return $.promise;O=angular.element("
"),O.attr("id",e.containerId),O.addClass(e.positionClass),O.css({"pointer-events":"auto"});var n=angular.element(document.querySelector(e.target));if(!n||!n.length)throw"Target for toasts doesn't exist";return t.enter(O,n).then(function(){$.resolve()}),$.promise}function C(n){function r(){return f.autoDismiss&&f.maxOpened&&B.length>f.maxOpened}function i(t,e,n){function s(e){if(n[e])return function(){n[e](t)}}n.allowHtml?(t.scope.allowHtml=!0,t.scope.title=o.trustAsHtml(e.title),t.scope.message=o.trustAsHtml(e.message)):(t.scope.title=e.title,t.scope.message=e.message),t.scope.toastType=t.iconClass,t.scope.toastId=t.toastId,t.scope.extraData=n.extraData,t.scope.options={extendedTimeOut:n.extendedTimeOut,messageClass:n.messageClass,onHidden:n.onHidden,onShown:s("onShown"),onTap:s("onTap"),progressBar:n.progressBar,tapToDismiss:n.tapToDismiss,timeOut:n.timeOut,titleClass:n.titleClass,toastClass:n.toastClass},n.closeButton&&(t.scope.options.closeHtml=n.closeHtml)}function l(){function t(t){for(var e=["containerId","iconClasses","maxOpened","newestOnTop","positionClass","preventDuplicates","preventOpenDuplicates","templates"],n=0,s=e.length;n"),s=e.get("$compile");return s(n)(t)}function u(){return f.maxOpened&&B.length<=f.maxOpened||!f.maxOpened}function p(){var t=f.preventDuplicates&&n.message===x,e=f.preventOpenDuplicates&&w[n.message];return!(!t&&!e)||(x=n.message,w[n.message]=!0,!1)}var f=v();if(!p()){var m=l();if(B.push(m),r())for(var g=B.slice(0,B.length-f.maxOpened),C=0,$=g.length;C<$;C++)d(g[C].toastId);return u()&&m.open.resolve(),m.open.promise.then(function(){h(f).then(function(){if(m.isOpened=!0,f.newestOnTop)t.enter(m.el,O).then(function(){m.scope.init()});else{var e=O[0].lastChild?angular.element(O[0].lastChild):null;t.enter(m.el,O,e).then(function(){m.scope.init()})}})}),m}}var O,T=0,B=[],x="",w={},$=a.defer(),D={active:i,clear:l,error:c,info:u,remove:d,success:p,warning:f,refreshTimer:m};return D}angular.module("toastr",[]).factory("toastr",t),t.$inject=["$animate","$injector","$document","$rootScope","$sce","toastrConfig","$q"]}(),function(){"use strict";angular.module("toastr").constant("toastrConfig",{allowHtml:!1,autoDismiss:!1,closeButton:!1,closeHtml:"",containerId:"toast-container",extendedTimeOut:1e3,iconClasses:{error:"toast-error",info:"toast-info",success:"toast-success",warning:"toast-warning"},maxOpened:0,messageClass:"toast-message",newestOnTop:!0,onHidden:null,onShown:null,onTap:null,positionClass:"toast-top-right",preventDuplicates:!1,preventOpenDuplicates:!1,progressBar:!1,tapToDismiss:!0,target:"body",templates:{toast:"directives/toast/toast.html",progressbar:"directives/progressbar/progressbar.html"},timeOut:5e3,titleClass:"toast-title",toastClass:"toast"})}(),function(){"use strict";function t(t){function e(t,e,n,s){function o(){var t=(i-(new Date).getTime())/a*100;e.css("width",t+"%")}var r,a,i;s.progressBar=t,t.start=function(t){r&&clearInterval(r),a=parseFloat(t),i=(new Date).getTime()+a,r=setInterval(o,10)},t.stop=function(){r&&clearInterval(r)},t.$on("$destroy",function(){clearInterval(r)})}return{require:"^toast",templateUrl:function(){return t.templates.progressbar},link:e}}angular.module("toastr").directive("progressBar",t),t.$inject=["toastrConfig"]}(),function(){"use strict";function t(){this.progressBar=null,this.startProgressBar=function(t){this.progressBar&&this.progressBar.start(t)},this.stopProgressBar=function(){this.progressBar&&this.progressBar.stop()}}angular.module("toastr").controller("ToastController",t)}(),function(){"use strict";function t(t,e,n,s){function o(n,o,r,a){function i(t){return a.startProgressBar(t),e(function(){a.stopProgressBar(),s.remove(n.toastId)},t,1)}function l(){n.progressBar=!1,a.stopProgressBar()}function c(){return n.options.closeHtml}var u;if(n.toastClass=n.options.toastClass,n.titleClass=n.options.titleClass,n.messageClass=n.options.messageClass,n.progressBar=n.options.progressBar,c()){var p=angular.element(n.options.closeHtml),f=t.get("$compile");p.addClass("toast-close-button"),p.attr("ng-click","close(true, $event)"),f(p)(n),o.children().prepend(p)}n.init=function(){n.options.timeOut&&(u=i(n.options.timeOut)),n.options.onShown&&n.options.onShown()},o.on("mouseenter",function(){l(),u&&e.cancel(u)}),n.tapToast=function(){angular.isFunction(n.options.onTap)&&n.options.onTap(),n.options.tapToDismiss&&n.close(!0)},n.close=function(t,e){e&&angular.isFunction(e.stopPropagation)&&e.stopPropagation(),s.remove(n.toastId,t)},n.refreshTimer=function(t){u&&(e.cancel(u),u=i(t||n.options.timeOut))},o.on("mouseleave",function(){0===n.options.timeOut&&0===n.options.extendedTimeOut||(n.$apply(function(){n.progressBar=n.options.progressBar}),u=i(n.options.extendedTimeOut))})}return{templateUrl:function(){return n.templates.toast},controller:"ToastController",link:o}}angular.module("toastr").directive("toast",t),t.$inject=["$injector","$interval","toastrConfig","toastr"]}(); -------------------------------------------------------------------------------- /dist/angular-toastr.tpls.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('toastr', []) 5 | .factory('toastr', toastr); 6 | 7 | toastr.$inject = ['$animate', '$injector', '$rootScope', '$sce', 'toastrConfig', '$q']; 8 | 9 | function toastr($animate, $injector, $rootScope, $sce, toastrConfig, $q) { 10 | var container; 11 | var index = 0; 12 | var toasts = []; 13 | 14 | var previousToastMessage = ''; 15 | var openToasts = {}; 16 | 17 | var containerDefer = $q.defer(); 18 | 19 | var toast = { 20 | active: active, 21 | clear: clear, 22 | error: error, 23 | info: info, 24 | remove: remove, 25 | success: success, 26 | warning: warning, 27 | refreshTimer: refreshTimer 28 | }; 29 | 30 | return toast; 31 | 32 | /* Public API */ 33 | function active() { 34 | return toasts.length; 35 | } 36 | 37 | function clear(toast) { 38 | // Bit of a hack, I will remove this soon with a BC 39 | if (arguments.length === 1 && !toast) { return; } 40 | 41 | if (toast) { 42 | remove(toast.toastId); 43 | } else { 44 | for (var i = 0; i < toasts.length; i++) { 45 | remove(toasts[i].toastId); 46 | } 47 | } 48 | } 49 | 50 | function error(message, title, optionsOverride) { 51 | var type = _getOptions().iconClasses.error; 52 | return _buildNotification(type, message, title, optionsOverride); 53 | } 54 | 55 | function info(message, title, optionsOverride) { 56 | var type = _getOptions().iconClasses.info; 57 | return _buildNotification(type, message, title, optionsOverride); 58 | } 59 | 60 | function success(message, title, optionsOverride) { 61 | var type = _getOptions().iconClasses.success; 62 | return _buildNotification(type, message, title, optionsOverride); 63 | } 64 | 65 | function warning(message, title, optionsOverride) { 66 | var type = _getOptions().iconClasses.warning; 67 | return _buildNotification(type, message, title, optionsOverride); 68 | } 69 | 70 | function refreshTimer(toast, newTime) { 71 | if (toast && toast.isOpened && toasts.indexOf(toast) >= 0) { 72 | toast.scope.refreshTimer(newTime); 73 | } 74 | } 75 | 76 | function remove(toastId, wasClicked) { 77 | var toast = findToast(toastId); 78 | 79 | if (toast && ! toast.deleting) { // Avoid clicking when fading out 80 | toast.deleting = true; 81 | toast.isOpened = false; 82 | $animate.leave(toast.el).then(function() { 83 | if (toast.scope.options.onHidden) { 84 | toast.scope.options.onHidden(!!wasClicked, toast); 85 | } 86 | toast.scope.$destroy(); 87 | var index = toasts.indexOf(toast); 88 | delete openToasts[toast.scope.message]; 89 | toasts.splice(index, 1); 90 | var maxOpened = toastrConfig.maxOpened; 91 | if (maxOpened && toasts.length >= maxOpened) { 92 | toasts[maxOpened - 1].open.resolve(); 93 | } 94 | if (lastToast()) { 95 | container.remove(); 96 | container = null; 97 | containerDefer = $q.defer(); 98 | } 99 | }); 100 | } 101 | 102 | function findToast(toastId) { 103 | for (var i = 0; i < toasts.length; i++) { 104 | if (toasts[i].toastId === toastId) { 105 | return toasts[i]; 106 | } 107 | } 108 | } 109 | 110 | function lastToast() { 111 | return !toasts.length; 112 | } 113 | } 114 | 115 | /* Internal functions */ 116 | function _buildNotification(type, message, title, optionsOverride) { 117 | if (angular.isObject(title)) { 118 | optionsOverride = title; 119 | title = null; 120 | } 121 | 122 | return _notify({ 123 | iconClass: type, 124 | message: message, 125 | optionsOverride: optionsOverride, 126 | title: title 127 | }); 128 | } 129 | 130 | function _getOptions() { 131 | return angular.extend({}, toastrConfig); 132 | } 133 | 134 | function _createOrGetContainer(options) { 135 | if(container) { return containerDefer.promise; } 136 | 137 | container = angular.element('
'); 138 | container.attr('id', options.containerId); 139 | container.addClass(options.positionClass); 140 | container.css({'pointer-events': 'auto'}); 141 | 142 | var target = angular.element(document.querySelector(options.target)); 143 | 144 | if ( ! target || ! target.length) { 145 | throw 'Target for toasts doesn\'t exist'; 146 | } 147 | 148 | $animate.enter(container, target).then(function() { 149 | containerDefer.resolve(); 150 | }); 151 | 152 | return containerDefer.promise; 153 | } 154 | 155 | function _notify(map) { 156 | var options = _getOptions(); 157 | 158 | if (shouldExit()) { return; } 159 | 160 | var newToast = createToast(); 161 | 162 | toasts.push(newToast); 163 | 164 | if (ifMaxOpenedAndAutoDismiss()) { 165 | var oldToasts = toasts.slice(0, (toasts.length - options.maxOpened)); 166 | for (var i = 0, len = oldToasts.length; i < len; i++) { 167 | remove(oldToasts[i].toastId); 168 | } 169 | } 170 | 171 | if (maxOpenedNotReached()) { 172 | newToast.open.resolve(); 173 | } 174 | 175 | newToast.open.promise.then(function() { 176 | _createOrGetContainer(options).then(function() { 177 | newToast.isOpened = true; 178 | if (options.newestOnTop) { 179 | $animate.enter(newToast.el, container).then(function() { 180 | newToast.scope.init(); 181 | }); 182 | } else { 183 | var sibling = container[0].lastChild ? angular.element(container[0].lastChild) : null; 184 | $animate.enter(newToast.el, container, sibling).then(function() { 185 | newToast.scope.init(); 186 | }); 187 | } 188 | }); 189 | }); 190 | 191 | return newToast; 192 | 193 | function ifMaxOpenedAndAutoDismiss() { 194 | return options.autoDismiss && options.maxOpened && toasts.length > options.maxOpened; 195 | } 196 | 197 | function createScope(toast, map, options) { 198 | if (options.allowHtml) { 199 | toast.scope.allowHtml = true; 200 | toast.scope.title = $sce.trustAsHtml(map.title); 201 | toast.scope.message = $sce.trustAsHtml(map.message); 202 | } else { 203 | toast.scope.title = map.title; 204 | toast.scope.message = map.message; 205 | } 206 | 207 | toast.scope.toastType = toast.iconClass; 208 | toast.scope.toastId = toast.toastId; 209 | toast.scope.extraData = options.extraData; 210 | 211 | toast.scope.options = { 212 | extendedTimeOut: options.extendedTimeOut, 213 | messageClass: options.messageClass, 214 | onHidden: options.onHidden, 215 | onShown: generateEvent('onShown'), 216 | onTap: generateEvent('onTap'), 217 | progressBar: options.progressBar, 218 | tapToDismiss: options.tapToDismiss, 219 | timeOut: options.timeOut, 220 | titleClass: options.titleClass, 221 | toastClass: options.toastClass 222 | }; 223 | 224 | if (options.closeButton) { 225 | toast.scope.options.closeHtml = options.closeHtml; 226 | } 227 | 228 | function generateEvent(event) { 229 | if (options[event]) { 230 | return function() { 231 | options[event](toast); 232 | }; 233 | } 234 | } 235 | } 236 | 237 | function createToast() { 238 | var newToast = { 239 | toastId: index++, 240 | isOpened: false, 241 | scope: $rootScope.$new(), 242 | open: $q.defer() 243 | }; 244 | newToast.iconClass = map.iconClass; 245 | if (map.optionsOverride) { 246 | angular.extend(options, cleanOptionsOverride(map.optionsOverride)); 247 | newToast.iconClass = map.optionsOverride.iconClass || newToast.iconClass; 248 | } 249 | 250 | createScope(newToast, map, options); 251 | 252 | newToast.el = createToastEl(newToast.scope); 253 | 254 | return newToast; 255 | 256 | function cleanOptionsOverride(options) { 257 | var badOptions = ['containerId', 'iconClasses', 'maxOpened', 'newestOnTop', 258 | 'positionClass', 'preventDuplicates', 'preventOpenDuplicates', 'templates']; 259 | for (var i = 0, l = badOptions.length; i < l; i++) { 260 | delete options[badOptions[i]]; 261 | } 262 | 263 | return options; 264 | } 265 | } 266 | 267 | function createToastEl(scope) { 268 | var angularDomEl = angular.element('
'), 269 | $compile = $injector.get('$compile'); 270 | return $compile(angularDomEl)(scope); 271 | } 272 | 273 | function maxOpenedNotReached() { 274 | return options.maxOpened && toasts.length <= options.maxOpened || !options.maxOpened; 275 | } 276 | 277 | function shouldExit() { 278 | var isDuplicateOfLast = options.preventDuplicates && map.message === previousToastMessage; 279 | var isDuplicateOpen = options.preventOpenDuplicates && openToasts[map.message]; 280 | 281 | if (isDuplicateOfLast || isDuplicateOpen) { 282 | return true; 283 | } 284 | 285 | previousToastMessage = map.message; 286 | openToasts[map.message] = true; 287 | 288 | return false; 289 | } 290 | } 291 | } 292 | }()); 293 | 294 | (function() { 295 | 'use strict'; 296 | 297 | angular.module('toastr') 298 | .constant('toastrConfig', { 299 | allowHtml: false, 300 | autoDismiss: false, 301 | closeButton: false, 302 | closeHtml: '', 303 | containerId: 'toast-container', 304 | extendedTimeOut: 1000, 305 | iconClasses: { 306 | error: 'toast-error', 307 | info: 'toast-info', 308 | success: 'toast-success', 309 | warning: 'toast-warning' 310 | }, 311 | maxOpened: 0, 312 | messageClass: 'toast-message', 313 | newestOnTop: true, 314 | onHidden: null, 315 | onShown: null, 316 | onTap: null, 317 | positionClass: 'toast-top-right', 318 | preventDuplicates: false, 319 | preventOpenDuplicates: false, 320 | progressBar: false, 321 | tapToDismiss: true, 322 | target: 'body', 323 | templates: { 324 | toast: 'directives/toast/toast.html', 325 | progressbar: 'directives/progressbar/progressbar.html' 326 | }, 327 | timeOut: 5000, 328 | titleClass: 'toast-title', 329 | toastClass: 'toast' 330 | }); 331 | }()); 332 | 333 | (function() { 334 | 'use strict'; 335 | 336 | angular.module('toastr') 337 | .directive('progressBar', progressBar); 338 | 339 | progressBar.$inject = ['toastrConfig']; 340 | 341 | function progressBar(toastrConfig) { 342 | return { 343 | require: '^toast', 344 | templateUrl: function() { 345 | return toastrConfig.templates.progressbar; 346 | }, 347 | link: linkFunction 348 | }; 349 | 350 | function linkFunction(scope, element, attrs, toastCtrl) { 351 | var intervalId, currentTimeOut, hideTime; 352 | 353 | toastCtrl.progressBar = scope; 354 | 355 | scope.start = function(duration) { 356 | if (intervalId) { 357 | clearInterval(intervalId); 358 | } 359 | 360 | currentTimeOut = parseFloat(duration); 361 | hideTime = new Date().getTime() + currentTimeOut; 362 | intervalId = setInterval(updateProgress, 10); 363 | }; 364 | 365 | scope.stop = function() { 366 | if (intervalId) { 367 | clearInterval(intervalId); 368 | } 369 | }; 370 | 371 | function updateProgress() { 372 | var percentage = ((hideTime - (new Date().getTime())) / currentTimeOut) * 100; 373 | element.css('width', percentage + '%'); 374 | } 375 | 376 | scope.$on('$destroy', function() { 377 | // Failsafe stop 378 | clearInterval(intervalId); 379 | }); 380 | } 381 | } 382 | }()); 383 | 384 | (function() { 385 | 'use strict'; 386 | 387 | angular.module('toastr') 388 | .controller('ToastController', ToastController); 389 | 390 | function ToastController() { 391 | this.progressBar = null; 392 | 393 | this.startProgressBar = function(duration) { 394 | if (this.progressBar) { 395 | this.progressBar.start(duration); 396 | } 397 | }; 398 | 399 | this.stopProgressBar = function() { 400 | if (this.progressBar) { 401 | this.progressBar.stop(); 402 | } 403 | }; 404 | } 405 | }()); 406 | 407 | (function() { 408 | 'use strict'; 409 | 410 | angular.module('toastr') 411 | .directive('toast', toast); 412 | 413 | toast.$inject = ['$injector', '$interval', 'toastrConfig', 'toastr']; 414 | 415 | function toast($injector, $interval, toastrConfig, toastr) { 416 | return { 417 | templateUrl: function() { 418 | return toastrConfig.templates.toast; 419 | }, 420 | controller: 'ToastController', 421 | link: toastLinkFunction 422 | }; 423 | 424 | function toastLinkFunction(scope, element, attrs, toastCtrl) { 425 | var timeout; 426 | 427 | scope.toastClass = scope.options.toastClass; 428 | scope.titleClass = scope.options.titleClass; 429 | scope.messageClass = scope.options.messageClass; 430 | scope.progressBar = scope.options.progressBar; 431 | 432 | if (wantsCloseButton()) { 433 | var button = angular.element(scope.options.closeHtml), 434 | $compile = $injector.get('$compile'); 435 | button.addClass('toast-close-button'); 436 | button.attr('ng-click', 'close(true, $event)'); 437 | $compile(button)(scope); 438 | element.children().prepend(button); 439 | } 440 | 441 | scope.init = function() { 442 | if (scope.options.timeOut) { 443 | timeout = createTimeout(scope.options.timeOut); 444 | } 445 | if (scope.options.onShown) { 446 | scope.options.onShown(); 447 | } 448 | }; 449 | 450 | element.on('mouseenter', function() { 451 | hideAndStopProgressBar(); 452 | if (timeout) { 453 | $interval.cancel(timeout); 454 | } 455 | }); 456 | 457 | scope.tapToast = function () { 458 | if (angular.isFunction(scope.options.onTap)) { 459 | scope.options.onTap(); 460 | } 461 | if (scope.options.tapToDismiss) { 462 | scope.close(true); 463 | } 464 | }; 465 | 466 | scope.close = function (wasClicked, $event) { 467 | if ($event && angular.isFunction($event.stopPropagation)) { 468 | $event.stopPropagation(); 469 | } 470 | toastr.remove(scope.toastId, wasClicked); 471 | }; 472 | 473 | scope.refreshTimer = function(newTime) { 474 | if (timeout) { 475 | $interval.cancel(timeout); 476 | timeout = createTimeout(newTime || scope.options.timeOut); 477 | } 478 | }; 479 | 480 | element.on('mouseleave', function() { 481 | if (scope.options.timeOut === 0 && scope.options.extendedTimeOut === 0) { return; } 482 | scope.$apply(function() { 483 | scope.progressBar = scope.options.progressBar; 484 | }); 485 | timeout = createTimeout(scope.options.extendedTimeOut); 486 | }); 487 | 488 | function createTimeout(time) { 489 | toastCtrl.startProgressBar(time); 490 | return $interval(function() { 491 | toastCtrl.stopProgressBar(); 492 | toastr.remove(scope.toastId); 493 | }, time, 1); 494 | } 495 | 496 | function hideAndStopProgressBar() { 497 | scope.progressBar = false; 498 | toastCtrl.stopProgressBar(); 499 | } 500 | 501 | function wantsCloseButton() { 502 | return scope.options.closeHtml; 503 | } 504 | } 505 | } 506 | }()); 507 | 508 | angular.module("toastr").run(["$templateCache", function($templateCache) {$templateCache.put("directives/progressbar/progressbar.html","
\n"); 509 | $templateCache.put("directives/toast/toast.html","
\n
\n
{{title}}
\n
{{message}}
\n
\n
\n
\n \n
\n");}]); -------------------------------------------------------------------------------- /dist/angular-toastr.tpls.min.js: -------------------------------------------------------------------------------- 1 | !function(){"use strict";function t(t,e,s,n,o,r,a){function i(){return w.length}function l(t){if(1!==arguments.length||t)if(t)m(t.toastId);else for(var e=0;e=0&&t.scope.refreshTimer(e)}function m(e,s){function n(t){for(var e=0;e=e&&w[e-1].open.resolve(),o()&&(O.remove(),O=null,$=a.defer())}))}function f(t,e,s,n){return angular.isObject(s)&&(n=s,s=null),C({iconClass:t,message:e,optionsOverride:n,title:s})}function v(){return angular.extend({},r)}function h(e){if(O)return $.promise;O=angular.element("
"),O.attr("id",e.containerId),O.addClass(e.positionClass),O.css({"pointer-events":"auto"});var s=angular.element(document.querySelector(e.target));if(!s||!s.length)throw"Target for toasts doesn't exist";return t.enter(O,s).then(function(){$.resolve()}),$.promise}function C(s){function r(){return g.autoDismiss&&g.maxOpened&&w.length>g.maxOpened}function i(t,e,s){function n(e){if(s[e])return function(){s[e](t)}}s.allowHtml?(t.scope.allowHtml=!0,t.scope.title=o.trustAsHtml(e.title),t.scope.message=o.trustAsHtml(e.message)):(t.scope.title=e.title,t.scope.message=e.message),t.scope.toastType=t.iconClass,t.scope.toastId=t.toastId,t.scope.extraData=s.extraData,t.scope.options={extendedTimeOut:s.extendedTimeOut,messageClass:s.messageClass,onHidden:s.onHidden,onShown:n("onShown"),onTap:n("onTap"),progressBar:s.progressBar,tapToDismiss:s.tapToDismiss,timeOut:s.timeOut,titleClass:s.titleClass,toastClass:s.toastClass},s.closeButton&&(t.scope.options.closeHtml=s.closeHtml)}function l(){function t(t){for(var e=["containerId","iconClasses","maxOpened","newestOnTop","positionClass","preventDuplicates","preventOpenDuplicates","templates"],s=0,n=e.length;s"),n=e.get("$compile");return n(s)(t)}function u(){return g.maxOpened&&w.length<=g.maxOpened||!g.maxOpened}function p(){var t=g.preventDuplicates&&s.message===B,e=g.preventOpenDuplicates&&x[s.message];return!(!t&&!e)||(B=s.message,x[s.message]=!0,!1)}var g=v();if(!p()){var d=l();if(w.push(d),r())for(var f=w.slice(0,w.length-g.maxOpened),C=0,$=f.length;C<$;C++)m(f[C].toastId);return u()&&d.open.resolve(),d.open.promise.then(function(){h(g).then(function(){if(d.isOpened=!0,g.newestOnTop)t.enter(d.el,O).then(function(){d.scope.init()});else{var e=O[0].lastChild?angular.element(O[0].lastChild):null;t.enter(d.el,O,e).then(function(){d.scope.init()})}})}),d}}var O,T=0,w=[],B="",x={},$=a.defer(),b={active:i,clear:l,error:c,info:u,remove:m,success:p,warning:g,refreshTimer:d};return b}angular.module("toastr",[]).factory("toastr",t),t.$inject=["$animate","$injector","$document","$rootScope","$sce","toastrConfig","$q"]}(),function(){"use strict";angular.module("toastr").constant("toastrConfig",{allowHtml:!1,autoDismiss:!1,closeButton:!1,closeHtml:"",containerId:"toast-container",extendedTimeOut:1e3,iconClasses:{error:"toast-error",info:"toast-info",success:"toast-success",warning:"toast-warning"},maxOpened:0,messageClass:"toast-message",newestOnTop:!0,onHidden:null,onShown:null,onTap:null,positionClass:"toast-top-right",preventDuplicates:!1,preventOpenDuplicates:!1,progressBar:!1,tapToDismiss:!0,target:"body",templates:{toast:"directives/toast/toast.html",progressbar:"directives/progressbar/progressbar.html"},timeOut:5e3,titleClass:"toast-title",toastClass:"toast"})}(),function(){"use strict";function t(t){function e(t,e,s,n){function o(){var t=(i-(new Date).getTime())/a*100;e.css("width",t+"%")}var r,a,i;n.progressBar=t,t.start=function(t){r&&clearInterval(r),a=parseFloat(t),i=(new Date).getTime()+a,r=setInterval(o,10)},t.stop=function(){r&&clearInterval(r)},t.$on("$destroy",function(){clearInterval(r)})}return{require:"^toast",templateUrl:function(){return t.templates.progressbar},link:e}}angular.module("toastr").directive("progressBar",t),t.$inject=["toastrConfig"]}(),function(){"use strict";function t(){this.progressBar=null,this.startProgressBar=function(t){this.progressBar&&this.progressBar.start(t)},this.stopProgressBar=function(){this.progressBar&&this.progressBar.stop()}}angular.module("toastr").controller("ToastController",t)}(),function(){"use strict";function t(t,e,s,n){function o(s,o,r,a){function i(t){return a.startProgressBar(t),e(function(){a.stopProgressBar(),n.remove(s.toastId)},t,1)}function l(){s.progressBar=!1,a.stopProgressBar()}function c(){return s.options.closeHtml}var u;if(s.toastClass=s.options.toastClass,s.titleClass=s.options.titleClass,s.messageClass=s.options.messageClass,s.progressBar=s.options.progressBar,c()){var p=angular.element(s.options.closeHtml),g=t.get("$compile");p.addClass("toast-close-button"),p.attr("ng-click","close(true, $event)"),g(p)(s),o.children().prepend(p)}s.init=function(){s.options.timeOut&&(u=i(s.options.timeOut)),s.options.onShown&&s.options.onShown()},o.on("mouseenter",function(){l(),u&&e.cancel(u)}),s.tapToast=function(){angular.isFunction(s.options.onTap)&&s.options.onTap(),s.options.tapToDismiss&&s.close(!0)},s.close=function(t,e){e&&angular.isFunction(e.stopPropagation)&&e.stopPropagation(),n.remove(s.toastId,t)},s.refreshTimer=function(t){u&&(e.cancel(u),u=i(t||s.options.timeOut))},o.on("mouseleave",function(){0===s.options.timeOut&&0===s.options.extendedTimeOut||(s.$apply(function(){s.progressBar=s.options.progressBar}),u=i(s.options.extendedTimeOut))})}return{templateUrl:function(){return s.templates.toast},controller:"ToastController",link:o}}angular.module("toastr").directive("toast",t),t.$inject=["$injector","$interval","toastrConfig","toastr"]}(),angular.module("toastr").run(["$templateCache",function(t){t.put("directives/progressbar/progressbar.html",'
\n'),t.put("directives/toast/toast.html",'
\n
\n
{{title}}
\n
{{message}}
\n
\n
\n
\n \n
\n')}]); -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var less = require('gulp-less'); 3 | var jshint = require('gulp-jshint'); 4 | var ngTemplates = require('gulp-angular-templatecache'); 5 | var rename = require('gulp-rename'); 6 | var uglify = require('gulp-uglify'); 7 | var concat = require('gulp-concat'); 8 | var cssnano = require('gulp-cssnano'); 9 | 10 | var del = require('del'); 11 | var stylish = require('jshint-stylish'); 12 | 13 | gulp.task('less-dev', function() { 14 | return gulp.src('src/toastr.less') 15 | .pipe(less()) 16 | .pipe(gulp.dest('gen')); 17 | }); 18 | 19 | gulp.task('less-prod', function() { 20 | return gulp.src('src/toastr.less') 21 | .pipe(less()) 22 | .pipe(rename('angular-toastr.css')) 23 | .pipe(gulp.dest('dist')) 24 | .pipe(cssnano({ 25 | zindex: false 26 | })) 27 | .pipe(rename('angular-toastr.min.css')) 28 | .pipe(gulp.dest('dist')); 29 | }); 30 | 31 | gulp.task('lint', function() { 32 | return gulp.src(['src/**/*.js', 'test/**/*_spec.js']) 33 | .pipe(jshint()) 34 | .pipe(jshint.reporter(stylish)); 35 | }); 36 | 37 | gulp.task('scripts-dev', function() { 38 | return gulp.src(['src/toastr.js', 'src/**/*.js']) 39 | .pipe(concat('toastr.js')) 40 | .pipe(gulp.dest('gen')); 41 | }); 42 | 43 | gulp.task('scripts-prod', function() { 44 | return gulp.src(['src/toastr.js', 'src/**/*.js']) 45 | .pipe(concat('angular-toastr.js')) 46 | .pipe(gulp.dest('dist')) 47 | .pipe(uglify()) 48 | .pipe(rename('angular-toastr.min.js')) 49 | .pipe(gulp.dest('dist')); 50 | }); 51 | 52 | gulp.task('scripts-prod-tpls', ['template'], function() { 53 | return gulp.src(['src/toastr.js', 'src/**/*.js', 'gen/toastr.tpl.js']) 54 | .pipe(concat('angular-toastr.tpls.js')) 55 | .pipe(gulp.dest('dist')) 56 | .pipe(uglify()) 57 | .pipe(rename('angular-toastr.tpls.min.js')) 58 | .pipe(gulp.dest('dist')); 59 | }); 60 | 61 | gulp.task('template', function() { 62 | return gulp.src('src/**/*.html') 63 | .pipe(ngTemplates({ 64 | module: 'toastr' 65 | })) 66 | .pipe(rename('toastr.tpl.js')) 67 | .pipe(gulp.dest('gen')); 68 | }); 69 | 70 | gulp.task('watch', function() { 71 | gulp.watch('src/**/*.js', ['lint', 'scripts-dev']); 72 | gulp.watch('src/toastr.less', ['less-dev']); 73 | gulp.watch('src/**/*.html', ['template']); 74 | }); 75 | 76 | gulp.task('clean', function(cb) { 77 | del(['dist', 'gen'], cb); 78 | }); 79 | 80 | gulp.task('default', ['less-dev', 'scripts-dev', 'template', 'watch']); 81 | gulp.task('production', ['less-prod', 'scripts-prod', 'scripts-prod-tpls']); 82 | gulp.task('travis', ['less-dev', 'scripts-dev', 'template']); 83 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('./dist/angular-toastr.tpls.js'); 2 | module.exports = 'toastr'; 3 | 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-toastr", 3 | "version": "2.1.1", 4 | "author": "Jesus Rodriguez ", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/Foxandxss/angular-toastr.git" 8 | }, 9 | "main": "index.js", 10 | "keywords": [ 11 | "angularjs", 12 | "toastr", 13 | "popup" 14 | ], 15 | "license": "MIT", 16 | "devDependencies": { 17 | "angular": "^1.5.0", 18 | "angular-mocks": "^1.5.0", 19 | "del": "^2.0.2", 20 | "gulp": "^3.8.10", 21 | "gulp-angular-templatecache": "^1.5.0", 22 | "gulp-concat": "^2.4.3", 23 | "gulp-cssnano": "^2.1.1", 24 | "gulp-jshint": "^2.0.0", 25 | "gulp-less": "^3.0.2", 26 | "gulp-rename": "^1.2.0", 27 | "gulp-uglify": "^1.0.2", 28 | "jquery": "^2.1.4", 29 | "jshint": "^2.9.1", 30 | "jshint-stylish": "^2.0.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/directives/progressbar/progressbar.directive.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('toastr') 5 | .directive('progressBar', progressBar); 6 | 7 | progressBar.$inject = ['toastrConfig']; 8 | 9 | function progressBar(toastrConfig) { 10 | return { 11 | require: '^toast', 12 | templateUrl: function() { 13 | return toastrConfig.templates.progressbar; 14 | }, 15 | link: linkFunction 16 | }; 17 | 18 | function linkFunction(scope, element, attrs, toastCtrl) { 19 | var intervalId, currentTimeOut, hideTime; 20 | 21 | toastCtrl.progressBar = scope; 22 | 23 | scope.start = function(duration) { 24 | if (intervalId) { 25 | clearInterval(intervalId); 26 | } 27 | 28 | currentTimeOut = parseFloat(duration); 29 | hideTime = new Date().getTime() + currentTimeOut; 30 | intervalId = setInterval(updateProgress, 10); 31 | }; 32 | 33 | scope.stop = function() { 34 | if (intervalId) { 35 | clearInterval(intervalId); 36 | } 37 | }; 38 | 39 | function updateProgress() { 40 | var percentage = ((hideTime - (new Date().getTime())) / currentTimeOut) * 100; 41 | element.css('width', percentage + '%'); 42 | } 43 | 44 | scope.$on('$destroy', function() { 45 | // Failsafe stop 46 | clearInterval(intervalId); 47 | }); 48 | } 49 | } 50 | }()); 51 | -------------------------------------------------------------------------------- /src/directives/progressbar/progressbar.html: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /src/directives/toast/toast.controller.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('toastr') 5 | .controller('ToastController', ToastController); 6 | 7 | function ToastController() { 8 | this.progressBar = null; 9 | 10 | this.startProgressBar = function(duration) { 11 | if (this.progressBar) { 12 | this.progressBar.start(duration); 13 | } 14 | }; 15 | 16 | this.stopProgressBar = function() { 17 | if (this.progressBar) { 18 | this.progressBar.stop(); 19 | } 20 | }; 21 | } 22 | }()); 23 | -------------------------------------------------------------------------------- /src/directives/toast/toast.directive.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('toastr') 5 | .directive('toast', toast); 6 | 7 | toast.$inject = ['$injector', '$interval', 'toastrConfig', 'toastr']; 8 | 9 | function toast($injector, $interval, toastrConfig, toastr) { 10 | return { 11 | templateUrl: function() { 12 | return toastrConfig.templates.toast; 13 | }, 14 | controller: 'ToastController', 15 | link: toastLinkFunction 16 | }; 17 | 18 | function toastLinkFunction(scope, element, attrs, toastCtrl) { 19 | var timeout; 20 | 21 | scope.toastClass = scope.options.toastClass; 22 | scope.titleClass = scope.options.titleClass; 23 | scope.messageClass = scope.options.messageClass; 24 | scope.progressBar = scope.options.progressBar; 25 | 26 | if (wantsCloseButton()) { 27 | var button = angular.element(scope.options.closeHtml), 28 | $compile = $injector.get('$compile'); 29 | button.addClass('toast-close-button'); 30 | button.attr('ng-click', 'close(true, $event)'); 31 | $compile(button)(scope); 32 | element.children().prepend(button); 33 | } 34 | 35 | scope.init = function() { 36 | if (scope.options.timeOut) { 37 | timeout = createTimeout(scope.options.timeOut); 38 | } 39 | if (scope.options.onShown) { 40 | scope.options.onShown(); 41 | } 42 | }; 43 | 44 | element.on('mouseenter', function() { 45 | hideAndStopProgressBar(); 46 | if (timeout) { 47 | $interval.cancel(timeout); 48 | } 49 | }); 50 | 51 | scope.tapToast = function () { 52 | if (angular.isFunction(scope.options.onTap)) { 53 | scope.options.onTap(); 54 | } 55 | if (scope.options.tapToDismiss) { 56 | scope.close(true); 57 | } 58 | }; 59 | 60 | scope.close = function (wasClicked, $event) { 61 | if ($event && angular.isFunction($event.stopPropagation)) { 62 | $event.stopPropagation(); 63 | } 64 | toastr.remove(scope.toastId, wasClicked); 65 | }; 66 | 67 | scope.refreshTimer = function(newTime) { 68 | if (timeout) { 69 | $interval.cancel(timeout); 70 | timeout = createTimeout(newTime || scope.options.timeOut); 71 | } 72 | }; 73 | 74 | element.on('mouseleave', function() { 75 | if (scope.options.timeOut === 0 && scope.options.extendedTimeOut === 0) { return; } 76 | scope.$apply(function() { 77 | scope.progressBar = scope.options.progressBar; 78 | }); 79 | timeout = createTimeout(scope.options.extendedTimeOut); 80 | }); 81 | 82 | function createTimeout(time) { 83 | toastCtrl.startProgressBar(time); 84 | return $interval(function() { 85 | toastCtrl.stopProgressBar(); 86 | toastr.remove(scope.toastId); 87 | }, time, 1); 88 | } 89 | 90 | function hideAndStopProgressBar() { 91 | scope.progressBar = false; 92 | toastCtrl.stopProgressBar(); 93 | } 94 | 95 | function wantsCloseButton() { 96 | return scope.options.closeHtml; 97 | } 98 | } 99 | } 100 | }()); 101 | -------------------------------------------------------------------------------- /src/directives/toast/toast.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
{{title}}
4 |
{{message}}
5 |
6 |
7 |
8 | 9 |
10 | -------------------------------------------------------------------------------- /src/toastr.config.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('toastr') 5 | .constant('toastrConfig', { 6 | allowHtml: false, 7 | autoDismiss: false, 8 | closeButton: false, 9 | closeHtml: '', 10 | containerId: 'toast-container', 11 | extendedTimeOut: 1000, 12 | iconClasses: { 13 | error: 'toast-error', 14 | info: 'toast-info', 15 | success: 'toast-success', 16 | warning: 'toast-warning' 17 | }, 18 | maxOpened: 0, 19 | messageClass: 'toast-message', 20 | newestOnTop: true, 21 | onHidden: null, 22 | onShown: null, 23 | onTap: null, 24 | positionClass: 'toast-top-right', 25 | preventDuplicates: false, 26 | preventOpenDuplicates: false, 27 | progressBar: false, 28 | tapToDismiss: true, 29 | target: 'body', 30 | templates: { 31 | toast: 'directives/toast/toast.html', 32 | progressbar: 'directives/progressbar/progressbar.html' 33 | }, 34 | timeOut: 5000, 35 | titleClass: 'toast-title', 36 | toastClass: 'toast' 37 | }); 38 | }()); 39 | -------------------------------------------------------------------------------- /src/toastr.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('toastr', []) 5 | .factory('toastr', toastr); 6 | 7 | toastr.$inject = ['$animate', '$injector', '$rootScope', '$sce', 'toastrConfig', '$q']; 8 | 9 | function toastr($animate, $injector, $rootScope, $sce, toastrConfig, $q) { 10 | var container; 11 | var index = 0; 12 | var toasts = []; 13 | 14 | var previousToastMessage = ''; 15 | var openToasts = {}; 16 | 17 | var containerDefer = $q.defer(); 18 | 19 | var toast = { 20 | active: active, 21 | clear: clear, 22 | error: error, 23 | info: info, 24 | remove: remove, 25 | success: success, 26 | warning: warning, 27 | refreshTimer: refreshTimer 28 | }; 29 | 30 | return toast; 31 | 32 | /* Public API */ 33 | function active() { 34 | return toasts.length; 35 | } 36 | 37 | function clear(toast) { 38 | // Bit of a hack, I will remove this soon with a BC 39 | if (arguments.length === 1 && !toast) { return; } 40 | 41 | if (toast) { 42 | remove(toast.toastId); 43 | } else { 44 | for (var i = 0; i < toasts.length; i++) { 45 | remove(toasts[i].toastId); 46 | } 47 | } 48 | } 49 | 50 | function error(message, title, optionsOverride) { 51 | var type = _getOptions().iconClasses.error; 52 | return _buildNotification(type, message, title, optionsOverride); 53 | } 54 | 55 | function info(message, title, optionsOverride) { 56 | var type = _getOptions().iconClasses.info; 57 | return _buildNotification(type, message, title, optionsOverride); 58 | } 59 | 60 | function success(message, title, optionsOverride) { 61 | var type = _getOptions().iconClasses.success; 62 | return _buildNotification(type, message, title, optionsOverride); 63 | } 64 | 65 | function warning(message, title, optionsOverride) { 66 | var type = _getOptions().iconClasses.warning; 67 | return _buildNotification(type, message, title, optionsOverride); 68 | } 69 | 70 | function refreshTimer(toast, newTime) { 71 | if (toast && toast.isOpened && toasts.indexOf(toast) >= 0) { 72 | toast.scope.refreshTimer(newTime); 73 | } 74 | } 75 | 76 | function remove(toastId, wasClicked) { 77 | var toast = findToast(toastId); 78 | 79 | if (toast && ! toast.deleting) { // Avoid clicking when fading out 80 | toast.deleting = true; 81 | toast.isOpened = false; 82 | $animate.leave(toast.el).then(function() { 83 | if (toast.scope.options.onHidden) { 84 | toast.scope.options.onHidden(!!wasClicked, toast); 85 | } 86 | toast.scope.$destroy(); 87 | var index = toasts.indexOf(toast); 88 | delete openToasts[toast.scope.message]; 89 | toasts.splice(index, 1); 90 | var maxOpened = toastrConfig.maxOpened; 91 | if (maxOpened && toasts.length >= maxOpened) { 92 | toasts[maxOpened - 1].open.resolve(); 93 | } 94 | if (lastToast()) { 95 | container.remove(); 96 | container = null; 97 | containerDefer = $q.defer(); 98 | } 99 | }); 100 | } 101 | 102 | function findToast(toastId) { 103 | for (var i = 0; i < toasts.length; i++) { 104 | if (toasts[i].toastId === toastId) { 105 | return toasts[i]; 106 | } 107 | } 108 | } 109 | 110 | function lastToast() { 111 | return !toasts.length; 112 | } 113 | } 114 | 115 | /* Internal functions */ 116 | function _buildNotification(type, message, title, optionsOverride) { 117 | if (angular.isObject(title)) { 118 | optionsOverride = title; 119 | title = null; 120 | } 121 | 122 | return _notify({ 123 | iconClass: type, 124 | message: message, 125 | optionsOverride: optionsOverride, 126 | title: title 127 | }); 128 | } 129 | 130 | function _getOptions() { 131 | return angular.extend({}, toastrConfig); 132 | } 133 | 134 | function _createOrGetContainer(options) { 135 | if(container) { return containerDefer.promise; } 136 | 137 | container = angular.element('
'); 138 | container.attr('id', options.containerId); 139 | container.addClass(options.positionClass); 140 | container.css({'pointer-events': 'auto'}); 141 | 142 | var target = angular.element(document.querySelector(options.target)); 143 | 144 | if ( ! target || ! target.length) { 145 | throw 'Target for toasts doesn\'t exist'; 146 | } 147 | 148 | $animate.enter(container, target).then(function() { 149 | containerDefer.resolve(); 150 | }); 151 | 152 | return containerDefer.promise; 153 | } 154 | 155 | function _notify(map) { 156 | var options = _getOptions(); 157 | 158 | if (shouldExit()) { return; } 159 | 160 | var newToast = createToast(); 161 | 162 | toasts.push(newToast); 163 | 164 | if (ifMaxOpenedAndAutoDismiss()) { 165 | var oldToasts = toasts.slice(0, (toasts.length - options.maxOpened)); 166 | for (var i = 0, len = oldToasts.length; i < len; i++) { 167 | remove(oldToasts[i].toastId); 168 | } 169 | } 170 | 171 | if (maxOpenedNotReached()) { 172 | newToast.open.resolve(); 173 | } 174 | 175 | newToast.open.promise.then(function() { 176 | _createOrGetContainer(options).then(function() { 177 | newToast.isOpened = true; 178 | if (options.newestOnTop) { 179 | $animate.enter(newToast.el, container).then(function() { 180 | newToast.scope.init(); 181 | }); 182 | } else { 183 | var sibling = container[0].lastChild ? angular.element(container[0].lastChild) : null; 184 | $animate.enter(newToast.el, container, sibling).then(function() { 185 | newToast.scope.init(); 186 | }); 187 | } 188 | }); 189 | }); 190 | 191 | return newToast; 192 | 193 | function ifMaxOpenedAndAutoDismiss() { 194 | return options.autoDismiss && options.maxOpened && toasts.length > options.maxOpened; 195 | } 196 | 197 | function createScope(toast, map, options) { 198 | if (options.allowHtml) { 199 | toast.scope.allowHtml = true; 200 | toast.scope.title = $sce.trustAsHtml(map.title); 201 | toast.scope.message = $sce.trustAsHtml(map.message); 202 | } else { 203 | toast.scope.title = map.title; 204 | toast.scope.message = map.message; 205 | } 206 | 207 | toast.scope.toastType = toast.iconClass; 208 | toast.scope.toastId = toast.toastId; 209 | toast.scope.extraData = options.extraData; 210 | 211 | toast.scope.options = { 212 | extendedTimeOut: options.extendedTimeOut, 213 | messageClass: options.messageClass, 214 | onHidden: options.onHidden, 215 | onShown: generateEvent('onShown'), 216 | onTap: generateEvent('onTap'), 217 | progressBar: options.progressBar, 218 | tapToDismiss: options.tapToDismiss, 219 | timeOut: options.timeOut, 220 | titleClass: options.titleClass, 221 | toastClass: options.toastClass 222 | }; 223 | 224 | if (options.closeButton) { 225 | toast.scope.options.closeHtml = options.closeHtml; 226 | } 227 | 228 | function generateEvent(event) { 229 | if (options[event]) { 230 | return function() { 231 | options[event](toast); 232 | }; 233 | } 234 | } 235 | } 236 | 237 | function createToast() { 238 | var newToast = { 239 | toastId: index++, 240 | isOpened: false, 241 | scope: $rootScope.$new(), 242 | open: $q.defer() 243 | }; 244 | newToast.iconClass = map.iconClass; 245 | if (map.optionsOverride) { 246 | angular.extend(options, cleanOptionsOverride(map.optionsOverride)); 247 | newToast.iconClass = map.optionsOverride.iconClass || newToast.iconClass; 248 | } 249 | 250 | createScope(newToast, map, options); 251 | 252 | newToast.el = createToastEl(newToast.scope); 253 | 254 | return newToast; 255 | 256 | function cleanOptionsOverride(options) { 257 | var badOptions = ['containerId', 'iconClasses', 'maxOpened', 'newestOnTop', 258 | 'positionClass', 'preventDuplicates', 'preventOpenDuplicates', 'templates']; 259 | for (var i = 0, l = badOptions.length; i < l; i++) { 260 | delete options[badOptions[i]]; 261 | } 262 | 263 | return options; 264 | } 265 | } 266 | 267 | function createToastEl(scope) { 268 | var angularDomEl = angular.element('
'), 269 | $compile = $injector.get('$compile'); 270 | return $compile(angularDomEl)(scope); 271 | } 272 | 273 | function maxOpenedNotReached() { 274 | return options.maxOpened && toasts.length <= options.maxOpened || !options.maxOpened; 275 | } 276 | 277 | function shouldExit() { 278 | var isDuplicateOfLast = options.preventDuplicates && map.message === previousToastMessage; 279 | var isDuplicateOpen = options.preventOpenDuplicates && openToasts[map.message]; 280 | 281 | if (isDuplicateOfLast || isDuplicateOpen) { 282 | return true; 283 | } 284 | 285 | previousToastMessage = map.message; 286 | openToasts[map.message] = true; 287 | 288 | return false; 289 | } 290 | } 291 | } 292 | }()); 293 | -------------------------------------------------------------------------------- /src/toastr.less: -------------------------------------------------------------------------------- 1 | // Mix-ins 2 | .borderRadius(@radius) { 3 | -moz-border-radius: @radius; 4 | -webkit-border-radius: @radius; 5 | border-radius: @radius; 6 | } 7 | 8 | .boxShadow(@boxShadow) { 9 | -moz-box-shadow: @boxShadow; 10 | -webkit-box-shadow: @boxShadow; 11 | box-shadow: @boxShadow; 12 | } 13 | 14 | // Variables 15 | @black: #000000; 16 | @grey: #999999; 17 | @light-grey: #CCCCCC; 18 | @white: #FFFFFF; 19 | @near-black: #030303; 20 | @green: #51A351; 21 | @red: #BD362F; 22 | @blue: #2F96B4; 23 | @orange: #F89406; 24 | 25 | // Styles 26 | .toast-title { 27 | font-weight: bold; 28 | } 29 | 30 | .toast-message { 31 | word-wrap: break-word; 32 | 33 | a, 34 | label { 35 | color: @white; 36 | } 37 | 38 | a:hover { 39 | color: @light-grey; 40 | text-decoration: none; 41 | } 42 | } 43 | 44 | .toast-close-button { 45 | position: relative; 46 | right: -0.3em; 47 | top: -0.3em; 48 | float: right; 49 | font-size: 20px; 50 | font-weight: bold; 51 | color: @white; 52 | -webkit-text-shadow: 0 1px 0 rgba(255,255,255,1); 53 | text-shadow: 0 1px 0 rgba(255,255,255,1); 54 | opacity: 0.8; 55 | 56 | &:hover, 57 | &:focus { 58 | color: @black; 59 | text-decoration: none; 60 | cursor: pointer; 61 | opacity: 0.4; 62 | } 63 | } 64 | 65 | /*Additional properties for button version 66 | iOS requires the button element instead of an anchor tag. 67 | If you want the anchor version, it requires `href="#"`.*/ 68 | button.toast-close-button { 69 | padding: 0; 70 | cursor: pointer; 71 | background: transparent; 72 | border: 0; 73 | -webkit-appearance: none; 74 | } 75 | 76 | //#endregion 77 | 78 | .toast-top-center { 79 | top: 0; 80 | right: 0; 81 | width: 100%; 82 | } 83 | 84 | .toast-bottom-center { 85 | bottom: 0; 86 | right: 0; 87 | width: 100%; 88 | } 89 | 90 | .toast-top-full-width { 91 | top: 0; 92 | right: 0; 93 | width: 100%; 94 | } 95 | 96 | .toast-bottom-full-width { 97 | bottom: 0; 98 | right: 0; 99 | width: 100%; 100 | } 101 | 102 | .toast-top-left { 103 | top: 12px; 104 | left: 12px; 105 | } 106 | 107 | .toast-top-right { 108 | top: 12px; 109 | right: 12px; 110 | } 111 | 112 | .toast-bottom-right { 113 | right: 12px; 114 | bottom: 12px; 115 | } 116 | 117 | .toast-bottom-left { 118 | bottom: 12px; 119 | left: 12px; 120 | } 121 | 122 | #toast-container { 123 | position: fixed; 124 | z-index: 999999; 125 | 126 | * { 127 | -moz-box-sizing: border-box; 128 | -webkit-box-sizing: border-box; 129 | box-sizing: border-box; 130 | } 131 | 132 | .toast { 133 | position: relative; 134 | overflow: hidden; 135 | margin: 0 0 6px; 136 | padding: 15px 15px 15px 50px; 137 | width: 300px; 138 | .borderRadius(3px 3px 3px 3px); 139 | background-position: 15px center; 140 | background-repeat: no-repeat; 141 | .boxShadow(0 0 12px @grey); 142 | color: @white; 143 | opacity: 0.8; 144 | } 145 | 146 | .toast:hover { 147 | .boxShadow(0 0 12px @black); 148 | opacity: 1; 149 | cursor: pointer; 150 | } 151 | 152 | .toast.toast-info { 153 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=") !important; 154 | } 155 | 156 | .toast.toast-error { 157 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=") !important; 158 | } 159 | 160 | .toast.toast-success { 161 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==") !important; 162 | } 163 | 164 | .toast.toast-warning { 165 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=") !important; 166 | } 167 | 168 | /*overrides*/ 169 | &.toast-top-center .toast, 170 | &.toast-bottom-center .toast { 171 | width: 300px; 172 | margin-left: auto; 173 | margin-right: auto; 174 | } 175 | 176 | &.toast-top-full-width .toast, 177 | &.toast-bottom-full-width .toast { 178 | width: 96%; 179 | margin-left: auto; 180 | margin-right: auto; 181 | } 182 | } 183 | 184 | .toast { 185 | background-color: @near-black; 186 | } 187 | 188 | .toast-success { 189 | background-color: @green; 190 | } 191 | 192 | .toast-error { 193 | background-color: @red; 194 | } 195 | 196 | .toast-info { 197 | background-color: @blue; 198 | } 199 | 200 | .toast-warning { 201 | background-color: @orange; 202 | } 203 | 204 | progress-bar { 205 | position: absolute; 206 | left: 0; 207 | bottom: 0; 208 | height: 4px; 209 | background-color: @black; 210 | opacity: 0.4; 211 | } 212 | 213 | /*Animations*/ 214 | div[toast] { 215 | opacity: 1 !important; 216 | } 217 | 218 | div[toast].ng-enter { 219 | opacity: 0 !important; 220 | transition: opacity .3s linear; 221 | } 222 | 223 | div[toast].ng-enter.ng-enter-active { 224 | opacity: 1 !important; 225 | } 226 | 227 | div[toast].ng-leave { 228 | opacity: 1; 229 | transition: opacity .3s linear; 230 | } 231 | 232 | div[toast].ng-leave.ng-leave-active { 233 | opacity: 0 !important; 234 | } 235 | 236 | 237 | /*Responsive Design*/ 238 | 239 | @media all and (max-width: 240px) { 240 | #toast-container { 241 | 242 | .toast.div { 243 | padding: 8px 8px 8px 50px; 244 | width: 11em; 245 | } 246 | 247 | & .toast-close-button { 248 | right: -0.2em; 249 | top: -0.2em; 250 | } 251 | } 252 | } 253 | 254 | @media all and (min-width: 241px) and (max-width: 480px) { 255 | #toast-container { 256 | .toast.div { 257 | padding: 8px 8px 8px 50px; 258 | width: 18em; 259 | } 260 | 261 | & .toast-close-button { 262 | right: -0.2em; 263 | top: -0.2em; 264 | } 265 | } 266 | } 267 | 268 | @media all and (min-width: 481px) and (max-width: 768px) { 269 | #toast-container { 270 | .toast.div { 271 | padding: 15px 15px 15px 50px; 272 | width: 25em; 273 | } 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /test/toastr_spec.js: -------------------------------------------------------------------------------- 1 | describe('toastr', function() { 2 | var $animate, $document, $rootScope, $timeout, $interval; 3 | var toastr, toastrConfig, originalConfig = {}; 4 | 5 | beforeEach(module('ngAnimateMock')); 6 | beforeEach(module('toastr')); 7 | 8 | beforeEach(inject(function(_$animate_, _$document_, _$rootScope_, _$interval_, _$timeout_, _toastr_, _toastrConfig_) { 9 | $animate = _$animate_; 10 | $document = _$document_; 11 | $rootScope = _$rootScope_; 12 | $interval = _$interval_; 13 | $timeout = _$timeout_; 14 | toastr = _toastr_; 15 | angular.copy(_toastrConfig_, originalConfig); 16 | toastrConfig = _toastrConfig_; 17 | })); 18 | 19 | afterEach(function() { 20 | $document.find('#toast-container').remove(); 21 | angular.copy(originalConfig, toastrConfig); 22 | }); 23 | 24 | beforeEach(function() { 25 | jasmine.addMatchers({ 26 | toHaveA: function() { 27 | return { 28 | compare: function(toast, tag) { 29 | var el = toast.el.find(tag); 30 | return { 31 | pass: el.length > 0 32 | }; 33 | } 34 | }; 35 | }, 36 | 37 | toHaveButtonWith: function(util, customEqualityTesters) { 38 | return { 39 | compare: function(toast, text) { 40 | var buttomDomEl = toast.el.find('.toast-close-button'); 41 | return { 42 | pass: util.equals(buttomDomEl.text(), text, customEqualityTesters) 43 | }; 44 | } 45 | }; 46 | }, 47 | 48 | toHaveClass: function() { 49 | return { 50 | compare: function(toast, klass) { 51 | return { 52 | pass: toast.el.find('div:first').hasClass(klass) 53 | }; 54 | } 55 | }; 56 | }, 57 | 58 | toHaveProgressBar: function(util, customEqualityTesters) { 59 | return { 60 | compare: function(toast) { 61 | var progressBarEl = toast.el.find('.toast-progress'); 62 | return { 63 | pass: util.equals(progressBarEl.length, 1, customEqualityTesters) 64 | }; 65 | } 66 | }; 67 | }, 68 | 69 | toHaveToastContainer: function(util, customEqualityTesters) { 70 | return { 71 | compare: function(document, target) { 72 | target = target || 'body'; 73 | var containerDomEl = document.find(target + ' > #toast-container'); 74 | return { 75 | pass: util.equals(containerDomEl.length, 1, customEqualityTesters) 76 | }; 77 | } 78 | }; 79 | }, 80 | 81 | toHaveToastOpen: function(util, customEqualityTesters) { 82 | return { 83 | compare: function(document, noOfToasts, target) { 84 | target = target || 'body'; 85 | var toastDomEls = document.find(target + ' .toast'); 86 | return { 87 | pass: util.equals(toastDomEls.length, noOfToasts, customEqualityTesters) 88 | }; 89 | } 90 | }; 91 | }, 92 | 93 | toHaveTitle: function(util, customEQualityTesters) { 94 | return { 95 | compare: function(toast) { 96 | var title = toast.el.find('.toast-title'); 97 | return { 98 | pass: util.equals(title.length, 1, customEQualityTesters) 99 | }; 100 | } 101 | }; 102 | }, 103 | 104 | toHaveAriaLabelOnTitle: function() { 105 | return { 106 | compare: function(toast) { 107 | var title = toast.el.find('.toast-title'); 108 | return { 109 | pass: title.is('[aria-label]') 110 | }; 111 | } 112 | }; 113 | }, 114 | 115 | toHaveAriaLabelOnMessage: function() { 116 | return { 117 | compare: function(toast) { 118 | var message = toast.el.find('.toast-message'); 119 | return { 120 | pass: message.is('[aria-label]') 121 | }; 122 | } 123 | }; 124 | }, 125 | 126 | toHaveType: function() { 127 | return { 128 | compare: function(toast, type) { 129 | var typeClass = 'toast-' + type; 130 | return { 131 | pass: toast.el.find('.toast').hasClass(typeClass) 132 | }; 133 | } 134 | }; 135 | }, 136 | 137 | toHaveToastWithMessage: function(util, customEqualityTesters) { 138 | return { 139 | compare: function(document, message, toast, target) { 140 | target = target || 'body'; 141 | var found, 142 | contentToCompare, 143 | toastsDomEl = document.find(target + ' .toast'); 144 | 145 | if (toast) { 146 | contentToCompare = toastsDomEl.eq(toast).find('.toast-message').eq(0).html(); 147 | 148 | found = util.equals(contentToCompare, message, customEqualityTesters); 149 | } else { 150 | for (var i = 0, l = toastsDomEl.length; i < l; i++) { 151 | contentToCompare = toastsDomEl.eq(i).find('.toast-message').eq(0).html(); 152 | 153 | found = util.equals(contentToCompare, message, customEqualityTesters); 154 | 155 | if (found) { 156 | break; 157 | } 158 | } 159 | } 160 | 161 | return { 162 | pass: found 163 | }; 164 | } 165 | }; 166 | }, 167 | 168 | toHaveToastWithTitle: function(util, customEqualityTesters) { 169 | return { 170 | compare: function(document, title, toast, target) { 171 | target = target || 'body'; 172 | var found, 173 | contentToCompare, 174 | toastsDomEl = document.find(target + ' .toast'); 175 | 176 | if (toast) { 177 | contentToCompare = toastsDomEl.eq(toast).find('.toast-title').eq(0).html(); 178 | 179 | found = util.equals(contentToCompare, title, customEqualityTesters); 180 | } else { 181 | for (var i = 0, l = toastsDomEl.length; i < l; i++) { 182 | contentToCompare = toastsDomEl.eq(i).find('.toast-title').eq(0).html(); 183 | 184 | found = util.equals(contentToCompare, title, customEqualityTesters); 185 | 186 | if (found) { 187 | break; 188 | } 189 | } 190 | } 191 | 192 | return { 193 | pass: found 194 | }; 195 | } 196 | }; 197 | } 198 | }); 199 | }); 200 | 201 | function _findToast(toast, target) { 202 | target = target || 'body'; 203 | return $document.find(target + ' > #toast-container .toast').eq(toast || 0); 204 | } 205 | 206 | function _findToastCloseButton(toast, target) { 207 | target = target || 'body'; 208 | return $document.find(target + ' > #toast-container .toast .toast-close-button').eq(toast || 0); 209 | } 210 | 211 | // Needed when we want to run the callback of enter or leave. 212 | function animationFlush() { 213 | // This is not compatible with all the tests 214 | // But it is easier to swallow the errors, tests still run and pass. 215 | try { 216 | $animate.flush(); 217 | $rootScope.$digest(); 218 | } catch (e) { 219 | 220 | } 221 | } 222 | 223 | function clickToast(noOfToast) { 224 | var toast = _findToast(noOfToast); 225 | toast.click(); 226 | 227 | $rootScope.$digest(); 228 | animationFlush(); 229 | } 230 | 231 | function clickToastCloseButton(noOfToast) { 232 | var toastCloseButton = _findToastCloseButton(noOfToast); 233 | toastCloseButton.click(); 234 | $rootScope.$digest(); 235 | animationFlush(); 236 | } 237 | 238 | function hoverToast(noOfToast) { 239 | var toast = _findToast(noOfToast); 240 | toast.trigger('mouseenter'); 241 | } 242 | 243 | function leaveToast(noOfToast) { 244 | var toast = _findToast(noOfToast); 245 | toast.trigger('mouseleave'); 246 | } 247 | 248 | function openToast(type, message, title, options) { 249 | var toast = toastr[type](message, title, options); 250 | 251 | $rootScope.$digest(); 252 | animationFlush(); 253 | animationFlush(); 254 | 255 | return toast; 256 | } 257 | 258 | function openToasts(noOfToast, optionsOverride) { 259 | for (var i = 0; i < noOfToast; i++) { 260 | toastr.success('message', 'title', optionsOverride); 261 | } 262 | $rootScope.$digest(); 263 | animationFlush(); 264 | animationFlush(); 265 | } 266 | 267 | function removeToast(toast) { 268 | toastr.clear(toast); 269 | $rootScope.$digest(); 270 | animationFlush(); 271 | } 272 | 273 | function intervalFlush(millis) { 274 | $interval.flush(millis || 5000); 275 | } 276 | 277 | describe('basic scenarios', function() { 278 | it('should be able to open a toast in the container', function() { 279 | openToasts(1); 280 | expect($document).toHaveToastOpen(1); 281 | intervalFlush(); 282 | expect($document).toHaveToastOpen(0); 283 | }); 284 | 285 | it('should be able to stack more than one toast', function() { 286 | openToasts(5); 287 | expect($document).toHaveToastOpen(5); 288 | intervalFlush(); 289 | expect($document).toHaveToastOpen(0); 290 | }); 291 | 292 | it('should close a toast upon click', function () { 293 | openToasts(1); 294 | expect($document).toHaveToastOpen(1); 295 | clickToast(); 296 | expect($document).toHaveToastOpen(0); 297 | }); 298 | 299 | it('should not close a toast with !tapToDismiss upon click', function () { 300 | openToasts(1, { tapToDismiss: false }); 301 | expect($document).toHaveToastOpen(1); 302 | clickToast(); 303 | expect($document).toHaveToastOpen(1); 304 | }); 305 | 306 | it('should close a toast clicking the close button', function () { 307 | openToasts(1, { tapToDismiss: false, closeButton: true }); 308 | expect($document).toHaveToastOpen(1); 309 | clickToastCloseButton(); 310 | expect($document).toHaveToastOpen(0); 311 | }); 312 | 313 | it('should contain a title and a message', function () { 314 | openToast('success', 'World', 'Hello'); 315 | expect($document).toHaveToastWithMessage('World'); 316 | expect($document).toHaveToastWithTitle('Hello'); 317 | }); 318 | 319 | it('have an optional title', function() { 320 | openToasts(5); 321 | var toast = openToast('success', 'Hello'); 322 | expect(toast).not.toHaveTitle(); 323 | }); 324 | 325 | it('has a flag indicating whether it is opened or not', function() { 326 | var toast = toastr.success('foo'); 327 | 328 | expect(toast.isOpened).toBe(false); 329 | 330 | $rootScope.$digest(); 331 | animationFlush(); 332 | animationFlush(); 333 | 334 | expect(toast.isOpened).toBe(true); 335 | 336 | intervalFlush(); 337 | 338 | expect(toast.isOpened).toBe(false); 339 | }); 340 | 341 | it('has multiple types of toasts', function() { 342 | var toast = openToast('success', 'foo'); 343 | expect(toast).toHaveType('success'); 344 | intervalFlush(); 345 | toast = openToast('error', 'foo'); 346 | expect(toast).toHaveType('error'); 347 | intervalFlush(); 348 | toast = openToast('info', 'foo'); 349 | expect(toast).toHaveType('info'); 350 | intervalFlush(); 351 | toast = openToast('warning', 'foo'); 352 | expect(toast).toHaveType('warning'); 353 | intervalFlush(); 354 | }); 355 | 356 | it('allows to manually close a toast in code', function() { 357 | var toast = openToast('success', 'foo'); 358 | expect($document).toHaveToastOpen(1); 359 | toastr.clear(toast); 360 | $rootScope.$digest(); 361 | expect($document).toHaveToastOpen(0); 362 | animationFlush(); 363 | expect($document).not.toHaveToastContainer(); 364 | }); 365 | 366 | it('allows to close all toasts at once', function() { 367 | openToasts(10); 368 | expect($document).toHaveToastOpen(10); 369 | toastr.clear(); 370 | $rootScope.$digest(); 371 | expect($document).toHaveToastOpen(0); 372 | animationFlush(); 373 | expect($document).not.toHaveToastContainer(); 374 | }); 375 | 376 | it('has a list of active toasts', function() { 377 | openToasts(5); 378 | expect(toastr.active()).toBe(5); 379 | clickToast(); 380 | clickToast(); 381 | expect(toastr.active()).toBe(3); 382 | intervalFlush(); 383 | animationFlush(); 384 | expect(toastr.active()).toBe(0); 385 | }); 386 | 387 | it('allows to restart the timer, keeping the toast visible longer', function() { 388 | toastrConfig.timeOut = 5000; 389 | var toast = openToast('success', 'foo'); 390 | expect($document).toHaveToastOpen(1); 391 | intervalFlush(2000); 392 | toastr.refreshTimer(toast); 393 | intervalFlush(3000); 394 | expect($document).toHaveToastOpen(1); 395 | intervalFlush(2000); 396 | expect($document).toHaveToastOpen(0); 397 | }); 398 | 399 | it('allows to restart the timer with a new duration', function() { 400 | toastrConfig.timeOut = 5000; 401 | var toast = openToast('success', 'foo'); 402 | expect($document).toHaveToastOpen(1); 403 | intervalFlush(2000); 404 | toastr.refreshTimer(toast, 10000); 405 | intervalFlush(5000); 406 | expect($document).toHaveToastOpen(1); 407 | intervalFlush(5000); 408 | expect($document).toHaveToastOpen(0); 409 | }); 410 | 411 | it('ignores requests to restart the timer for manually-closed toasts', function() { 412 | toastrConfig.timeOut = 5000; 413 | var toast = openToast('success', 'foo'); 414 | spyOn(toast.scope, 'refreshTimer'); 415 | expect($document).toHaveToastOpen(1); 416 | intervalFlush(1000); 417 | toastr.clear(toast); 418 | intervalFlush(1000); 419 | toastr.refreshTimer(toast); 420 | expect(toast.scope.refreshTimer).not.toHaveBeenCalled(); 421 | }); 422 | 423 | it('ignores requests to restart the timer for recently-expired toasts', function() { 424 | toastrConfig.timeOut = 5000; 425 | var toast = openToast('success', 'foo'); 426 | spyOn(toast.scope, 'refreshTimer'); 427 | expect($document).toHaveToastOpen(1); 428 | intervalFlush(5000); 429 | toastr.refreshTimer(toast); 430 | expect(toast.scope.refreshTimer).not.toHaveBeenCalled(); 431 | }); 432 | 433 | it('ignores requests to restart the timer for old toasts', function() { 434 | toastrConfig.timeOut = 5000; 435 | var toast = openToast('success', 'foo'); 436 | spyOn(toast.scope, 'refreshTimer'); 437 | expect($document).toHaveToastOpen(1); 438 | intervalFlush(60000); 439 | expect($document).toHaveToastOpen(0); 440 | toastr.refreshTimer(toast); 441 | expect(toast.scope.refreshTimer).not.toHaveBeenCalled(); 442 | }); 443 | 444 | }); 445 | 446 | describe('container', function() { 447 | it('should create a new toastr container when the first toast is created', function() { 448 | expect($document).not.toHaveToastContainer(); 449 | openToasts(1); 450 | expect($document).toHaveToastContainer(); 451 | }); 452 | 453 | it('should delete the toastr container when the last toast is gone', function() { 454 | expect($document).not.toHaveToastContainer(); 455 | openToasts(2); 456 | expect($document).toHaveToastContainer(); 457 | clickToast(); 458 | expect($document).toHaveToastContainer(); 459 | clickToast(); 460 | expect($document).not.toHaveToastContainer(); 461 | }); 462 | 463 | it('is created again if it gets deleted', function() { 464 | expect($document).not.toHaveToastContainer(); 465 | openToasts(2); 466 | expect($document).toHaveToastContainer(); 467 | clickToast(); 468 | expect($document).toHaveToastContainer(); 469 | clickToast(); 470 | expect($document).not.toHaveToastContainer(); 471 | openToasts(1); 472 | expect($document).toHaveToastContainer(); 473 | }); 474 | 475 | it('can add the container to a custom target', function() { 476 | toastrConfig.target = '#toast-target'; 477 | var target = angular.element('
'); 478 | $document.find('body').append(target); 479 | 480 | var toast = openToast('success', 'toast'); 481 | 482 | expect($document).toHaveToastContainer('#toast-target'); 483 | 484 | expect($document).toHaveToastOpen(1, '#toast-target'); 485 | 486 | intervalFlush(); 487 | 488 | expect($document).toHaveToastOpen(0, '#toast-target'); 489 | animationFlush(); 490 | expect($document).not.toHaveToastContainer('#toast-target'); 491 | }); 492 | 493 | it('should throw an exception if the custom target doesn\'t exist', function() { 494 | toastrConfig.target = '#no-exist'; 495 | 496 | expect(function() { 497 | openToast('success', 'foo'); 498 | }).toThrow('Target for toasts doesn\'t exist'); 499 | }); 500 | }); 501 | 502 | describe('directive behavior', function() { 503 | it('should not close a toast if hovered', function() { 504 | openToasts(1); 505 | hoverToast(); 506 | intervalFlush(); 507 | expect($document).toHaveToastOpen(1); 508 | }); 509 | 510 | it('should close all the toasts but the hovered one', function() { 511 | openToasts(5); 512 | hoverToast(2); 513 | intervalFlush(); // Closing others... 514 | intervalFlush(); 515 | expect($document).toHaveToastOpen(1); 516 | }); 517 | 518 | it('should re-enable the timeout of a toast if you leave it', function() { 519 | openToasts(1); 520 | hoverToast(); 521 | intervalFlush(); 522 | expect($document).toHaveToastOpen(1); 523 | leaveToast(); 524 | intervalFlush(); 525 | expect($document).toHaveToastOpen(0); 526 | }); 527 | }); 528 | 529 | describe('options overriding', function() { 530 | it('can change the type of the toast', function() { 531 | var options = { 532 | iconClass: 'toast-pink' 533 | }; 534 | var toast = openToast('success', 'message', 'title', options); 535 | expect(toast).toHaveClass(options.iconClass); 536 | }); 537 | 538 | it('can override the toast class', function() { 539 | var options = { 540 | toastClass: 'my-toast' 541 | }; 542 | var toast = openToast('error', 'message', 'title', options); 543 | expect(toast).toHaveClass(options.toastClass); 544 | }); 545 | 546 | it('title and message should contain aria-label', function() { 547 | var toast = openToast('error', 'message', 'title'); 548 | expect(toast).toHaveAriaLabelOnMessage(); 549 | expect(toast).toHaveAriaLabelOnTitle(); 550 | }); 551 | 552 | it('can make a toast stick until is clicked or hovered (extended timeout)', function() { 553 | var options = { 554 | timeOut: 0 555 | }; 556 | openToast('info', 'I don\'t want to go...', options); 557 | intervalFlush(); 558 | expect($document).toHaveToastOpen(1); 559 | clickToast(); 560 | expect($document).toHaveToastOpen(0); 561 | 562 | openToast('info', 'I don\'t want to go...', options); 563 | intervalFlush(); 564 | expect($document).toHaveToastOpen(1); 565 | hoverToast(); 566 | leaveToast(); 567 | intervalFlush(); 568 | expect($document).toHaveToastOpen(0); 569 | }); 570 | 571 | it('can make a toast stick until is clicked', function() { 572 | var options = { 573 | timeOut: 0, 574 | extendedTimeOut: 0 575 | }; 576 | openToast('info', 'I don\'t want to go...', options); 577 | intervalFlush(); 578 | expect($document).toHaveToastOpen(1); 579 | hoverToast(); 580 | leaveToast(); 581 | expect($document).toHaveToastOpen(1); 582 | clickToast(); 583 | expect($document).toHaveToastOpen(0); 584 | }); 585 | 586 | it('can show custom html on the toast message', function() { 587 | var toast = openToast('success', 'I like to have a ', { 588 | allowHtml: true 589 | }); 590 | expect(toast).toHaveA('button'); 591 | }); 592 | 593 | it('can show custom html on the toast title', function() { 594 | var toast = openToast('success', 'I want a surprise', ' Surprise', { 595 | allowHtml: true 596 | }); 597 | expect(toast).toHaveA('button'); 598 | }); 599 | 600 | it('can limit the maximum opened toasts', function() { 601 | toastrConfig.maxOpened = 3; 602 | var toast1 = openToast('success', 'Toast 1'); 603 | var toast2 = openToast('success', 'Toast 2'); 604 | openToast('success', 'Toast 3'); 605 | expect($document).toHaveToastOpen(3); 606 | openToast('success', 'Toast 4'); 607 | expect($document).toHaveToastOpen(3); 608 | removeToast(toast1); 609 | expect($document).toHaveToastOpen(3); 610 | expect($document).not.toHaveToastWithMessage('Toast 1'); 611 | openToast('success', 'Toast 5'); 612 | expect($document).toHaveToastOpen(3); 613 | removeToast(toast2); 614 | expect($document).not.toHaveToastWithMessage('Toast 2'); 615 | }); 616 | 617 | it('can limit the maximum opened toasts with newestOnTop false', function() { 618 | toastrConfig.maxOpened = 3; 619 | toastrConfig.newestOnTop = false; 620 | var toast1 = openToast('success', 'Toast 1'); 621 | openToast('success', 'Toast 2'); 622 | openToast('success', 'Toast 3'); 623 | expect($document).toHaveToastOpen(3); 624 | openToast('success', 'Toast 4'); 625 | expect($document).toHaveToastOpen(3); 626 | removeToast(toast1); 627 | expect($document).not.toHaveToastWithMessage('Toast 1'); 628 | }); 629 | 630 | it('can auto dismiss old toasts', function() { 631 | toastrConfig.maxOpened = 1; 632 | toastrConfig.autoDismiss = true; 633 | var toast1 = openToast('success', 'Toast 1'); 634 | openToast('success', 'Toast 2'); 635 | openToast('success', 'Toast 3'); 636 | expect($document).toHaveToastOpen(1); 637 | expect($document).toHaveToastWithMessage('Toast 3'); 638 | }); 639 | 640 | it('maxOpened and autoDimiss works together #95', function() { 641 | toastrConfig.maxOpened = 3; 642 | toastrConfig.autoDismiss = true; 643 | var toast1 = openToast('success', 'Toast 1'); 644 | openToast('success', 'Toast 2'); 645 | openToast('success', 'Toast 3'); 646 | expect($document).toHaveToastOpen(3); 647 | }); 648 | 649 | it('has not limit if maxOpened is 0', function() { 650 | toastrConfig.maxOpened = 0; 651 | openToast('success', 'Toast 1'); 652 | openToast('success', 'Toast 2'); 653 | openToast('success', 'Toast 3'); 654 | expect($document).toHaveToastOpen(3); 655 | openToast('success', 'Toast 4'); 656 | animationFlush(); 657 | expect($document).toHaveToastOpen(4); 658 | expect($document).toHaveToastWithMessage('Toast 1'); 659 | }); 660 | 661 | it('can prevent duplicate toasts', function() { 662 | toastrConfig.preventDuplicates = true; 663 | openToast('success', 'Toast 1'); 664 | expect($document).toHaveToastOpen(1); 665 | intervalFlush(); 666 | openToast('success', 'Toast 1'); 667 | expect($document).toHaveToastOpen(0); 668 | }); 669 | 670 | it('can prevent duplicate of open toasts', function() { 671 | toastrConfig.preventDuplicates = false; 672 | toastrConfig.preventOpenDuplicates = true; 673 | var toast1 = openToast('success', 'Toast 1'); 674 | var toast2 = openToast('success', 'Toast 2'); 675 | openToast('success', 'Toast 1'); 676 | openToast('success', 'Toast 2'); 677 | var toast3 = openToast('success', 'Toast 3'); 678 | openToast('success', 'Toast 1'); 679 | expect($document).toHaveToastOpen(3); 680 | removeToast(toast1); 681 | removeToast(toast2); 682 | removeToast(toast3); 683 | openToast('success', 'Toast 1'); 684 | expect($document).toHaveToastOpen(1); 685 | }); 686 | 687 | it('does not merge options not meant for concrete toasts', function() { 688 | openToasts(2, { 689 | maxOpened: 2 // this is not meant for the toasts and gives weird side effects 690 | }); 691 | expect($document).toHaveToastOpen(2); 692 | intervalFlush(); 693 | openToasts(2, { 694 | maxOpened: 2 695 | }); 696 | expect($document).toHaveToastOpen(2); 697 | }); 698 | 699 | it('allows to change the templates of the directives', inject(function($templateCache) { 700 | $templateCache.put('foo/bar/template.html', '
This is my Template
'); 701 | toastrConfig.timeOut = 200000; 702 | toastrConfig.templates.toast = 'foo/bar/template.html'; 703 | 704 | var toast = openToast('success', 'foo'); 705 | expect(toast.el.text()).toBe('This is my Template'); 706 | })); 707 | 708 | it('allows to pass global extra data to the toastr directive', inject(function($templateCache) { 709 | $templateCache.put('foo/bar/template.html', '
{{extraData.foo}}
'); 710 | toastrConfig.extraData = {foo: 'Hello!'}; 711 | toastrConfig.templates.toast = 'foo/bar/template.html'; 712 | 713 | var toast = openToast('success', 'foo'); 714 | expect(toast.el.text()).toBe('Hello!'); 715 | })); 716 | 717 | it('allows to pass extra data per toast to the toastr directive', inject(function($templateCache) { 718 | $templateCache.put('foo/bar/template.html', '
{{extraData.msg}}
'); 719 | toastrConfig.templates.toast = 'foo/bar/template.html'; 720 | var toast = openToast('success', 'foo', { 721 | extraData: {msg: 'First toast'} 722 | }); 723 | 724 | var toast2 = openToast('info', 'bar', { 725 | extraData: {msg: 'Second toast'} 726 | }); 727 | 728 | expect(toast.el.text()).toBe('First toast'); 729 | expect(toast2.el.text()).toBe('Second toast'); 730 | })); 731 | 732 | it('allows to override the global extra data per toast', inject(function($templateCache) { 733 | $templateCache.put('foo/bar/template.html', '
{{extraData.msg}}
'); 734 | toastrConfig.extraData = {msg: 'Hello!'}; 735 | toastrConfig.templates.toast = 'foo/bar/template.html'; 736 | var toast = openToast('success', 'foo'); 737 | 738 | var toast2 = openToast('info', 'bar', { 739 | extraData: {msg: 'Second toast'} 740 | }); 741 | 742 | expect(toast.el.text()).toBe('Hello!'); 743 | expect(toast2.el.text()).toBe('Second toast'); 744 | })); 745 | }); 746 | 747 | describe('close button', function() { 748 | it('should contain a close button with × if you add it', function() { 749 | var toast = openToast('info', 'I have a button', { 750 | closeButton: true 751 | }); 752 | 753 | expect(toast).toHaveButtonWith('×'); 754 | }); 755 | 756 | it('allows custom button text on the close button', function() { 757 | var toast = openToast('info', 'I have a button', { 758 | closeButton: true, 759 | closeHtml: '' 760 | }); 761 | 762 | expect(toast).toHaveButtonWith('1'); 763 | }); 764 | 765 | it('allows custom element as the close button', function() { 766 | var toast = openToast('info', 'I have a button', { 767 | closeButton: true, 768 | closeHtml: '1' 769 | }); 770 | 771 | expect(toast).toHaveButtonWith('1'); 772 | }); 773 | }); 774 | 775 | describe('toast order', function() { 776 | it('adds the newest toasts on top by default', function() { 777 | var toast1 = openToast('success', 'I will be on the bottom'); 778 | var toast2 = openToast('info', 'I like the top part!'); 779 | expect($document).toHaveToastWithMessage(toast2.scope.message, 0); 780 | expect($document).toHaveToastWithMessage(toast1.scope.message, 1); 781 | }); 782 | 783 | it('adds the older toasts on top setting newestOnTop to false', function() { 784 | toastrConfig.newestOnTop = false; 785 | 786 | var toast1 = openToast('success', 'I will be on the top now'); 787 | var toast2 = openToast('info', 'I dont like the bottom part!'); 788 | expect($document).toHaveToastWithMessage(toast2.scope.message, 1); 789 | expect($document).toHaveToastWithMessage(toast1.scope.message, 0); 790 | }); 791 | }); 792 | 793 | describe('callbacks', function() { 794 | it('calls the onShown callback when showing a toast', function() { 795 | var callback = jasmine.createSpy(); 796 | var toast = openToast('success', 'A toast', { onShown: callback }); 797 | expect(callback).toHaveBeenCalledWith(toast); 798 | }); 799 | 800 | it('calls the onHidden callback after a toast is closed on click', function() { 801 | var callback = jasmine.createSpy(); 802 | var toast = openToast('success', 'A toast', { onHidden: callback }); 803 | expect(callback).not.toHaveBeenCalled(); 804 | clickToast(); 805 | expect(callback).toHaveBeenCalledWith(true, toast); 806 | }); 807 | 808 | it('calls the onHidden callback after a toast is closed by timeout', function() { 809 | var callback = jasmine.createSpy(); 810 | var toast = openToast('success', 'A toast', { onHidden: callback }); 811 | expect(callback).not.toHaveBeenCalled(); 812 | intervalFlush(); 813 | animationFlush(); 814 | expect(callback).toHaveBeenCalledWith(false, toast); 815 | }); 816 | 817 | it('calls the onHidden callback with "true" if the button was clicked', function() { 818 | var callback = jasmine.createSpy(); 819 | var toast = openToast('info', 'I have a button', { 820 | onHidden: callback, 821 | closeButton: true 822 | }); 823 | clickToastCloseButton(); 824 | expect(callback).toHaveBeenCalledWith(true, toast); 825 | }); 826 | 827 | it('can call the callbacks even if the title is set to null', function() { 828 | var callback = jasmine.createSpy(); 829 | var toast = openToast('success', 'some message', null, {onShown: callback}); 830 | expect(callback).toHaveBeenCalledWith(toast); 831 | }); 832 | 833 | it('calls the onTap callback when toast is clicked', function() { 834 | var callback = jasmine.createSpy(); 835 | var toast = openToast('success', 'A toast', { onTap: callback }); 836 | expect(callback).not.toHaveBeenCalled(); 837 | clickToast(); 838 | expect(callback).toHaveBeenCalledWith(toast); 839 | }); 840 | }); 841 | 842 | describe('toast controller', function() { 843 | var ctrl; 844 | 845 | beforeEach(inject(function($controller) { 846 | ctrl = $controller('ToastController'); 847 | })); 848 | 849 | it('does not register a progressbar by default', function() { 850 | expect(ctrl.progressBar).toBeNull(); 851 | }); 852 | 853 | it('can start the progressbar', function() { 854 | var scope = { 855 | start: jasmine.createSpy() 856 | }; 857 | ctrl.progressBar = scope; 858 | ctrl.startProgressBar(5000); 859 | 860 | expect(scope.start).toHaveBeenCalledWith(5000); 861 | }); 862 | 863 | it('can stop the progressbar', function() { 864 | var scope = { 865 | stop: jasmine.createSpy() 866 | }; 867 | ctrl.progressBar = scope; 868 | ctrl.stopProgressBar(); 869 | 870 | expect(scope.stop).toHaveBeenCalled(); 871 | }); 872 | }); 873 | 874 | describe('progressbar', function() { 875 | beforeEach(function() { 876 | toastrConfig.progressBar = true; 877 | }); 878 | 879 | it('contains a progressBar if the option is set to true', function() { 880 | var toast = openToast('success', 'foo'); 881 | expect(toast).toHaveProgressBar(); 882 | intervalFlush(); 883 | }); 884 | 885 | it('removes the progressBar if the toast is hovered', function() { 886 | var toast = openToast('success', 'foo'); 887 | expect(toast).toHaveProgressBar(); 888 | hoverToast(); 889 | intervalFlush(); 890 | $rootScope.$digest(); 891 | expect(toast).not.toHaveProgressBar(); 892 | leaveToast(); 893 | expect(toast).toHaveProgressBar(); 894 | intervalFlush(); 895 | }); 896 | }); 897 | }); 898 | --------------------------------------------------------------------------------