├── .bowerrc
├── .editorconfig
├── .gitattributes
├── .gitignore
├── .jshintrc
├── .travis.yml
├── Gruntfile.js
├── LICENSE
├── Makefile
├── app
├── components
├── index.html
├── scripts
│ ├── app.js
│ └── controllers
│ │ └── main-ctrl.js
├── styles
│ └── main.css
└── views
│ └── main.html
├── bower.json
├── dist
├── angular-flash.js
└── angular-flash.min.js
├── karma.conf.js
├── package.json
├── readme.md
├── src
├── .jshintrc
├── directives
│ └── flash-alert-directive.js
└── services
│ └── flash-service.js
└── test
├── .jshintrc
└── unit
├── directives
└── flash-alert-directive-spec.js
└── services
└── flash-service-spec.js
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "bower_components"
3 | }
4 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 |
8 | [*]
9 |
10 | # Change these settings to your own preference
11 | indent_style = space
12 | indent_size = 4
13 |
14 | # We recommend you to keep these unchanged
15 | end_of_line = lf
16 | charset = utf-8
17 | trim_trailing_whitespace = true
18 | insert_final_newline = true
19 |
20 | [*.md]
21 | trim_trailing_whitespace = false
22 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Set default behaviour, in case users don't have core.autocrlf set.
2 | * text=auto
3 |
4 | # Explicitly declare text files we want to always be normalized and converted
5 | # to native line endings on checkout.
6 | *.js text
7 | *.html text
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | node_modules
3 | bower_components
4 | libpeerconnection.log
5 | reports
6 |
7 | *.swp
8 | *.swo
9 |
10 | npm-debug.log
11 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node": true,
3 | "browser": false,
4 | "jquery": false,
5 | "es5": true,
6 | "bitwise": true,
7 | "camelcase": false,
8 | "latedef": true,
9 | "boss": false,
10 | "curly": true,
11 | "debug": false,
12 | "devel": false,
13 | "eqeqeq": true,
14 | "evil": true,
15 | "forin": true,
16 | "immed": true,
17 | "laxbreak": false,
18 | "newcap": true,
19 | "noarg": true,
20 | "noempty": true,
21 | "nonew": true,
22 | "nomen": false,
23 | "onevar": false,
24 | "plusplus": true,
25 | "regexp": false,
26 | "undef": true,
27 | "sub": true,
28 | "strict": true,
29 | "white": false,
30 | "unused": true,
31 | "smarttabs": false,
32 | "indent": 4,
33 | "quotmark": "single"
34 | }
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "0.10"
4 | before_script:
5 | - npm install grunt-cli -g
6 | - npm install bower -g
7 | - bower install
8 | after_script:
9 | - npm run coveralls
10 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var mountFolder = function (connect, dir) {
3 | return connect.static(require('path').resolve(dir));
4 | };
5 |
6 | // configurable paths
7 | var yeomanConfig = {
8 | app: 'app',
9 | src: 'src',
10 | dist: 'dist'
11 | };
12 |
13 | try {
14 | yeomanConfig.app = require('./bower.json').appPath || yeomanConfig.app;
15 | } catch (e) {
16 | }
17 |
18 | module.exports = function (grunt) {
19 |
20 | // Project configuration.
21 | grunt.initConfig({
22 | yeoman: yeomanConfig,
23 | pkg: grunt.file.readJSON('package.json'),
24 | lifecycle: {
25 | validate: [
26 | 'jshint'
27 | ],
28 | compile: [],
29 | test: [
30 | 'karma:phantom'
31 | ],
32 | 'package': [
33 | 'concat',
34 | 'uglify'
35 | ],
36 | 'integration-test': [],
37 | verify: [],
38 | install: [],
39 | deploy: []
40 | },
41 | jshint: {
42 | src: {
43 | options: {
44 | jshintrc: 'src/.jshintrc'
45 | },
46 | src: ['src/**/*.js']
47 | },
48 | test: {
49 | options: {
50 | jshintrc: 'test/.jshintrc'
51 | },
52 | src: ['test/unit/*.js']
53 | },
54 | grunt: {
55 | options: {
56 | jshintrc: '.jshintrc'
57 | },
58 | src: ['Gruntfile.js']
59 | }
60 | },
61 | karma: {
62 | options: {
63 | configFile: 'karma.conf.js'
64 | },
65 | unit: {
66 | singleRun: true
67 | },
68 | phantom: {
69 | singleRun: true,
70 | browsers: ['PhantomJS']
71 | },
72 | debug: {
73 | singleRun: false,
74 | reporters: ['progress', 'junit']
75 | }
76 | },
77 | concat: {
78 | options: {
79 | banner: ['/**! ',
80 | ' * @license <%= pkg.name %> v<%= pkg.version %>',
81 | ' * Copyright (c) 2013 <%= pkg.author.name %>. <%= pkg.homepage %>',
82 | ' * License: MIT',
83 | ' */\n'].join('\n')
84 | },
85 | main: {
86 | src: [
87 | 'src/services/flash-service.js',
88 | 'src/directives/flash-alert-directive.js'
89 | ],
90 | dest: 'dist/<%= pkg.name %>.js'
91 | }
92 | },
93 | uglify: {
94 | options: {
95 | banner: ['/**! ',
96 | ' * @license <%= pkg.name %> v<%= pkg.version %>',
97 | ' * Copyright (c) 2013 <%= pkg.author.name %>. <%= pkg.homepage %>',
98 | ' * License: MIT',
99 | ' */\n'].join('\n')
100 | },
101 | main: {
102 | files: {
103 | 'dist/<%= pkg.name %>.min.js': [
104 | '<%= concat.main.dest %>'
105 | ]
106 | }
107 | }
108 | },
109 | watch: {
110 | scripts: {
111 | files: ['src/**/*.js'],
112 | tasks: ['phase-package']
113 | },
114 | livereload: {
115 | options: {
116 | livereload: true
117 | },
118 | files: [
119 | '<%= yeoman.app %>/*.html',
120 | '<%= yeoman.app %>/scrips/*.js',
121 | '<%= yeoman.app %>/scrips/**/*.js',
122 | '<%= yeoman.dist %>/*.js'
123 | ]
124 |
125 | }
126 | },
127 | bumpup: ['package.json', 'bower.json'],
128 | connect: {
129 | options: {
130 | port: 9000,
131 | // Change this to '0.0.0.0' to access the server from outside.
132 | hostname: 'localhost'
133 | },
134 | livereload: {
135 | options: {
136 | middleware: function (connect) {
137 | return [
138 | require('connect-livereload')(),
139 | mountFolder(connect, yeomanConfig.dist),
140 | mountFolder(connect, yeomanConfig.app),
141 | mountFolder(connect, yeomanConfig.src),
142 | mountFolder(connect, 'test')
143 | ];
144 | }
145 | }
146 | }
147 | },
148 | open: {
149 | server: {
150 | url: 'http://localhost:<%= connect.options.port %>'
151 | }
152 | }
153 | });
154 |
155 | // load all grunt tasks
156 | require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);
157 |
158 | grunt.registerTask('bump', function (type) {
159 | type = type ? type : 'patch';
160 | grunt.task.run('bumpup:' + type);
161 | });
162 |
163 | grunt.registerTask('server', [
164 | 'package',
165 | 'connect:livereload',
166 | 'open',
167 | 'watch'
168 | ]);
169 |
170 | grunt.registerTask('test-phantom', ['karma:phantom']);
171 | grunt.registerTask('test-start', ['karma:debug:start']);
172 | grunt.registerTask('test-run', ['karma:debug:run']);
173 | grunt.registerTask('build', ['install']);
174 | grunt.registerTask('default', ['install']);
175 |
176 | };
177 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright (c) 2013 William L. Bunselmeyer. https://github.com/wmluke/angular-blocks
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.
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 |
2 | install:
3 | npm install # Install node modules
4 | bower install # Install bower components
5 | grunt install # Build & test client app
--------------------------------------------------------------------------------
/app/components:
--------------------------------------------------------------------------------
1 | ../bower_components
--------------------------------------------------------------------------------
/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
28 |
29 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
×
40 |
{{flash.type}}
41 |
{{flash.message}}
42 |
43 |
This alert:
44 |
45 | Shows all alerts
46 | Does not fade away
47 | Has a close button
48 | Works outside of controller scope
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | ×
61 |
62 | {{flash.type}}
63 | {{flash.message}}
64 |
Alert #1
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/app/scripts/app.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | 'use strict';
3 |
4 | angular.module('App', ['ngRoute', 'angular-flash.flash-alert-directive', 'App.main-ctrl'])
5 | .config(function ($routeProvider, $locationProvider) {
6 | $routeProvider
7 | .when('/', {
8 | templateUrl: 'views/main.html',
9 | controller: 'MainCtrl'
10 | })
11 | .otherwise({
12 | redirectTo: '/'
13 | });
14 | $locationProvider.html5Mode(true);
15 | })
16 | .config(function (flashProvider) {
17 | // Support bootstrap 3.0 "alert-danger" class with error flash types
18 | flashProvider.errorClassnames.push('alert-danger');
19 |
20 | /**
21 | * Also have...
22 | *
23 | * flashProvider.warnClassnames
24 | * flashProvider.infoClassnames
25 | * flashProvider.successClassnames
26 | */
27 |
28 | })
29 | .run();
30 | }());
31 |
--------------------------------------------------------------------------------
/app/scripts/controllers/main-ctrl.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | 'use strict';
3 |
4 | var MainCtrl = function ($scope, flash) {
5 |
6 |
7 | $scope.all = function () {
8 | $scope.info();
9 | $scope.warn();
10 | $scope.success();
11 | $scope.error();
12 | };
13 |
14 | $scope.info = function () {
15 | flash.info = 'info message';
16 | };
17 |
18 | $scope.info1 = function () {
19 | flash.to('alert-1').info = 'info message';
20 | };
21 |
22 | $scope.warn = function () {
23 | flash.warn = 'warn message';
24 | };
25 |
26 | $scope.warn1 = function () {
27 | flash.to('alert-1').warn = 'warn message';
28 | };
29 |
30 | $scope.success = function () {
31 | flash.success = 'success message';
32 | };
33 |
34 | $scope.success1 = function () {
35 | flash.to('alert-1').success = 'success message';
36 | };
37 |
38 | $scope.error = function () {
39 | flash.error = 'error message';
40 | };
41 |
42 | $scope.error1 = function () {
43 | flash.to('alert-1').error = 'error message';
44 | };
45 |
46 | $scope.dismissAlert1 = function () {
47 | flash.to('alert-1').error = false;
48 | };
49 |
50 | $scope.all();
51 | };
52 |
53 | angular.module('App.main-ctrl', [])
54 | .controller('MainCtrl', ['$scope', 'flash', MainCtrl]);
55 | }());
56 |
--------------------------------------------------------------------------------
/app/styles/main.css:
--------------------------------------------------------------------------------
1 |
2 | body {
3 | padding-top: 60px;
4 | }
5 |
6 | .alert-flash {
7 | padding: 8px 35px 8px 14px;
8 | margin-bottom: 20px;
9 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
10 | border: 1px solid transparent;
11 | -webkit-border-radius: 4px;
12 | -moz-border-radius: 4px;
13 | border-radius: 4px;
14 |
15 | -webkit-transition-property: all;
16 | transition-property: all;
17 |
18 | }
19 |
20 | .alert-flash h4 {
21 | margin: 0;
22 | }
23 |
24 | .alert-flash .close {
25 | position: relative;
26 | top: -2px;
27 | right: -21px;
28 | line-height: 20px;
29 | }
30 |
31 | .alert-warn {
32 | background-color: #fcf8e3;
33 | border: 1px solid #fbeed5;
34 | }
35 |
36 | .alert-warn,
37 | .alert-warn h4 {
38 | color: #c09853;
39 | }
40 |
--------------------------------------------------------------------------------
/app/views/main.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
angular-flash
5 |
6 |
7 | Info
8 | Alert #1
9 |
10 |
11 |
12 | Warning
13 | Alert #1
14 |
15 |
16 |
17 | Success
18 | Alert #1
19 |
20 |
21 |
22 | Error
23 |
24 |
25 |
26 |
30 |
31 |
32 |
All
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | Ahem...
42 | {{flash.message}}
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | Not so good.
52 | {{flash.message}}
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | Nice work!
62 | {{flash.message}}
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | Yikes!
72 | {{flash.message}}
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-flash",
3 | "description": "Flash messages for Angular Js",
4 | "version": "0.1.14",
5 | "main": [
6 | "dist/angular-flash.js"
7 | ],
8 | "ignore": [
9 | "app",
10 | "bower_components",
11 | "test",
12 | ".jshintrc",
13 | "src/.jshintrc",
14 | ".travis.yml",
15 | "karma.conf.js",
16 | "Gruntfile.js"
17 | ],
18 | "dependencies": {
19 | "angular": "1.0 - 1.3"
20 | },
21 | "devDependencies": {
22 | "jquery": "~2.0.0",
23 | "angular-mocks": "1.3.2",
24 | "angular-scenario": "1.3.2",
25 | "angular-route": "1.3.2",
26 | "json3": "~3.2.4",
27 | "es5-shim": "~2.0.8",
28 | "bootstrap": "~2.3.2",
29 | "font-awesome": "~3.2.1"
30 | }
31 | }
--------------------------------------------------------------------------------
/dist/angular-flash.js:
--------------------------------------------------------------------------------
1 | /**!
2 | * @license angular-flash v0.1.14
3 | * Copyright (c) 2013 William L. Bunselmeyer. https://github.com/wmluke/angular-flash
4 | * License: MIT
5 | */
6 | /* global angular */
7 |
8 | (function () {
9 | 'use strict';
10 |
11 | var subscriberCount = 0;
12 |
13 | var Flash = function (options) {
14 | var _options = angular.extend({
15 | id: null,
16 | subscribers: {},
17 | classnames: {
18 | error: [],
19 | warn: [],
20 | info: [],
21 | success: []
22 | }
23 | }, options);
24 |
25 | var _self = this;
26 | var _success;
27 | var _info;
28 | var _warn;
29 | var _error;
30 | var _type;
31 |
32 | function _notify(type, message) {
33 | angular.forEach(_options.subscribers, function (subscriber) {
34 | var matchesType = !subscriber.type || subscriber.type === type;
35 | var matchesId = (!_options.id && !subscriber.id) || subscriber.id === _options.id;
36 | if (matchesType && matchesId) {
37 | subscriber.cb(message, type);
38 | }
39 | });
40 | }
41 |
42 | this.clean = function () {
43 | _success = null;
44 | _info = null;
45 | _warn = null;
46 | _error = null;
47 | _type = null;
48 | };
49 |
50 | this.subscribe = function (subscriber, type, id) {
51 | subscriberCount += 1;
52 | _options.subscribers[subscriberCount] = {
53 | cb: subscriber,
54 | type: type,
55 | id: id
56 | };
57 | return subscriberCount;
58 | };
59 |
60 | this.unsubscribe = function (handle) {
61 | delete _options.subscribers[handle];
62 | };
63 |
64 | this.to = function (id) {
65 | var options = angular.copy(_options);
66 | options.id = id;
67 | return new Flash(options);
68 | };
69 |
70 | Object.defineProperty(this, 'success', {
71 | get: function () {
72 | return _success;
73 | },
74 | set: function (message) {
75 | _success = message;
76 | _type = 'success';
77 | _notify(_type, message);
78 | }
79 | });
80 |
81 | Object.defineProperty(this, 'info', {
82 | get: function () {
83 | return _info;
84 | },
85 | set: function (message) {
86 | _info = message;
87 | _type = 'info';
88 | _notify(_type, message);
89 | }
90 | });
91 |
92 | Object.defineProperty(this, 'warn', {
93 | get: function () {
94 | return _warn;
95 | },
96 | set: function (message) {
97 | _warn = message;
98 | _type = 'warn';
99 | _notify(_type, message);
100 | }
101 | });
102 |
103 | Object.defineProperty(this, 'error', {
104 | get: function () {
105 | return _error;
106 | },
107 | set: function (message) {
108 | _error = message;
109 | _type = 'error';
110 | _notify(_type, message);
111 | }
112 | });
113 |
114 | Object.defineProperty(this, 'type', {
115 | get: function () {
116 | return _type;
117 | }
118 | });
119 |
120 | Object.defineProperty(this, 'message', {
121 | get: function () {
122 | return _type ? _self[_type] : null;
123 | }
124 | });
125 |
126 | Object.defineProperty(this, 'classnames', {
127 | get: function () {
128 | return _options.classnames;
129 | }
130 | });
131 |
132 | Object.defineProperty(this, 'id', {
133 | get: function () {
134 | return _options.id;
135 | }
136 | });
137 | };
138 |
139 | angular.module('angular-flash.service', [])
140 | .provider('flash', function () {
141 | var _self = this;
142 | this.errorClassnames = ['alert-error'];
143 | this.warnClassnames = ['alert-warn'];
144 | this.infoClassnames = ['alert-info'];
145 | this.successClassnames = ['alert-success'];
146 |
147 | this.$get = function () {
148 | return new Flash({
149 | classnames: {
150 | error: _self.errorClassnames,
151 | warn: _self.warnClassnames,
152 | info: _self.infoClassnames,
153 | success: _self.successClassnames
154 | }
155 | });
156 | };
157 | });
158 |
159 | }());
160 |
161 | /* global angular */
162 |
163 | (function () {
164 | 'use strict';
165 |
166 | function isBlank(str) {
167 | if (str === null || str === undefined) {
168 | str = '';
169 | }
170 | return (/^\s*$/).test(str);
171 | }
172 |
173 | function flashAlertDirective(flash, $timeout) {
174 | return {
175 | scope: true,
176 | link: function ($scope, element, attr) {
177 | var timeoutHandle, subscribeHandle;
178 |
179 | $scope.flash = {};
180 |
181 | $scope.hide = function () {
182 | removeAlertClasses();
183 | if (!isBlank(attr.activeClass)) {
184 | element.removeClass(attr.activeClass);
185 | }
186 | };
187 |
188 | $scope.$on('$destroy', function () {
189 | flash.clean();
190 | flash.unsubscribe(subscribeHandle);
191 | });
192 |
193 | function removeAlertClasses() {
194 | var classnames = [].concat(flash.classnames.error, flash.classnames.warn, flash.classnames.info, flash.classnames.success);
195 | angular.forEach(classnames, function (clazz) {
196 | element.removeClass(clazz);
197 | });
198 | }
199 |
200 | function show(message, type) {
201 | if (timeoutHandle) {
202 | $timeout.cancel(timeoutHandle);
203 | }
204 |
205 | $scope.flash.type = type;
206 | $scope.flash.message = message;
207 | removeAlertClasses();
208 | angular.forEach(flash.classnames[type], function (clazz) {
209 | element.addClass(clazz);
210 | });
211 |
212 | if (!isBlank(attr.activeClass)) {
213 | element.addClass(attr.activeClass);
214 | }
215 |
216 | if (!message) {
217 | $scope.hide();
218 | return;
219 | }
220 |
221 | var delay = Number(attr.duration || 5000);
222 | if (delay > 0) {
223 | timeoutHandle = $timeout($scope.hide, delay);
224 | }
225 | }
226 |
227 | subscribeHandle = flash.subscribe(show, attr.flashAlert, attr.id);
228 |
229 | /**
230 | * Fixes timing issues: display the last flash message sent before this directive subscribed.
231 | */
232 |
233 | if (attr.flashAlert && flash[attr.flashAlert]) {
234 | show(flash[attr.flashAlert], attr.flashAlert);
235 | }
236 |
237 | if (!attr.flashAlert && flash.message) {
238 | show(flash.message, flash.type);
239 | }
240 |
241 | }
242 | };
243 | }
244 |
245 | angular.module('angular-flash.flash-alert-directive', ['angular-flash.service'])
246 | .directive('flashAlert', ['flash', '$timeout', flashAlertDirective]);
247 |
248 | }());
249 |
--------------------------------------------------------------------------------
/dist/angular-flash.min.js:
--------------------------------------------------------------------------------
1 | /**!
2 | * @license angular-flash v0.1.14
3 | * Copyright (c) 2013 William L. Bunselmeyer. https://github.com/wmluke/angular-flash
4 | * License: MIT
5 | */
6 | !function(){"use strict";var a=0,b=function(c){function d(a,b){angular.forEach(j.subscribers,function(c){var d=!c.type||c.type===a,e=!j.id&&!c.id||c.id===j.id;d&&e&&c.cb(b,a)})}var e,f,g,h,i,j=angular.extend({id:null,subscribers:{},classnames:{error:[],warn:[],info:[],success:[]}},c),k=this;this.clean=function(){e=null,f=null,g=null,h=null,i=null},this.subscribe=function(b,c,d){return a+=1,j.subscribers[a]={cb:b,type:c,id:d},a},this.unsubscribe=function(a){delete j.subscribers[a]},this.to=function(a){var c=angular.copy(j);return c.id=a,new b(c)},Object.defineProperty(this,"success",{get:function(){return e},set:function(a){e=a,i="success",d(i,a)}}),Object.defineProperty(this,"info",{get:function(){return f},set:function(a){f=a,i="info",d(i,a)}}),Object.defineProperty(this,"warn",{get:function(){return g},set:function(a){g=a,i="warn",d(i,a)}}),Object.defineProperty(this,"error",{get:function(){return h},set:function(a){h=a,i="error",d(i,a)}}),Object.defineProperty(this,"type",{get:function(){return i}}),Object.defineProperty(this,"message",{get:function(){return i?k[i]:null}}),Object.defineProperty(this,"classnames",{get:function(){return j.classnames}}),Object.defineProperty(this,"id",{get:function(){return j.id}})};angular.module("angular-flash.service",[]).provider("flash",function(){var a=this;this.errorClassnames=["alert-error"],this.warnClassnames=["alert-warn"],this.infoClassnames=["alert-info"],this.successClassnames=["alert-success"],this.$get=function(){return new b({classnames:{error:a.errorClassnames,warn:a.warnClassnames,info:a.infoClassnames,success:a.successClassnames}})}})}(),function(){"use strict";function a(a){return(null===a||void 0===a)&&(a=""),/^\s*$/.test(a)}function b(b,c){return{scope:!0,link:function(d,e,f){function g(){var a=[].concat(b.classnames.error,b.classnames.warn,b.classnames.info,b.classnames.success);angular.forEach(a,function(a){e.removeClass(a)})}function h(h,j){if(i&&c.cancel(i),d.flash.type=j,d.flash.message=h,g(),angular.forEach(b.classnames[j],function(a){e.addClass(a)}),a(f.activeClass)||e.addClass(f.activeClass),!h)return void d.hide();var k=Number(f.duration||5e3);k>0&&(i=c(d.hide,k))}var i,j;d.flash={},d.hide=function(){g(),a(f.activeClass)||e.removeClass(f.activeClass)},d.$on("$destroy",function(){b.clean(),b.unsubscribe(j)}),j=b.subscribe(h,f.flashAlert,f.id),f.flashAlert&&b[f.flashAlert]&&h(b[f.flashAlert],f.flashAlert),!f.flashAlert&&b.message&&h(b.message,b.type)}}}angular.module("angular-flash.flash-alert-directive",["angular-flash.service"]).directive("flashAlert",["flash","$timeout",b])}();
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration
2 | // Generated on Fri Aug 09 2013 09:44:05 GMT-0700 (PDT)
3 |
4 | module.exports = function (config) {
5 | config.set({
6 |
7 | // base path, that will be used to resolve files and exclude
8 | basePath: '',
9 |
10 | // frameworks to use
11 | frameworks: ['jasmine'],
12 |
13 | // list of files / patterns to load in the browser
14 | files: [
15 | 'bower_components/jquery/jquery.js',
16 | 'bower_components/angular/angular.js',
17 | 'bower_components/angular-mocks/angular-mocks.js',
18 | 'src/**/*.js',
19 | 'test/unit/**/*-spec.js'
20 | ],
21 |
22 | // list of files to exclude
23 | exclude: [],
24 |
25 | // test results reporter to use
26 | // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage'
27 | reporters: ['progress', 'junit', 'coverage'],
28 |
29 | preprocessors: {
30 | // source files, that you wanna generate coverage for
31 | // do not include tests or libraries
32 | // (these files will be instrumented by Istanbul)
33 | 'src/**/*.js': ['coverage']
34 | },
35 |
36 | coverageReporter: {
37 | type: 'lcov', // lcov format supported by Coveralls
38 | dir: 'reports/coverage'
39 | },
40 |
41 | junitReporter: {
42 | outputFile: "reports/test/unit-test-results.xml"
43 | },
44 |
45 | // web server port
46 | port: 9876,
47 |
48 | runnerPort: 9100,
49 |
50 | // enable / disable colors in the output (reporters and logs)
51 | colors: true,
52 |
53 | // level of logging
54 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
55 | logLevel: config.LOG_INFO,
56 |
57 | // enable / disable watching file and executing tests whenever any file changes
58 | autoWatch: false,
59 |
60 | // Start these browsers, currently available:
61 | // - Chrome
62 | // - ChromeCanary
63 | // - Firefox
64 | // - Opera
65 | // - Safari (only Mac)
66 | // - PhantomJS
67 | // - IE (only Windows)
68 | browsers: ['Chrome'],
69 |
70 | // If browser does not capture in given timeout [ms], kill it
71 | captureTimeout: 60000,
72 |
73 | // Continuous Integration mode
74 | // if true, it capture browsers, run tests and exit
75 | singleRun: false
76 | });
77 | };
78 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-flash",
3 | "description": "Flash messages for Angular JS",
4 | "private": false,
5 | "version": "0.1.14",
6 | "homepage": "https://github.com/wmluke/angular-flash",
7 | "author": {
8 | "name": "William L. Bunselmeyer",
9 | "email": "wmlukeb@gmail.com"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git://github.com/wmluke/angular-flash.git"
14 | },
15 | "bugs": {
16 | "url": "https://github.com/wmluke/angular-flash/issues"
17 | },
18 | "engines": {
19 | "node": ">= 0.8.0",
20 | "npm": "1.1.x"
21 | },
22 | "scripts": {
23 | "test": "grunt test-phantom",
24 | "start": "node app",
25 | "coveralls": "cat ./reports/coverage/*/lcov.info | ./node_modules/coveralls/bin/coveralls.js"
26 | },
27 | "dependencies": {},
28 | "devDependencies": {
29 | "connect-livereload": "~0.4.0",
30 | "coveralls": "~2.10.0",
31 | "underscore.string": "~2.3.1",
32 | "grunt": "~0.4.1",
33 | "chai": "~1.5.0",
34 | "istanbul": "~0.1.34",
35 | "sinon-chai": "~2.3.1",
36 | "sinon": "~1.6.0",
37 | "jshint": "~2.1.4",
38 | "matchdep": "~0.1.2",
39 | "grunt-contrib-jshint": "~0.1.1",
40 | "grunt-contrib-uglify": "~0.2.0",
41 | "grunt-bumpup": "~0.2.0",
42 | "grunt-contrib-watch": "~0.5.1",
43 | "grunt-exec": "~0.4.5",
44 | "grunt-contrib-concat": "~0.3.0",
45 | "grunt-build-lifecycle": "~0.1.1",
46 | "grunt-open": "~0.2.0",
47 | "grunt-contrib-connect": "~0.7.1",
48 | "grunt-karma": "0.8.2",
49 | "karma": "~0.12.9",
50 | "karma-ng-scenario": "~0.1.0",
51 | "karma-junit-reporter": "~0.2.2",
52 | "karma-coverage": "~0.2.1",
53 | "karma-jasmine": "~0.1.5",
54 | "karma-phantomjs-launcher": "~0.1.4",
55 | "karma-chrome-launcher": "~0.1.2"
56 | },
57 | "keywords": [
58 | "angular",
59 | "flash"
60 | ]
61 | }
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # angular-flash
2 |
3 | [](https://travis-ci.org/wmluke/angular-flash)
4 | [](https://coveralls.io/r/wmluke/angular-flash?branch=master)
5 |
6 | A flash service and directive for setting and displaying flash messages in [Angular JS](http://angularjs.org). Specifically, the flash service is a publisher of flash messages and the flash directive is a subscriber to flash messages. The flash directive leverages the Twitter Bootstrap Alert component.
7 |
8 | ## Installation
9 |
10 | Download [angular-flash.min.js](https://github.com/wmluke/angular-flash/blob/master/dist/angular-flash.min.js) or install with bower.
11 |
12 | ```bash
13 | $ bower install angular-flash --save
14 | ```
15 |
16 | Load the `angular-flash.service` and the `angular-flash.flash-alert-directive` modules in your app.
17 |
18 | ```javascript
19 | angular.module('app', ['angular-flash.service', 'angular-flash.flash-alert-directive']);
20 | ```
21 |
22 | ## Configure
23 |
24 | ```javascript
25 | angular.module('app', ['angular-flash.service', 'angular-flash.flash-alert-directive'])
26 | .config(function (flashProvider) {
27 |
28 | // Support bootstrap 3.0 "alert-danger" class with error flash types
29 | flashProvider.errorClassnames.push('alert-danger');
30 |
31 | /**
32 | * Also have...
33 | *
34 | * flashProvider.warnClassnames
35 | * flashProvider.infoClassnames
36 | * flashProvider.successClassnames
37 | */
38 |
39 | })
40 | ```
41 |
42 | ## Usage
43 |
44 | Use the `flash` service to publish a flash messages...
45 |
46 | ```javascript
47 |
48 | var FooController = function(flash){
49 | // Publish a success flash
50 | flash.success = 'Do it live!';
51 |
52 | // Publish a error flash
53 | flash.error = 'Fail!';
54 |
55 | // Publish an info flash to the `alert-1` subscriber
56 | flash.to('alert-1').info = 'Only for alert 1';
57 |
58 | // The `flash-alert` directive hides itself when if receives falsey flash messages of any type
59 | flash.error = '';
60 |
61 | };
62 |
63 | FooController.$inject = ['flash'];
64 |
65 | ```
66 |
67 | Use the `flash-alert` directive to subscribe to flash messages...
68 |
69 | ```html
70 |
71 |
72 | Congrats!
73 | {{flash.message}}
74 |
75 |
76 |
77 |
78 | Boo!
79 | {{flash.message}}
80 |
81 |
82 |
83 |
84 | Boo!
85 | {{flash.message}}
86 |
87 |
88 |
89 |
90 | Boo!
91 | {{flash.message}}
92 |
93 |
94 |
95 |
96 |
97 | ×
98 | Boo!
99 | {{flash.message}}
100 |
101 | ```
102 |
103 | When a flash message is published, the `flash-alert` directive will add a class of the form `alert-` and also add classes specified in `active-class`. Then after 5 seconds it will remove them.
104 |
105 | The example above leverages Twitter Bootstrap CSS3 transitions: `fade` and `in`.
106 |
107 | ### Styling Considerations
108 |
109 | Bootstrap 2 has a few styling quirks with the `.alert` and `.fade` classes.
110 |
111 | #### Visible or not
112 |
113 | Some folks may want hidden alerts to take up visible space others may not. Fortunately, each case is easy to achieve by declaring `.alert` as indicated below...
114 |
115 | Takes up no visible space when hidden
116 | ```html
117 |
118 | ...
119 |
120 | ```
121 |
122 | Takes up visible space when hidden
123 | ```html
124 |
125 | ...
126 |
127 | ```
128 |
129 | #### CSS Transition Quirks
130 |
131 | The `.fade` class only transitions opacity and the base `.alert` class has a background color and background border used for alert warnings. Together these styling attributes can make it challenging to achieve smooth transitions.
132 |
133 | Fortunately, its easy to replace the `.alert` class and move the warning colors to `.alert-warn` as illustrated below...
134 |
135 | Styling
136 | ```css
137 | /* Remove colors and add transition property */
138 | .alert-flash {
139 | padding: 8px 35px 8px 14px;
140 | margin-bottom: 20px;
141 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
142 | border: 1px solid transparent;
143 | -webkit-border-radius: 4px;
144 | -moz-border-radius: 4px;
145 | border-radius: 4px;
146 |
147 | /* change transition property to all */
148 | -webkit-transition-property: all;
149 | transition-property: all;
150 | }
151 |
152 | .alert-flash h4 {
153 | margin: 0;
154 | }
155 |
156 | .alert-flash .close {
157 | position: relative;
158 | top: -2px;
159 | right: -21px;
160 | line-height: 20px;
161 | }
162 |
163 | /* add warning colors to warn class */
164 | .alert-warn {
165 | background-color: #fcf8e3;
166 | border: 1px solid #fbeed5;
167 | }
168 |
169 | .alert-warn,
170 | .alert-warn h4 {
171 | color: #c09853;
172 | }
173 | ```
174 |
175 | Template:
176 | ```html
177 |
178 |
179 | Ahem...
180 | {{flash.message}}
181 |
182 | ```
183 |
184 | ### FlashProvider API
185 |
186 | ```javascript
187 | flashProvider.errorClassnames
188 | flashProvider.warnClassnames
189 | flashProvider.infoClassnames
190 | flashProvider.successClassnames
191 | ```
192 |
193 | ### Flash Service API
194 |
195 | #### Properties
196 | Set and get flash messages with the following flash properties...
197 |
198 | * success
199 | * info
200 | * warn
201 | * error
202 |
203 | #### Methods
204 |
205 | ##### subscribe(listener, [type])
206 | Register a subscriber callback function to be notified of flash messages. The subscriber function has two arguments: `message` and `type`.
207 |
208 | ##### clean()
209 | Clear all subscribers and flash messages.
210 |
211 | ## Contributing
212 |
213 | ### Prerequisites
214 |
215 | The project requires [Bower](http://bower.io), [Grunt](http://gruntjs.com), and [PhantomJS](http://phantomjs.org). Once you have installed them, you can build, test, and run the project.
216 |
217 | ### Build & Test
218 |
219 | To build and run tests, run either...
220 |
221 | ```bash
222 | $ make install
223 | ```
224 |
225 | or
226 |
227 | ```bash
228 | $ npm install
229 | $ bower install
230 | $ grunt install
231 | ```
232 |
233 | ### Demo & Develop
234 |
235 | To run a live demo or do some hackery, run...
236 |
237 | ```bash
238 | $ grunt server
239 | ```
240 |
--------------------------------------------------------------------------------
/src/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node": false,
3 | "browser": true,
4 | "jquery": true,
5 | "es5": true,
6 | "bitwise": true,
7 | "camelcase": false,
8 | "latedef": true,
9 | "boss": false,
10 | "curly": true,
11 | "debug": false,
12 | "devel": false,
13 | "eqeqeq": true,
14 | "evil": true,
15 | "forin": true,
16 | "immed": true,
17 | "laxbreak": false,
18 | "newcap": true,
19 | "noarg": true,
20 | "noempty": true,
21 | "nonew": true,
22 | "nomen": false,
23 | "onevar": false,
24 | "plusplus": true,
25 | "regexp": false,
26 | "undef": true,
27 | "sub": true,
28 | "strict": true,
29 | "white": false,
30 | "unused": true,
31 | "smarttabs": false,
32 | "indent": 4,
33 | "quotmark": "single",
34 | "globals": {
35 | "angular": true
36 | }
37 | }
--------------------------------------------------------------------------------
/src/directives/flash-alert-directive.js:
--------------------------------------------------------------------------------
1 | /* global angular */
2 |
3 | (function () {
4 | 'use strict';
5 |
6 | function isBlank(str) {
7 | if (str === null || str === undefined) {
8 | str = '';
9 | }
10 | return (/^\s*$/).test(str);
11 | }
12 |
13 | function flashAlertDirective(flash, $timeout) {
14 | return {
15 | scope: true,
16 | link: function ($scope, element, attr) {
17 | var timeoutHandle, subscribeHandle;
18 |
19 | $scope.flash = {};
20 |
21 | $scope.hide = function () {
22 | removeAlertClasses();
23 | if (!isBlank(attr.activeClass)) {
24 | element.removeClass(attr.activeClass);
25 | }
26 | };
27 |
28 | $scope.$on('$destroy', function () {
29 | flash.clean();
30 | flash.unsubscribe(subscribeHandle);
31 | });
32 |
33 | function removeAlertClasses() {
34 | var classnames = [].concat(flash.classnames.error, flash.classnames.warn, flash.classnames.info, flash.classnames.success);
35 | angular.forEach(classnames, function (clazz) {
36 | element.removeClass(clazz);
37 | });
38 | }
39 |
40 | function show(message, type) {
41 | if (timeoutHandle) {
42 | $timeout.cancel(timeoutHandle);
43 | }
44 |
45 | $scope.flash.type = type;
46 | $scope.flash.message = message;
47 | removeAlertClasses();
48 | angular.forEach(flash.classnames[type], function (clazz) {
49 | element.addClass(clazz);
50 | });
51 |
52 | if (!isBlank(attr.activeClass)) {
53 | element.addClass(attr.activeClass);
54 | }
55 |
56 | if (!message) {
57 | $scope.hide();
58 | return;
59 | }
60 |
61 | var delay = Number(attr.duration || 5000);
62 | if (delay > 0) {
63 | timeoutHandle = $timeout($scope.hide, delay);
64 | }
65 | }
66 |
67 | subscribeHandle = flash.subscribe(show, attr.flashAlert, attr.id);
68 |
69 | /**
70 | * Fixes timing issues: display the last flash message sent before this directive subscribed.
71 | */
72 |
73 | if (attr.flashAlert && flash[attr.flashAlert]) {
74 | show(flash[attr.flashAlert], attr.flashAlert);
75 | }
76 |
77 | if (!attr.flashAlert && flash.message) {
78 | show(flash.message, flash.type);
79 | }
80 |
81 | }
82 | };
83 | }
84 |
85 | angular.module('angular-flash.flash-alert-directive', ['angular-flash.service'])
86 | .directive('flashAlert', ['flash', '$timeout', flashAlertDirective]);
87 |
88 | }());
89 |
--------------------------------------------------------------------------------
/src/services/flash-service.js:
--------------------------------------------------------------------------------
1 | /* global angular */
2 |
3 | (function () {
4 | 'use strict';
5 |
6 | var subscriberCount = 0;
7 |
8 | var Flash = function (options) {
9 | var _options = angular.extend({
10 | id: null,
11 | subscribers: {},
12 | classnames: {
13 | error: [],
14 | warn: [],
15 | info: [],
16 | success: []
17 | }
18 | }, options);
19 |
20 | var _self = this;
21 | var _success;
22 | var _info;
23 | var _warn;
24 | var _error;
25 | var _type;
26 |
27 | function _notify(type, message) {
28 | angular.forEach(_options.subscribers, function (subscriber) {
29 | var matchesType = !subscriber.type || subscriber.type === type;
30 | var matchesId = (!_options.id && !subscriber.id) || subscriber.id === _options.id;
31 | if (matchesType && matchesId) {
32 | subscriber.cb(message, type);
33 | }
34 | });
35 | }
36 |
37 | this.clean = function () {
38 | _success = null;
39 | _info = null;
40 | _warn = null;
41 | _error = null;
42 | _type = null;
43 | };
44 |
45 | this.subscribe = function (subscriber, type, id) {
46 | subscriberCount += 1;
47 | _options.subscribers[subscriberCount] = {
48 | cb: subscriber,
49 | type: type,
50 | id: id
51 | };
52 | return subscriberCount;
53 | };
54 |
55 | this.unsubscribe = function (handle) {
56 | delete _options.subscribers[handle];
57 | };
58 |
59 | this.to = function (id) {
60 | var options = angular.copy(_options);
61 | options.id = id;
62 | return new Flash(options);
63 | };
64 |
65 | Object.defineProperty(this, 'success', {
66 | get: function () {
67 | return _success;
68 | },
69 | set: function (message) {
70 | _success = message;
71 | _type = 'success';
72 | _notify(_type, message);
73 | }
74 | });
75 |
76 | Object.defineProperty(this, 'info', {
77 | get: function () {
78 | return _info;
79 | },
80 | set: function (message) {
81 | _info = message;
82 | _type = 'info';
83 | _notify(_type, message);
84 | }
85 | });
86 |
87 | Object.defineProperty(this, 'warn', {
88 | get: function () {
89 | return _warn;
90 | },
91 | set: function (message) {
92 | _warn = message;
93 | _type = 'warn';
94 | _notify(_type, message);
95 | }
96 | });
97 |
98 | Object.defineProperty(this, 'error', {
99 | get: function () {
100 | return _error;
101 | },
102 | set: function (message) {
103 | _error = message;
104 | _type = 'error';
105 | _notify(_type, message);
106 | }
107 | });
108 |
109 | Object.defineProperty(this, 'type', {
110 | get: function () {
111 | return _type;
112 | }
113 | });
114 |
115 | Object.defineProperty(this, 'message', {
116 | get: function () {
117 | return _type ? _self[_type] : null;
118 | }
119 | });
120 |
121 | Object.defineProperty(this, 'classnames', {
122 | get: function () {
123 | return _options.classnames;
124 | }
125 | });
126 |
127 | Object.defineProperty(this, 'id', {
128 | get: function () {
129 | return _options.id;
130 | }
131 | });
132 | };
133 |
134 | angular.module('angular-flash.service', [])
135 | .provider('flash', function () {
136 | var _self = this;
137 | this.errorClassnames = ['alert-error'];
138 | this.warnClassnames = ['alert-warn'];
139 | this.infoClassnames = ['alert-info'];
140 | this.successClassnames = ['alert-success'];
141 |
142 | this.$get = function () {
143 | return new Flash({
144 | classnames: {
145 | error: _self.errorClassnames,
146 | warn: _self.warnClassnames,
147 | info: _self.infoClassnames,
148 | success: _self.successClassnames
149 | }
150 | });
151 | };
152 | });
153 |
154 | }());
155 |
--------------------------------------------------------------------------------
/test/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node": false,
3 | "browser": true,
4 | "jquery": true,
5 | "es5": true,
6 | "bitwise": true,
7 | "camelcase": false,
8 | "latedef": true,
9 | "boss": false,
10 | "curly": true,
11 | "debug": false,
12 | "devel": false,
13 | "eqeqeq": true,
14 | "evil": true,
15 | "forin": true,
16 | "immed": true,
17 | "laxbreak": false,
18 | "newcap": true,
19 | "noarg": true,
20 | "noempty": true,
21 | "nonew": true,
22 | "nomen": false,
23 | "onevar": false,
24 | "plusplus": true,
25 | "regexp": false,
26 | "undef": true,
27 | "sub": true,
28 | "strict": true,
29 | "white": false,
30 | "unused": true,
31 | "smarttabs": false,
32 | "indent": 4,
33 | "quotmark": "single",
34 | "globals": {
35 | "angular": true,
36 | "module": true,
37 | "describe": true,
38 | "beforeEach": true,
39 | "afterEach": true,
40 | "it": true,
41 | "inject": true,
42 | "expect": true,
43 | "jasmine": true
44 | }
45 |
46 | }
--------------------------------------------------------------------------------
/test/unit/directives/flash-alert-directive-spec.js:
--------------------------------------------------------------------------------
1 | describe('flash-alert-directive', function () {
2 | 'use strict';
3 |
4 | beforeEach(function () {
5 | module('angular-flash.service', 'angular-flash.flash-alert-directive');
6 |
7 | module(function ($provide, flashProvider) {
8 |
9 | flashProvider.errorClassnames.push('alert-danger');
10 |
11 | $provide.decorator('$timeout', function ($delegate, $browser) {
12 | var spy = jasmine.createSpy('$timeout').andCallFake($delegate);
13 | spy.flush = function () {
14 | $browser.defer.flush();
15 | };
16 | spy.cancel = $delegate.cancel;
17 | return spy;
18 | });
19 | });
20 |
21 | });
22 |
23 |
24 | it('should display all flash messages', inject(function ($rootScope, $compile, $timeout, flash) {
25 |
26 | var template = [
27 | '',
28 | '{{flash.type}} ',
29 | '{{flash.message}} ',
30 | '
'
31 | ];
32 |
33 | var element = angular.element(template.join('\n'));
34 | $compile(element)($rootScope);
35 | $rootScope.$digest();
36 |
37 | expect(element.find('.alert-heading').text()).toBe('');
38 | expect(element.find('.alert-message').text()).toBe('');
39 | expect(element.hasClass('alert-error')).toBe(false);
40 | expect(element.hasClass('alert-danger')).toBe(false);
41 | expect(element.hasClass('in')).toBe(false);
42 |
43 | flash.error = ':error-message';
44 | $rootScope.$digest();
45 |
46 | expect(element.find('.alert-heading').text()).toBe('error');
47 | expect(element.find('.alert-message').text()).toBe(':error-message');
48 | expect(element.hasClass('alert-error')).toBe(true);
49 | expect(element.hasClass('alert-danger')).toBe(true);
50 | expect(element.hasClass('in')).toBe(true);
51 |
52 | $timeout.flush();
53 |
54 | expect(element.find('.alert-heading').text()).toBe('error');
55 | expect(element.find('.alert-message').text()).toBe(':error-message');
56 | expect(element.hasClass('alert-error')).toBe(false);
57 | expect(element.hasClass('in')).toBe(false);
58 |
59 | flash.success = ':success-message';
60 | $rootScope.$digest();
61 |
62 |
63 | expect(element.find('.alert-heading').text()).toBe('success');
64 | expect(element.find('.alert-message').text()).toBe(':success-message');
65 | expect(element.hasClass('alert-success')).toBe(true);
66 | expect(element.hasClass('in')).toBe(true);
67 |
68 | $timeout.flush();
69 |
70 | expect(element.find('.alert-heading').text()).toBe('success');
71 | expect(element.find('.alert-message').text()).toBe(':success-message');
72 | expect(element.hasClass('alert-success')).toBe(false);
73 | expect(element.hasClass('in')).toBe(false);
74 |
75 | }));
76 |
77 | it('should display only error flash messages', inject(function ($rootScope, $compile, $timeout, flash) {
78 |
79 | var template = [
80 | '',
81 | '{{flash.type}} ',
82 | '{{flash.message}} ',
83 | '
'
84 | ];
85 |
86 | var element = angular.element(template.join('\n'));
87 | $compile(element)($rootScope);
88 | $rootScope.$digest();
89 |
90 | expect(element.find('.alert-heading').text()).toBe('');
91 | expect(element.find('.alert-message').text()).toBe('');
92 | expect(element.hasClass('alert-error')).toBe(false);
93 | expect(element.hasClass('alert-danger')).toBe(false);
94 | expect(element.hasClass('in')).toBe(false);
95 |
96 | flash.error = ':error-message';
97 | $rootScope.$digest();
98 |
99 | expect(element.find('.alert-heading').text()).toBe('error');
100 | expect(element.find('.alert-message').text()).toBe(':error-message');
101 | expect(element.hasClass('alert-error')).toBe(true);
102 | expect(element.hasClass('alert-danger')).toBe(true);
103 | expect(element.hasClass('in')).toBe(true);
104 |
105 | $timeout.flush();
106 |
107 | expect(element.find('.alert-heading').text()).toBe('error');
108 | expect(element.find('.alert-message').text()).toBe(':error-message');
109 | expect(element.hasClass('alert-error')).toBe(false);
110 | expect(element.hasClass('alert-danger')).toBe(false);
111 | expect(element.hasClass('in')).toBe(false);
112 |
113 | flash.success = ':success-message';
114 | $rootScope.$digest();
115 |
116 | expect(element.find('.alert-heading').text()).toBe('error');
117 | expect(element.find('.alert-message').text()).toBe(':error-message');
118 | expect(element.hasClass('alert-success')).toBe(false);
119 | expect(element.hasClass('in')).toBe(false);
120 |
121 | }));
122 |
123 | it('should only display the most recent flash message', inject(function ($rootScope, $compile, $timeout, flash) {
124 | var template = [
125 | '',
126 | '{{flash.type}} ',
127 | '{{flash.message}} ',
128 | '
'
129 | ];
130 |
131 | var element = angular.element(template.join('\n'));
132 | $compile(element)($rootScope);
133 | $rootScope.$digest();
134 |
135 | expect(element.find('.alert-heading').text()).toBe('');
136 | expect(element.find('.alert-message').text()).toBe('');
137 | expect(element.hasClass('alert-error')).toBe(false);
138 | expect(element.hasClass('alert-danger')).toBe(false);
139 | expect(element.hasClass('alert-success')).toBe(false);
140 | expect(element.hasClass('alert-info')).toBe(false);
141 | expect(element.hasClass('alert-warning')).toBe(false);
142 | expect(element.hasClass('in')).toBe(false);
143 |
144 | flash.info = ':info-message';
145 | flash.success = ':success-message';
146 | $rootScope.$digest();
147 |
148 | expect(element.find('.alert-heading').text()).toBe('success');
149 | expect(element.find('.alert-message').text()).toBe(':success-message');
150 | expect(element.hasClass('alert-error')).toBe(false);
151 | expect(element.hasClass('alert-danger')).toBe(false);
152 | expect(element.hasClass('alert-success')).toBe(true);
153 | expect(element.hasClass('alert-info')).toBe(false);
154 | expect(element.hasClass('alert-warning')).toBe(false);
155 | expect(element.hasClass('in')).toBe(true);
156 |
157 | $timeout.flush();
158 |
159 | expect(element.find('.alert-heading').text()).toBe('success');
160 | expect(element.find('.alert-message').text()).toBe(':success-message');
161 | expect(element.hasClass('alert-error')).toBe(false);
162 | expect(element.hasClass('alert-danger')).toBe(false);
163 | expect(element.hasClass('alert-success')).toBe(false);
164 | expect(element.hasClass('alert-info')).toBe(false);
165 | expect(element.hasClass('alert-warning')).toBe(false);
166 | expect(element.hasClass('in')).toBe(false);
167 |
168 | }));
169 |
170 | it('should appear for 5 seconds with no duration attribute', inject(function ($rootScope, $compile, $timeout, flash) {
171 | var template = [
172 | '',
173 | '{{flash.type}} ',
174 | '{{flash.message}} ',
175 | '
'
176 | ];
177 |
178 | var element = angular.element(template.join('\n'));
179 | $compile(element)($rootScope);
180 |
181 | flash.success = ':success-message';
182 | $rootScope.$digest();
183 |
184 | expect($timeout).toHaveBeenCalledWith(jasmine.any(Function), 5000);
185 | }));
186 |
187 | it('should appear for the number of msec specified by the duration attribute', inject(function ($rootScope, $compile, $timeout, flash) {
188 | var template = [
189 | '',
190 | '{{flash.type}} ',
191 | '{{flash.message}} ',
192 | '
'
193 | ];
194 |
195 | var element = angular.element(template.join('\n'));
196 | $compile(element)($rootScope);
197 |
198 | flash.success = ':success-message';
199 | $rootScope.$digest();
200 |
201 | expect($timeout).toHaveBeenCalledWith(jasmine.any(Function), 3000);
202 | }));
203 |
204 |
205 | it('should not fade away with duration set to 0', inject(function ($rootScope, $compile, $timeout, flash) {
206 | var template = [
207 | '',
208 | '{{flash.type}} ',
209 | '{{flash.message}} ',
210 | '
'
211 | ];
212 |
213 | var element = angular.element(template.join('\n'));
214 | $compile(element)($rootScope);
215 |
216 | flash.success = ':success-message';
217 | $rootScope.$digest();
218 |
219 | expect($timeout.wasCalled).toBe(false);
220 |
221 | }));
222 |
223 | it('should should display the error message even if the message was set before the directive subscribed', inject(function ($rootScope, $compile, flash) {
224 | var template = [
225 | '',
226 | '{{flash.type}} ',
227 | '{{flash.message}} ',
228 | '
'
229 | ];
230 |
231 | flash.error = ':error-message';
232 |
233 | var element = angular.element(template.join('\n'));
234 |
235 | $compile(element)($rootScope);
236 | $rootScope.$digest();
237 |
238 | expect(element.find('.alert-heading').text()).toBe('error');
239 | expect(element.find('.alert-message').text()).toBe(':error-message');
240 | expect(element.hasClass('alert-error')).toBe(true);
241 | expect(element.hasClass('alert-danger')).toBe(true);
242 | expect(element.hasClass('in')).toBe(true);
243 | }));
244 |
245 | it('should should display the any message even if the message was set before the directive subscribed', inject(function ($rootScope, $compile, flash) {
246 | var template = [
247 | '',
248 | '{{flash.type}} ',
249 | '{{flash.message}} ',
250 | '
'
251 | ];
252 |
253 | flash.error = ':error-message';
254 |
255 | var element = angular.element(template.join('\n'));
256 |
257 | $compile(element)($rootScope);
258 | $rootScope.$digest();
259 |
260 | expect(element.find('.alert-heading').text()).toBe('error');
261 | expect(element.find('.alert-message').text()).toBe(':error-message');
262 | expect(element.hasClass('alert-error')).toBe(true);
263 | expect(element.hasClass('alert-danger')).toBe(true);
264 | expect(element.hasClass('in')).toBe(true);
265 | }));
266 |
267 | it('should display the error message even if the error message was not the last message', inject(function ($rootScope, $compile, flash) {
268 | var template = [
269 | '',
270 | '{{flash.type}} ',
271 | '{{flash.message}} ',
272 | '
'
273 | ];
274 |
275 | flash.error = ':error-message';
276 | flash.success = ':success-message';
277 |
278 | var element = angular.element(template.join('\n'));
279 |
280 | $compile(element)($rootScope);
281 | $rootScope.$digest();
282 |
283 | expect(element.find('.alert-heading').text()).toBe('error');
284 | expect(element.find('.alert-message').text()).toBe(':error-message');
285 | expect(element.hasClass('alert-error')).toBe(true);
286 | expect(element.hasClass('alert-danger')).toBe(true);
287 | expect(element.hasClass('in')).toBe(true);
288 | }));
289 |
290 | it('should hide the alert if the flash message is falsey', inject(function ($rootScope, $compile, flash) {
291 | var template = [
292 | '',
293 | '{{flash.type}} ',
294 | '{{flash.message}} ',
295 | '
'
296 | ];
297 |
298 | var element = angular.element(template.join('\n'));
299 | $compile(element)($rootScope);
300 | $rootScope.$digest();
301 |
302 | expect(element.find('.alert-heading').text()).toBe('');
303 | expect(element.find('.alert-message').text()).toBe('');
304 | expect(element.hasClass('alert-error')).toBe(false);
305 | expect(element.hasClass('alert-danger')).toBe(false);
306 | expect(element.hasClass('in')).toBe(false);
307 |
308 | flash.error = ':error-message';
309 | $rootScope.$digest();
310 |
311 | expect(element.find('.alert-heading').text()).toBe('error');
312 | expect(element.find('.alert-message').text()).toBe(':error-message');
313 | expect(element.hasClass('alert-error')).toBe(true);
314 | expect(element.hasClass('alert-danger')).toBe(true);
315 | expect(element.hasClass('in')).toBe(true);
316 |
317 | flash.error = '';
318 | $rootScope.$digest();
319 |
320 | expect(element.find('.alert-heading').text()).toBe('error');
321 | expect(element.find('.alert-message').text()).toBe('');
322 | expect(element.hasClass('alert-error')).toBe(false);
323 | expect(element.hasClass('alert-danger')).toBe(false);
324 | expect(element.hasClass('in')).toBe(false);
325 |
326 | }));
327 |
328 | describe('scope destroy', function () {
329 |
330 | it('should clean the flash service when the directive scope is destroyed', inject(function ($rootScope, $compile, flash) {
331 | var template = [
332 | '',
333 | '{{flash.type}} ',
334 | '{{flash.message}} ',
335 | '
'
336 | ];
337 |
338 | flash.error = ':error-message';
339 | flash.success = ':success-message';
340 |
341 | var element = angular.element(template.join('\n'));
342 |
343 | var $scope = $rootScope.$new();
344 |
345 | spyOn(flash, 'clean').andCallThrough();
346 | spyOn(flash, 'unsubscribe').andCallThrough();
347 |
348 | $compile(element)($scope);
349 | $scope.$digest();
350 |
351 | expect(element.find('.alert-heading').text()).toBe('error');
352 | expect(element.find('.alert-message').text()).toBe(':error-message');
353 | expect(element.hasClass('alert-error')).toBe(true);
354 | expect(element.hasClass('alert-danger')).toBe(true);
355 | expect(element.hasClass('in')).toBe(true);
356 |
357 | $scope.$destroy();
358 |
359 | expect(flash.clean).toHaveBeenCalled();
360 | expect(flash.unsubscribe).toHaveBeenCalledWith(jasmine.any(Number));
361 | expect(flash.message).toBeNull();
362 |
363 | }));
364 |
365 | it('should not unsubscribe subscribers when the directive scope is destroyed', inject(function ($rootScope, $compile, flash) {
366 | var template = [
367 | '',
368 | '{{flash.type}} ',
369 | '{{flash.message}} ',
370 | '
'
371 | ];
372 |
373 | var element1 = angular.element(template.join('\n'));
374 | var element2 = angular.element(template.join('\n'));
375 |
376 | var $scope1 = $rootScope.$new();
377 | var $scope2 = $rootScope.$new();
378 |
379 | spyOn(flash, 'clean').andCallThrough();
380 | spyOn(flash, 'unsubscribe').andCallThrough();
381 |
382 | $compile(element1)($scope1);
383 | $compile(element2)($scope2);
384 |
385 | flash.success = ':success-message';
386 |
387 | $scope1.$digest();
388 | $scope2.$digest();
389 |
390 | $scope1.$destroy();
391 |
392 | flash.error = ':error-message';
393 |
394 | $scope1.$digest();
395 | $scope2.$digest();
396 |
397 | expect(flash.clean.calls.length).toEqual(1);
398 | expect(flash.unsubscribe.calls.length).toEqual(1);
399 |
400 | expect(element1.find('.alert-heading').text()).toBe('success');
401 | expect(element1.find('.alert-message').text()).toBe(':success-message');
402 | expect(element1.hasClass('alert-success')).toBe(true);
403 | expect(element1.hasClass('in')).toBe(true);
404 |
405 | expect(element2.find('.alert-heading').text()).toBe('error');
406 | expect(element2.find('.alert-message').text()).toBe(':error-message');
407 | expect(element2.hasClass('alert-error')).toBe(true);
408 | expect(element2.hasClass('alert-danger')).toBe(true);
409 | expect(element2.hasClass('in')).toBe(true);
410 | }));
411 | });
412 |
413 |
414 | });
415 |
--------------------------------------------------------------------------------
/test/unit/services/flash-service-spec.js:
--------------------------------------------------------------------------------
1 | describe('FlashService', function () {
2 | 'use strict';
3 |
4 | var _flash;
5 |
6 | beforeEach(module('angular-flash.service'));
7 |
8 | beforeEach(inject(function (flash) {
9 | _flash = flash;
10 | }));
11 |
12 | it('it should send flash messages to subscribers', function () {
13 | var subscriber1 = jasmine.createSpy('subscriber1');
14 | var subscriber2 = jasmine.createSpy('subscriber2');
15 |
16 | _flash.subscribe(subscriber1);
17 | _flash.subscribe(subscriber2);
18 |
19 | _flash.error = ':error-message';
20 | _flash.warn = ':warn-message';
21 | _flash.info = ':info-message';
22 | _flash.success = ':success-message';
23 |
24 | expect(subscriber1).toHaveBeenCalledWith(':error-message', 'error');
25 | expect(subscriber1).toHaveBeenCalledWith(':warn-message', 'warn');
26 | expect(subscriber1).toHaveBeenCalledWith(':info-message', 'info');
27 | expect(subscriber1).toHaveBeenCalledWith(':success-message', 'success');
28 |
29 | expect(subscriber2).toHaveBeenCalledWith(':error-message', 'error');
30 | expect(subscriber2).toHaveBeenCalledWith(':warn-message', 'warn');
31 | expect(subscriber2).toHaveBeenCalledWith(':info-message', 'info');
32 | expect(subscriber2).toHaveBeenCalledWith(':success-message', 'success');
33 |
34 | });
35 |
36 | it('it should send flash messages to subscribers of the right type of flash', function () {
37 | var errorSubscriber = jasmine.createSpy('errorSubscriber');
38 | var warnSubscriber = jasmine.createSpy('warnSubscriber');
39 | var infoSubscriber = jasmine.createSpy('infoSubscriber');
40 | var successSubscriber = jasmine.createSpy('successSubscriber');
41 |
42 | _flash.subscribe(errorSubscriber, 'error');
43 | _flash.subscribe(warnSubscriber, 'warn');
44 | _flash.subscribe(infoSubscriber, 'info');
45 | _flash.subscribe(successSubscriber, 'success');
46 |
47 | _flash.error = ':error-message';
48 | _flash.warn = ':warn-message';
49 | _flash.info = ':info-message';
50 | _flash.success = ':success-message';
51 |
52 | expect(errorSubscriber).toHaveBeenCalledWith(':error-message', 'error');
53 | expect(errorSubscriber.calls.length).toEqual(1);
54 |
55 | expect(warnSubscriber).toHaveBeenCalledWith(':warn-message', 'warn');
56 | expect(warnSubscriber.calls.length).toEqual(1);
57 |
58 | expect(infoSubscriber).toHaveBeenCalledWith(':info-message', 'info');
59 | expect(infoSubscriber.calls.length).toEqual(1);
60 |
61 | expect(successSubscriber).toHaveBeenCalledWith(':success-message', 'success');
62 | expect(successSubscriber.calls.length).toEqual(1);
63 | });
64 |
65 | it('it should send flash messages to the right subscribers', function () {
66 | var subscriber1 = jasmine.createSpy('subscriber1');
67 | var subscriber2 = jasmine.createSpy('subscriber2');
68 |
69 | _flash.subscribe(subscriber1, null, 'foo');
70 | _flash.subscribe(subscriber2, null, 'bar');
71 |
72 | _flash.to('foo').error = 'error 1';
73 |
74 | _flash.to('bar').error = 'error 2';
75 |
76 | _flash.error = ':error-message';
77 |
78 | expect(_flash.id).toBeNull();
79 | expect(_flash.to('foo').id).toEqual('foo');
80 | expect(_flash.to('bar').id).toEqual('bar');
81 |
82 | expect(subscriber1).toHaveBeenCalledWith('error 1', 'error');
83 | expect(subscriber1.calls.length).toEqual(1);
84 |
85 | expect(subscriber2).toHaveBeenCalledWith('error 2', 'error');
86 | expect(subscriber2.calls.length).toEqual(1);
87 | });
88 |
89 | it('the flash getters should return the right message', function () {
90 | _flash.error = ':error-message';
91 | _flash.warn = ':warn-message';
92 | _flash.info = ':info-message';
93 | _flash.success = ':success-message';
94 |
95 | expect(_flash.error).toBe(':error-message');
96 | expect(_flash.warn).toBe(':warn-message');
97 | expect(_flash.info).toBe(':info-message');
98 | expect(_flash.success).toBe(':success-message');
99 | });
100 |
101 | it('flash.type and flash.message should return the last flash', function () {
102 | _flash.error = ':error-message';
103 | _flash.warn = ':warn-message';
104 |
105 | expect(_flash.type).toBe('warn');
106 | expect(_flash.message).toBe(':warn-message');
107 | });
108 |
109 | });
110 |
--------------------------------------------------------------------------------