├── .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 | [](https://codeclimate.com/github/Foxandxss/angular-toastr) [](https://travis-ci.org/Foxandxss/angular-toastr) [](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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 button ', {
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', 'button 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: '1 '
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 |
--------------------------------------------------------------------------------