├── .gitignore ├── .jshintrc ├── LICENSE ├── README.md ├── bower.json ├── build ├── angular-growl.js ├── angular-growl.min.css └── angular-growl.min.js ├── demo ├── demo.html └── demo.js ├── doc └── screenshot.jpg ├── gruntfile.js ├── karma.conf.js ├── package.json ├── src ├── growl.css ├── growl.js ├── growlDirective.js └── growlFactory.js └── test └── growlDirectiveTest.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | components 4 | bower_components 5 | .idea 6 | *.iml 7 | nbproject 8 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "latedef": true, 6 | "newcap": true, 7 | "noarg": true, 8 | "sub": true, 9 | "undef": true, 10 | "boss": true, 11 | "eqnull": true, 12 | "unused": true, 13 | "strict": true, 14 | "bitwise": true, 15 | "trailing": true, 16 | "regexp": true, 17 | "nonew": true, 18 | "forin": true, 19 | "globals": { 20 | "angular": true, 21 | "console": true, 22 | "define": true, 23 | "require": true, 24 | "document": true, 25 | "describe": true, 26 | "it": true, 27 | "expect": true, 28 | "jasmine": true, 29 | "window": true, 30 | "beforeEach": true, 31 | "module": true, 32 | "inject": true 33 | } 34 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Marco Rinck 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #angular-growl 2 | 3 | > growl like notifications for angularJS projects, using bootstrap alert classes 4 | 5 | ##Features 6 | 7 | ![Standard bootstrap 2.x styles](doc/screenshot.jpg) 8 | 9 | * growl like notifications like in MacOS X 10 | * using standard bootstrap classes (alert, alert-info, alert-error, alert-success) 11 | * global or per message configuration of a timeout when message will be automatically closed 12 | * automatic translation of messages if [angular-translate](https://github.com/PascalPrecht/angular-translate) filter is 13 | present, you only have to provide keys as messages, angular-translate will translate them 14 | * pre-defined $http-Interceptor to automatically handle $http responses for server-sent messages 15 | * automatic CSS animations when adding/closing notifications (only when using >= angularJS 1.2) 16 | * < 1 kB after GZIP 17 | 18 | ##Changelog 19 | 20 | **0.4.0** - 19th Nov 2013 21 | 22 | * updated dependency to angularJS 1.2.x, angular-growl does not work with 1.0.x anymore (BREAKING CHANGE) 23 | * new option: only display unique messages, which is the new default, disable to allow same message more than once (BREAKING CHANGE) 24 | * new option: allow html tags in messages, default is off you need to 25 | 26 | **0.3.1** - 1st Oct 2013 27 | 28 | * bugfix: translating of messages works again 29 | * change: also set alert css classes introduced by bootstrap 3 30 | 31 | **0.3.0** - 26th Sept 2013 32 | 33 | * adding css animations support via ngAnimate (for angularJS >= 1.2) 34 | * ability to configure server message keys 35 | 36 | **0.2.0** - 22nd Sept 2013 37 | 38 | * reworking, bugfixing and documenting handling of server sent messages/notifications 39 | * externalizing css styles of growl class 40 | * provide minified versions of js and css files in build folder 41 | 42 | **0.1.3** - 20th Sept 2013 43 | 44 | * introducing ttl config option, fixes #2 45 | 46 | ##Installation 47 | 48 | You can install angular-growl with bower: 49 | 50 | > bower install angular-growl 51 | 52 | Alternatively you can download the files in the [build folder](build/) manually and include them in your project. 53 | 54 | ````html 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | ```` 66 | 67 | As angular-growl is based on its own angularJS module, you have to alter your dependency list when creating your application 68 | module: 69 | 70 | ````javascript 71 | var app = angular.module('myApp', ['angular-growl']); 72 | ```` 73 | 74 | Finally, you have to include the directive somewhere in your HTML like this: 75 | 76 | ````html 77 | 78 |
79 | 80 | ```` 81 | 82 | ##Usage 83 | 84 | Just let angular inject the growl Factory into your code and call the 4 functions that the factory provides accordingly: 85 | 86 | ````javascript 87 | app.controller("demoCtrl", ['$scope', 'growl', function($scope, growl) { 88 | $scope.addSpecialWarnMessage = function() { 89 | growl.addWarnMessage("This adds a warn message"); 90 | growl.addInfoMessage("This adds a info message"); 91 | growl.addSuccessMessage("This adds a success message"); 92 | growl.addErrorMessage("This adds a error message"); 93 | } 94 | }]); 95 | ```` 96 | 97 | If [angular-translate](https://github.com/PascalPrecht/angular-translate) is present, its filter is automatically called for translating of messages, so you have to provide 98 | only the key: 99 | 100 | ````javascript 101 | app.controller("demoCtrl", ['$scope', 'growl', function($scope, growl) { 102 | $scope.addSpecialWarnMessage = function() { 103 | growl.addSuccessMessage("SAVE_SUCCESS_MESSAGE"); 104 | growl.addErrorMessage("VALIDATION_ERROR"); 105 | } 106 | }]); 107 | ```` 108 | 109 | ##Configuration 110 | 111 | ###Only unique messages 112 | 113 | * Default: true 114 | 115 | Accept only unique messages as a new message. If a message is already displayed (text and severity are the same) then this 116 | message will not be added to the displayed message list. Set to false, to always display all messages regardless if they 117 | are already displayed or not: 118 | 119 | ````javascript 120 | var app = angular.module('myApp', ['angular-growl']); 121 | 122 | app.config(['growlProvider', function(growlProvider) { 123 | growlProvider.onlyUniqueMessages(false); 124 | }]); 125 | ```` 126 | 127 | ###Automatic closing of notifications (timeout, ttl) 128 | 129 | * Default: none (all messages need to be closed manually by the user.) 130 | 131 | However, you can configure a global timeout (TTL) after which notifications should be automatically closed. To do 132 | this, you have to configure this during config phase of angular bootstrap like this: 133 | 134 | ````javascript 135 | var app = angular.module('myApp', ['angular-growl']); 136 | 137 | app.config(['growlProvider', function(growlProvider) { 138 | growlProvider.globalTimeToLive(5000); 139 | }]); 140 | ```` 141 | 142 | This sets a global timeout of 5 seconds after which every notification will be closed. 143 | 144 | You can override TTL generally for every single message if you want: 145 | 146 | ````javascript 147 | app.controller("demoCtrl", ['$scope', 'growl', function($scope, growl) { 148 | $scope.addSpecialWarnMessage = function() { 149 | growl.addWarnMessage("Override global ttl setting", {ttl: 10000}); 150 | } 151 | }]); 152 | ```` 153 | 154 | This sets a 10 second timeout, after which the notification will be automatically closed. 155 | 156 | If you have set a global TTL, you can disable automatic closing of single notifications by setting their ttl to -1: 157 | 158 | ````javascript 159 | app.controller("demoCtrl", ['$scope', 'growl', function($scope, growl) { 160 | $scope.addSpecialWarnMessage = function() { 161 | growl.addWarnMessage("this will not be closed automatically even when a global ttl is set", {ttl: -1}); 162 | } 163 | }]); 164 | ```` 165 | 166 | ###Allow HTML in messages 167 | 168 | * Default: false 169 | 170 | Turn this on to be able to display html tags in messages, default behaviour is to NOT display HTML. 171 | 172 | For this to work, you have to declare a dependency to "ngSanitize" (and load the extra javascript) in your own application 173 | module! 174 | 175 | ````javascript 176 | var app = angular.module('myApp', ['angular-growl', 'ngSanitize']); 177 | 178 | app.config(['growlProvider', function(growlProvider) { 179 | growlProvider.globalEnableHtml(true); 180 | }]); 181 | ```` 182 | 183 | You can override the global option and allow HTML tags in single messages too: 184 | 185 | ````javascript 186 | app.controller("demoCtrl", ['$scope', 'growl', function($scope, growl) { 187 | $scope.addSpecialWarnMessage = function() { 188 | growl.addWarnMessage("This is a HTML message", {enableHtml: true}); 189 | } 190 | }]); 191 | ```` 192 | 193 | ###Animations 194 | 195 | Beginning with angularJS 1.2 growl messages can be automatically animated with CSS animations when adding and/or closing 196 | them. All you have to do is load the angular-animate.js provided by angularJS and add **ngAnimate** to your applications 197 | dependency list: 198 | 199 | ````html 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | ```` 211 | 212 | ````javascript 213 | var app = angular.module('myApp', ['angular-growl', 'ngAnimate']); 214 | ```` 215 | 216 | That's it. The angular-growl.css comes with a pre-defined animation of 0.5s to opacity. 217 | 218 | To configure the animations, just change the _growl-item.*_ classes in the css file to your preference. F.i. to change length 219 | of animation from 0.5s to 1s do this: 220 | 221 | ````css 222 | .growl-item.ng-enter, 223 | .growl-item.ng-leave { 224 | -webkit-transition:1s linear all; 225 | -moz-transition:1s linear all; 226 | -o-transition:1s linear all; 227 | transition:1s linear all; 228 | } 229 | ```` 230 | 231 | Basically you can style your animations just as you like if ngAnimate can pick it up automatically. See the [ngAnimate 232 | docs](http://docs.angularjs.org/api/ngAnimate) for more info. 233 | 234 | ###Handling of server sent notifications 235 | 236 | When doing $http requests, you can configure angular-growl to look automatically for messages in $http responses, so your 237 | business logic on the server is able to send messages/notifications to the client and you can display them automagically: 238 | 239 | ````javascript 240 | var app = angular.module('myApp', ['angular-growl']); 241 | 242 | app.config(['growlProvider', '$httpProvider', function(growlProvider, $httpProvider) { 243 | $httpProvider.responseInterceptors.push(growlProvider.serverMessagesInterceptor); 244 | }]); 245 | ```` 246 | 247 | This adds a pre-defined angularJS HTTP interceptor that is called on every HTTP request and looks if response contains 248 | messages. Interceptor looks in response for a "messages" array of objects with "text" and "severity" key. This is an example 249 | response which results in 3 growl messages: 250 | 251 | ````json 252 | { 253 | "someOtherData": {...}, 254 | "messages": [ 255 | {"text":"this is a server message", "severity": "warn"}, 256 | {"text":"this is another server message", "severity": "info"}, 257 | {"text":"and another", "severity": "error"} 258 | ] 259 | } 260 | ```` 261 | 262 | You can configure the keys, the interceptor is looking for like this: 263 | 264 | ````javascript 265 | var app = angular.module("demo", ["angular-growl"]); 266 | 267 | app.config(["growlProvider", "$httpProvider", function(growlProvider, $httpProvider) { 268 | growlProvider.messagesKey("my-messages"); 269 | growlProvider.messageTextKey("messagetext"); 270 | growlProvider.messageSeverityKey("severity-level"); 271 | $httpProvider.responseInterceptors.push(growlProvider.serverMessagesInterceptor); 272 | }]); 273 | ```` 274 | 275 | Server messages will be created with default TTL. 276 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Marco Rinck", 3 | "name": "angular-growl", 4 | "description": "growl like notifications for angularJS projects, using bootstrap alert classes", 5 | "version": "0.4.0", 6 | "homepage": "https://github.com/marcorinck/angular-growl", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/marcorinck/angular-growl" 10 | }, 11 | "license": "MIT", 12 | "main": "./build/angular-growl.js", 13 | "ignore": [ 14 | "src", 15 | "test", 16 | ".jshintrc", 17 | "package.json", 18 | "gruntfile.js", 19 | "karma.conf.js", 20 | "bower.json", 21 | "demo", 22 | ".gitignore" 23 | ], 24 | "dependencies": { 25 | "angular": "1.2.1" 26 | }, 27 | "devDependencies": { 28 | "angular-mocks": "1.2.1" 29 | } 30 | } -------------------------------------------------------------------------------- /build/angular-growl.js: -------------------------------------------------------------------------------- 1 | /** 2 | * angular-growl - v0.4.0 - 2013-11-19 3 | * https://github.com/marcorinck/angular-growl 4 | * Copyright (c) 2013 Marco Rinck; Licensed MIT 5 | */ 6 | angular.module('angular-growl', []); 7 | angular.module('angular-growl').directive('growl', [ 8 | '$rootScope', 9 | function ($rootScope) { 10 | 'use strict'; 11 | return { 12 | restrict: 'A', 13 | template: '
' + '\t
' + '\t\t' + '
' + '
' + '
' + '
' + '\t
' + '
', 14 | replace: false, 15 | scope: true, 16 | controller: [ 17 | '$scope', 18 | '$timeout', 19 | 'growl', 20 | function ($scope, $timeout, growl) { 21 | var onlyUnique = growl.onlyUnique(); 22 | $scope.messages = []; 23 | function addMessage(message) { 24 | $scope.messages.push(message); 25 | if (message.ttl && message.ttl !== -1) { 26 | $timeout(function () { 27 | $scope.deleteMessage(message); 28 | }, message.ttl); 29 | } 30 | } 31 | $rootScope.$on('growlMessage', function (event, message) { 32 | var found; 33 | if (onlyUnique) { 34 | angular.forEach($scope.messages, function (msg) { 35 | if (message.text === msg.text && message.severity === msg.severity) { 36 | found = true; 37 | } 38 | }); 39 | if (!found) { 40 | addMessage(message); 41 | } 42 | } else { 43 | addMessage(message); 44 | } 45 | }); 46 | $scope.deleteMessage = function (message) { 47 | var index = $scope.messages.indexOf(message); 48 | if (index > -1) { 49 | $scope.messages.splice(index, 1); 50 | } 51 | }; 52 | $scope.computeClasses = function (message) { 53 | return { 54 | 'alert-success': message.severity === 'success', 55 | 'alert-error': message.severity === 'error', 56 | 'alert-danger': message.severity === 'error', 57 | 'alert-info': message.severity === 'info', 58 | 'alert-warning': message.severity === 'warn' 59 | }; 60 | }; 61 | } 62 | ] 63 | }; 64 | } 65 | ]); 66 | angular.module('angular-growl').provider('growl', function () { 67 | 'use strict'; 68 | var _ttl = null, _enableHtml = false, _messagesKey = 'messages', _messageTextKey = 'text', _messageSeverityKey = 'severity', _onlyUniqueMessages = true; 69 | this.globalTimeToLive = function (ttl) { 70 | _ttl = ttl; 71 | }; 72 | this.globalEnableHtml = function (enableHtml) { 73 | _enableHtml = enableHtml; 74 | }; 75 | this.messagesKey = function (messagesKey) { 76 | _messagesKey = messagesKey; 77 | }; 78 | this.messageTextKey = function (messageTextKey) { 79 | _messageTextKey = messageTextKey; 80 | }; 81 | this.messageSeverityKey = function (messageSeverityKey) { 82 | _messageSeverityKey = messageSeverityKey; 83 | }; 84 | this.onlyUniqueMessages = function (onlyUniqueMessages) { 85 | _onlyUniqueMessages = onlyUniqueMessages; 86 | }; 87 | this.serverMessagesInterceptor = [ 88 | '$q', 89 | 'growl', 90 | function ($q, growl) { 91 | function checkResponse(response) { 92 | if (response.data[_messagesKey] && response.data[_messagesKey].length > 0) { 93 | growl.addServerMessages(response.data[_messagesKey]); 94 | } 95 | } 96 | function success(response) { 97 | checkResponse(response); 98 | return response; 99 | } 100 | function error(response) { 101 | checkResponse(response); 102 | return $q.reject(response); 103 | } 104 | return function (promise) { 105 | return promise.then(success, error); 106 | }; 107 | } 108 | ]; 109 | this.$get = [ 110 | '$rootScope', 111 | '$filter', 112 | function ($rootScope, $filter) { 113 | var translate; 114 | try { 115 | translate = $filter('translate'); 116 | } catch (e) { 117 | } 118 | function broadcastMessage(message) { 119 | if (translate) { 120 | message.text = translate(message.text); 121 | } 122 | $rootScope.$broadcast('growlMessage', message); 123 | } 124 | function sendMessage(text, config, severity) { 125 | var _config = config || {}, message; 126 | message = { 127 | text: text, 128 | severity: severity, 129 | ttl: _config.ttl || _ttl, 130 | enableHtml: _config.enableHtml || _enableHtml 131 | }; 132 | broadcastMessage(message); 133 | } 134 | function addWarnMessage(text, config) { 135 | sendMessage(text, config, 'warn'); 136 | } 137 | function addErrorMessage(text, config) { 138 | sendMessage(text, config, 'error'); 139 | } 140 | function addInfoMessage(text, config) { 141 | sendMessage(text, config, 'info'); 142 | } 143 | function addSuccessMessage(text, config) { 144 | sendMessage(text, config, 'success'); 145 | } 146 | function addServerMessages(messages) { 147 | var i, message, severity, length; 148 | length = messages.length; 149 | for (i = 0; i < length; i++) { 150 | message = messages[i]; 151 | if (message[_messageTextKey] && message[_messageSeverityKey]) { 152 | switch (message[_messageSeverityKey]) { 153 | case 'warn': 154 | severity = 'warn'; 155 | break; 156 | case 'success': 157 | severity = 'success'; 158 | break; 159 | case 'info': 160 | severity = 'info'; 161 | break; 162 | case 'error': 163 | severity = 'error'; 164 | break; 165 | } 166 | sendMessage(message[_messageTextKey], undefined, severity); 167 | } 168 | } 169 | } 170 | function onlyUnique() { 171 | return _onlyUniqueMessages; 172 | } 173 | return { 174 | addWarnMessage: addWarnMessage, 175 | addErrorMessage: addErrorMessage, 176 | addInfoMessage: addInfoMessage, 177 | addSuccessMessage: addSuccessMessage, 178 | addServerMessages: addServerMessages, 179 | onlyUnique: onlyUnique 180 | }; 181 | } 182 | ]; 183 | }); -------------------------------------------------------------------------------- /build/angular-growl.min.css: -------------------------------------------------------------------------------- 1 | /** 2 | * angular-growl - v0.4.0 - 2013-11-19 3 | * https://github.com/marcorinck/angular-growl 4 | * Copyright (c) 2013 Marco Rinck; Licensed MIT 5 | */ 6 | 7 | .growl{position:fixed;top:10px;right:10px;float:right;width:250px}.growl-item.ng-enter,.growl-item.ng-leave{-webkit-transition:.5s linear all;-moz-transition:.5s linear all;-o-transition:.5s linear all;transition:.5s linear all}.growl-item.ng-enter,.growl-item.ng-leave.ng-leave-active{opacity:0}.growl-item.ng-leave,.growl-item.ng-enter.ng-enter-active{opacity:1} -------------------------------------------------------------------------------- /build/angular-growl.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * angular-growl - v0.4.0 - 2013-11-19 3 | * https://github.com/marcorinck/angular-growl 4 | * Copyright (c) 2013 Marco Rinck; Licensed MIT 5 | */ 6 | angular.module("angular-growl",[]),angular.module("angular-growl").directive("growl",["$rootScope",function(a){"use strict";return{restrict:"A",template:'
',replace:!1,scope:!0,controller:["$scope","$timeout","growl",function(b,c,d){function e(a){b.messages.push(a),a.ttl&&-1!==a.ttl&&c(function(){b.deleteMessage(a)},a.ttl)}var f=d.onlyUnique();b.messages=[],a.$on("growlMessage",function(a,c){var d;f?(angular.forEach(b.messages,function(a){c.text===a.text&&c.severity===a.severity&&(d=!0)}),d||e(c)):e(c)}),b.deleteMessage=function(a){var c=b.messages.indexOf(a);c>-1&&b.messages.splice(c,1)},b.computeClasses=function(a){return{"alert-success":"success"===a.severity,"alert-error":"error"===a.severity,"alert-danger":"error"===a.severity,"alert-info":"info"===a.severity,"alert-warning":"warn"===a.severity}}}]}}]),angular.module("angular-growl").provider("growl",function(){"use strict";var a=null,b=!1,c="messages",d="text",e="severity",f=!0;this.globalTimeToLive=function(b){a=b},this.globalEnableHtml=function(a){b=a},this.messagesKey=function(a){c=a},this.messageTextKey=function(a){d=a},this.messageSeverityKey=function(a){e=a},this.onlyUniqueMessages=function(a){f=a},this.serverMessagesInterceptor=["$q","growl",function(a,b){function d(a){a.data[c]&&a.data[c].length>0&&b.addServerMessages(a.data[c])}function e(a){return d(a),a}function f(b){return d(b),a.reject(b)}return function(a){return a.then(e,f)}}],this.$get=["$rootScope","$filter",function(c,g){function h(a){p&&(a.text=p(a.text)),c.$broadcast("growlMessage",a)}function i(c,d,e){var f,g=d||{};f={text:c,severity:e,ttl:g.ttl||a,enableHtml:g.enableHtml||b},h(f)}function j(a,b){i(a,b,"warn")}function k(a,b){i(a,b,"error")}function l(a,b){i(a,b,"info")}function m(a,b){i(a,b,"success")}function n(a){var b,c,f,g;for(g=a.length,b=0;g>b;b++)if(c=a[b],c[d]&&c[e]){switch(c[e]){case"warn":f="warn";break;case"success":f="success";break;case"info":f="info";break;case"error":f="error"}i(c[d],void 0,f)}}function o(){return f}var p;try{p=g("translate")}catch(q){}return{addWarnMessage:j,addErrorMessage:k,addInfoMessage:l,addSuccessMessage:m,addServerMessages:n,onlyUnique:o}}]}); -------------------------------------------------------------------------------- /demo/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | angular-growl demo 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |

angular-growl Demo site

25 | 26 |
27 |
28 |
29 | 32 | 33 |
34 | 38 | 42 | 46 | 50 |
51 | 52 | 56 | 57 | 61 | 62 | 66 | 67 | 68 | 69 |
70 |
71 | 72 |
73 | 74 |
75 | 76 |
77 | 78 | 79 | -------------------------------------------------------------------------------- /demo/demo.js: -------------------------------------------------------------------------------- 1 | var app = angular.module("demo", ["angular-growl", "ngAnimate", "ngMockE2E"]); 2 | 3 | app.config(["growlProvider", "$httpProvider", function(growlProvider, $httpProvider) { 4 | growlProvider.globalTimeToLive(2000); 5 | growlProvider.messagesKey("my-messages"); 6 | growlProvider.messageTextKey("messagetext"); 7 | growlProvider.messageSeverityKey("severity-level"); 8 | growlProvider.onlyUniqueMessages(true); 9 | $httpProvider.responseInterceptors.push(growlProvider.serverMessagesInterceptor); 10 | 11 | 12 | }]); 13 | 14 | app.run(function($httpBackend) { 15 | //mocking backend to simulate handling server messages 16 | $httpBackend.when('GET', '/mockbackend').respond({ 17 | someData: "fhsdfshfshdfs", 18 | "my-messages": [ 19 | {"messagetext":"this is a server message", "severity-level": "warn"}, 20 | {"messagetext":"this is another server message", "severity-level": "info"}, 21 | {"messagetext":"and another", "severity-level": "error"} 22 | ] 23 | }); 24 | }); 25 | 26 | app.controller("demoCtrl", function demoCtrl($scope, growl, $http) { 27 | 28 | $scope.createMessage = function () { 29 | var config = {}; 30 | if ($scope.timeout) { 31 | config.ttl = $scope.timeout; 32 | } 33 | if ($scope.enableHtml) { 34 | config.enableHtml = $scope.enableHtml; 35 | } 36 | 37 | if ($scope.alertType === "success") { 38 | growl.addSuccessMessage($scope.message, config); 39 | } 40 | 41 | if ($scope.alertType === "warn") { 42 | growl.addWarnMessage($scope.message, config); 43 | } 44 | 45 | if ($scope.alertType === "info") { 46 | growl.addInfoMessage($scope.message, config); 47 | } 48 | 49 | if ($scope.alertType === "error") { 50 | growl.addErrorMessage($scope.message, config); 51 | } 52 | }; 53 | 54 | $scope.simulateServerMessages= function() { 55 | $http.get("/mockbackend").then(function(data) { 56 | console.log(data); 57 | }); 58 | }; 59 | }); -------------------------------------------------------------------------------- /doc/screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcorinck/angular-growl/9eb745aeeeca8f600e0e6afea9a88bb488940cfa/doc/screenshot.jpg -------------------------------------------------------------------------------- /gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | "use strict"; 3 | 4 | grunt.initConfig({ 5 | 6 | pkg: grunt.file.readJSON('bower.json'), 7 | 8 | language: grunt.option('lang') || 'en', 9 | 10 | meta: { 11 | banner: '/**\n * <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' + 12 | '<%= grunt.template.today("yyyy-mm-dd") %>\n' + 13 | ' * <%= pkg.homepage %>\n' + 14 | ' * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author %>;' + 15 | ' Licensed <%= pkg.license %>\n */\n' 16 | }, 17 | 18 | build_dir: 'build', 19 | 20 | lib_files: { 21 | 22 | core: [ 23 | 'src/growl.js', 24 | 'src/growlDirective.js', 25 | 'src/growlFactory.js' 26 | ], 27 | css: [ 28 | 'src/growl.css' 29 | ], 30 | test: ['test/**/*.js'] 31 | }, 32 | 33 | watch: { 34 | 35 | scripts: { 36 | files: ['gruntfile.js', '<%= lib_files.core %>', '<%= lib_files.test %>'], 37 | tasks: ['jshint:all', 'karma:unit'] 38 | }, 39 | 40 | livereload: { 41 | options: { 42 | livereload: true 43 | }, 44 | files: ['src/**/*.*'], 45 | tasks: ['jshint', 'karma:unit'] 46 | } 47 | }, 48 | 49 | jshint: { 50 | options: { 51 | jshintrc: '.jshintrc' 52 | }, 53 | 54 | all: ['gruntfile.js', '<%= lib_files.core %>', '<%= lib_files.test %>'], 55 | 56 | core: { 57 | files: { 58 | src: ['<%= lib_files.core %>'] 59 | } 60 | }, 61 | 62 | test: { 63 | files: { 64 | src: ['<%= lib_files.test %>'] 65 | } 66 | } 67 | }, 68 | 69 | concat: { 70 | banner: { 71 | options: { 72 | banner: '<%= meta.banner %>' 73 | }, 74 | src: '<%= concat.core.dest %>', 75 | dest: '<%= concat.core.dest %>' 76 | }, 77 | 78 | core: { 79 | src: ['<%= lib_files.core %>'], 80 | dest: '<%= build_dir %>/angular-growl.js' 81 | } 82 | }, 83 | 84 | cssmin: { 85 | core: { 86 | files: { 87 | 'build/angular-growl.min.css': '<%= lib_files.css %>' 88 | }, 89 | options: { 90 | 'banner': '<%= meta.banner %>', 91 | 'report': 'gzip' 92 | } 93 | } 94 | }, 95 | 96 | uglify: { 97 | core: { 98 | files: { 99 | '<%= build_dir %>/angular-growl.min.js': '<%= concat.core.dest %>' 100 | }, 101 | options: { 102 | banner: '<%= meta.banner %>', 103 | report: 'gzip' 104 | } 105 | } 106 | }, 107 | 108 | karma: { 109 | unit: { 110 | configFile: 'karma.conf.js', 111 | singleRun: true 112 | } 113 | }, 114 | 115 | ngmin: { 116 | core: { 117 | src: '<%= concat.core.dest %>', 118 | dest: '<%= concat.core.dest %>' 119 | } 120 | }, 121 | push: { 122 | options: { 123 | files: ['package.json', 'bower.json'], 124 | add: true, 125 | addFiles: ['.'], // '.' for all files except ingored files in .gitignore 126 | commit: true, 127 | commitMessage: 'Release v%VERSION%', 128 | commitFiles: ['package.json', 'bower.json', 'build/angular-growl.js', 'build/angular-growl.min.js', 'build/angular-growl.min.css', 'README.md'], // '-a' for all files 129 | createTag: true, 130 | tagName: 'v%VERSION%', 131 | tagMessage: 'Version %VERSION%', 132 | push: true, 133 | pushTo: 'origin', 134 | npm: true, 135 | npmTag: 'Release v%VERSION%', 136 | gitDescribeOptions: '--tags --always --abbrev=1 --dirty=-d' // options to use with '$ git describe' 137 | } 138 | } 139 | }); 140 | 141 | 142 | grunt.registerTask('default', ['jshint:all', 'karma']); 143 | grunt.registerTask('test', ['karma']); 144 | 145 | grunt.registerTask('build', [ 146 | 'jshint:all', 147 | 'karma', 148 | 'build:core' 149 | ]); 150 | 151 | grunt.registerTask('build:core', [ 152 | 'concat:core', 153 | 'ngmin:core', 154 | 'concat:banner', 155 | 'uglify:core', 156 | 'cssmin:core' 157 | ]); 158 | 159 | // For development purpose. 160 | grunt.registerTask('dev', ['jshint', 'karma:unit', 'watch:livereload']); 161 | 162 | require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks); 163 | }; -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Mon Sep 16 2013 12:55:41 GMT+0200 (W. Europe Daylight Time) 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 | 11 | // frameworks to use 12 | frameworks: ['jasmine'], 13 | 14 | 15 | // list of files / patterns to load in the browser 16 | files: [ 17 | 'bower_components/angular/angular.js', 18 | 'bower_components/angular-mocks/angular-mocks.js', 19 | 'src/**/*.js', 20 | 'test/**/*.js' 21 | ], 22 | 23 | 24 | // list of files to exclude 25 | exclude: [ 26 | 27 | ], 28 | 29 | 30 | // test results reporter to use 31 | // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' 32 | reporters: ['progress'], 33 | 34 | 35 | // web server port 36 | port: 9876, 37 | 38 | 39 | // enable / disable colors in the output (reporters and logs) 40 | colors: true, 41 | 42 | 43 | // level of logging 44 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 45 | logLevel: config.LOG_INFO, 46 | 47 | 48 | // enable / disable watching file and executing tests whenever any file changes 49 | autoWatch: false, 50 | 51 | 52 | // Start these browsers, currently available: 53 | // - Chrome 54 | // - ChromeCanary 55 | // - Firefox 56 | // - Opera 57 | // - Safari (only Mac) 58 | // - PhantomJS 59 | // - IE (only Windows) 60 | browsers: ['PhantomJS'], 61 | 62 | 63 | // If browser does not capture in given timeout [ms], kill it 64 | captureTimeout: 60000, 65 | 66 | 67 | // Continuous Integration mode 68 | // if true, it capture browsers, run tests and exit 69 | singleRun: true 70 | }); 71 | }; 72 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-growl", 3 | "version": "0.4.0", 4 | "description": "growl like notifications for angularJS projects, using bootstrap alert classes", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/marcorinck/angular-growl" 8 | }, 9 | "author": { 10 | "name": "Marco Rinck" 11 | }, 12 | "license": "MIT", 13 | "devDependencies": { 14 | "karma": "~0.10.x", 15 | "grunt": "~0.4.1", 16 | "grunt-contrib-clean": "~0.5.0", 17 | "grunt-contrib-copy": "~0.4.1", 18 | "grunt-contrib-concat": "~0.3.x", 19 | "grunt-contrib-jshint": "~0.6.x", 20 | "grunt-contrib-uglify": "~0.2.x", 21 | "grunt-contrib-watch": "~0.5.x", 22 | "grunt-bump": "0.0.2", 23 | "grunt-karma": "~0.7.x", 24 | "grunt-ngmin": "0.0.2", 25 | "grunt-push-release": "0.1.x", 26 | "matchdep": "0.1.2", 27 | "grunt-contrib-cssmin": "~0.6.2" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/growl.css: -------------------------------------------------------------------------------- 1 | .growl { 2 | position: fixed; 3 | top: 10px; 4 | right: 10px; 5 | float: right; 6 | width: 250px; 7 | } 8 | 9 | .growl-item.ng-enter, 10 | .growl-item.ng-leave { 11 | -webkit-transition:0.5s linear all; 12 | -moz-transition:0.5s linear all; 13 | -o-transition:0.5s linear all; 14 | transition:0.5s linear all; 15 | } 16 | 17 | .growl-item.ng-enter, 18 | .growl-item.ng-leave.ng-leave-active { 19 | opacity:0; 20 | } 21 | .growl-item.ng-leave, 22 | .growl-item.ng-enter.ng-enter-active { 23 | opacity:1; 24 | 25 | } -------------------------------------------------------------------------------- /src/growl.js: -------------------------------------------------------------------------------- 1 | angular.module('angular-growl', []); -------------------------------------------------------------------------------- /src/growlDirective.js: -------------------------------------------------------------------------------- 1 | angular.module("angular-growl").directive("growl", ["$rootScope", function ($rootScope) { 2 | "use strict"; 3 | 4 | return { 5 | restrict: 'A', 6 | template: '
' + 7 | '
' + 8 | ' ' + 9 | '
' + 10 | '
' + 11 | '
' + 12 | '
' + 13 | '
' + 14 | '
', 15 | replace: false, 16 | scope: true, 17 | controller: ['$scope', '$timeout', 'growl', function ($scope, $timeout, growl) { 18 | var onlyUnique = growl.onlyUnique(); 19 | 20 | $scope.messages = []; 21 | 22 | function addMessage(message) { 23 | $scope.messages.push(message); 24 | 25 | if (message.ttl && message.ttl !== -1) { 26 | $timeout(function () { 27 | $scope.deleteMessage(message); 28 | }, message.ttl); 29 | } 30 | } 31 | $rootScope.$on("growlMessage", function (event, message) { 32 | var found; 33 | if (onlyUnique) { 34 | angular.forEach($scope.messages, function(msg) { 35 | if (message.text === msg.text && message.severity === msg.severity) { 36 | found = true; 37 | } 38 | }); 39 | 40 | if (!found) { 41 | addMessage(message); 42 | } 43 | } else { 44 | addMessage(message); 45 | } 46 | }); 47 | 48 | $scope.deleteMessage = function (message) { 49 | var index = $scope.messages.indexOf(message); 50 | if (index > -1) { 51 | $scope.messages.splice(index, 1); 52 | } 53 | 54 | }; 55 | 56 | $scope.computeClasses = function (message) { 57 | return { 58 | 'alert-success': message.severity === "success", 59 | 'alert-error': message.severity === "error", //bootstrap 2.3 60 | 'alert-danger': message.severity === "error", //bootstrap 3 61 | 'alert-info': message.severity === "info", 62 | 'alert-warning': message.severity === "warn" //bootstrap 3, no effect in bs 2.3 63 | }; 64 | }; 65 | }] 66 | }; 67 | }]); 68 | -------------------------------------------------------------------------------- /src/growlFactory.js: -------------------------------------------------------------------------------- 1 | angular.module("angular-growl").provider("growl", function() { 2 | "use strict"; 3 | 4 | var _ttl = null, 5 | _enableHtml = false, 6 | _messagesKey = 'messages', 7 | _messageTextKey = 'text', 8 | _messageSeverityKey = 'severity', 9 | _onlyUniqueMessages = true; 10 | 11 | /** 12 | * set a global timeout (time to live) after which messages will be automatically closed 13 | * 14 | * @param ttl in seconds 15 | */ 16 | this.globalTimeToLive = function(ttl) { 17 | _ttl = ttl; 18 | }; 19 | 20 | /** 21 | * set whether HTML in message content should be escaped (default) or binded as-is 22 | * 23 | * @param {bool} enableHtml true to make all messages not escapes 24 | */ 25 | this.globalEnableHtml = function(enableHtml) { 26 | _enableHtml = enableHtml; 27 | }; 28 | 29 | /** 30 | * sets the key in $http response the serverMessagesInterecptor is looking for server-sent messages, value of key 31 | * needs to be an array of objects 32 | * 33 | * @param {string} messagesKey default: messages 34 | */ 35 | this.messagesKey = function(messagesKey) { 36 | _messagesKey = messagesKey; 37 | }; 38 | 39 | /** 40 | * sets the key in server sent messages the serverMessagesInterecptor is looking for text of message 41 | * 42 | * @param {string} messageTextKey default: text 43 | */ 44 | this.messageTextKey = function(messageTextKey) { 45 | _messageTextKey = messageTextKey; 46 | }; 47 | 48 | /** 49 | * sets the key in server sent messages the serverMessagesInterecptor is looking for severity of message 50 | * 51 | * @param {string} messageSeverityKey default: severity 52 | */ 53 | this.messageSeverityKey = function(messageSeverityKey) { 54 | _messageSeverityKey = messageSeverityKey; 55 | }; 56 | 57 | this.onlyUniqueMessages = function(onlyUniqueMessages) { 58 | _onlyUniqueMessages = onlyUniqueMessages; 59 | }; 60 | 61 | /** 62 | * $http interceptor that can be added to array of $http interceptors during config phase of application 63 | * via $httpProvider.responseInterceptors.push(...) 64 | * 65 | */ 66 | this.serverMessagesInterceptor = ['$q', 'growl', function ($q, growl) { 67 | function checkResponse(response) { 68 | if (response.data[_messagesKey] && response.data[_messagesKey].length > 0) { 69 | growl.addServerMessages(response.data[_messagesKey]); 70 | } 71 | } 72 | 73 | function success(response) { 74 | checkResponse(response); 75 | return response; 76 | } 77 | 78 | function error(response) { 79 | checkResponse(response); 80 | return $q.reject(response); 81 | } 82 | 83 | return function (promise) { 84 | return promise.then(success, error); 85 | }; 86 | }]; 87 | 88 | this.$get = ["$rootScope", "$filter", function ($rootScope, $filter) { 89 | var translate; 90 | 91 | try { 92 | translate = $filter("translate"); 93 | } catch (e) { 94 | // 95 | } 96 | 97 | function broadcastMessage(message) { 98 | if (translate) { 99 | message.text = translate(message.text); 100 | } 101 | $rootScope.$broadcast("growlMessage", message); 102 | } 103 | 104 | function sendMessage(text, config, severity) { 105 | var _config = config || {}, message; 106 | 107 | message = { 108 | text: text, 109 | severity: severity, 110 | ttl: _config.ttl || _ttl, 111 | enableHtml: _config.enableHtml || _enableHtml 112 | }; 113 | 114 | broadcastMessage(message); 115 | } 116 | 117 | /** 118 | * add one warn message with bootstrap class: alert 119 | * 120 | * @param {string} text 121 | * @param {{ttl: number}} config 122 | */ 123 | function addWarnMessage(text, config) { 124 | sendMessage(text, config, "warn"); 125 | } 126 | 127 | /** 128 | * add one error message with bootstrap classes: alert, alert-error 129 | * 130 | * @param {string} text 131 | * @param {{ttl: number}} config 132 | */ 133 | function addErrorMessage(text, config) { 134 | sendMessage(text, config, "error"); 135 | } 136 | 137 | /** 138 | * add one info message with bootstrap classes: alert, alert-info 139 | * 140 | * @param {string} text 141 | * @param {{ttl: number}} config 142 | */ 143 | function addInfoMessage(text, config) { 144 | sendMessage(text, config, "info"); 145 | } 146 | 147 | /** 148 | * add one success message with bootstrap classes: alert, alert-success 149 | * 150 | * @param {string} text 151 | * @param {{ttl: number}} config 152 | */ 153 | function addSuccessMessage(text, config) { 154 | sendMessage(text, config, "success"); 155 | } 156 | 157 | /** 158 | * add a indefinite number of messages that a backend server may have sent as a validation result 159 | * 160 | * @param {Array.} messages 161 | */ 162 | function addServerMessages(messages) { 163 | var i, message, severity, length; 164 | length = messages.length; 165 | for (i = 0; i < length; i++) { 166 | message = messages[i]; 167 | 168 | if (message[_messageTextKey] && message[_messageSeverityKey]) { 169 | switch (message[_messageSeverityKey]) { 170 | case "warn": 171 | severity = "warn"; 172 | break; 173 | case "success": 174 | severity = "success"; 175 | break; 176 | case "info": 177 | severity = "info"; 178 | break; 179 | case "error": 180 | severity = "error"; 181 | break; 182 | } 183 | sendMessage(message[_messageTextKey], undefined, severity); 184 | } 185 | } 186 | } 187 | 188 | function onlyUnique() { 189 | return _onlyUniqueMessages; 190 | } 191 | 192 | return { 193 | addWarnMessage: addWarnMessage, 194 | addErrorMessage: addErrorMessage, 195 | addInfoMessage: addInfoMessage, 196 | addSuccessMessage: addSuccessMessage, 197 | addServerMessages: addServerMessages, 198 | onlyUnique: onlyUnique 199 | }; 200 | }]; 201 | }); 202 | -------------------------------------------------------------------------------- /test/growlDirectiveTest.js: -------------------------------------------------------------------------------- 1 | describe("growlDirective", function() { 2 | "use strict"; 3 | 4 | it("should be true ", function() { 5 | expect(true).toBe(true); 6 | }); 7 | }); --------------------------------------------------------------------------------