├── src ├── growl.js ├── images │ ├── error-red.png │ ├── info-blue.png │ ├── info-white.png │ ├── error-white.png │ ├── success-green.png │ ├── success-white.png │ ├── warning-white.png │ └── warning-yellow.png ├── growlDirective.js ├── growlMessageService.js ├── growlFactory.js └── growl.css ├── doc └── screenshot.jpg ├── .gitignore ├── test ├── growlMessageServiceTests.js ├── growlDirectiveTests.js └── growlFactoryTests.js ├── .jshintrc ├── demo ├── app.js └── index.html ├── bower.json ├── package.json ├── LICENSE ├── karma.conf.js ├── gruntfile.js ├── CHANGELOG.md └── README.md /src/growl.js: -------------------------------------------------------------------------------- 1 | angular.module('angular-growl', []); -------------------------------------------------------------------------------- /doc/screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanStevens/angular-growl-2/HEAD/doc/screenshot.jpg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | components 4 | bower_components 5 | .idea 6 | *.iml 7 | nbproject 8 | -------------------------------------------------------------------------------- /src/images/error-red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanStevens/angular-growl-2/HEAD/src/images/error-red.png -------------------------------------------------------------------------------- /src/images/info-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanStevens/angular-growl-2/HEAD/src/images/info-blue.png -------------------------------------------------------------------------------- /src/images/info-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanStevens/angular-growl-2/HEAD/src/images/info-white.png -------------------------------------------------------------------------------- /src/images/error-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanStevens/angular-growl-2/HEAD/src/images/error-white.png -------------------------------------------------------------------------------- /src/images/success-green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanStevens/angular-growl-2/HEAD/src/images/success-green.png -------------------------------------------------------------------------------- /src/images/success-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanStevens/angular-growl-2/HEAD/src/images/success-white.png -------------------------------------------------------------------------------- /src/images/warning-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanStevens/angular-growl-2/HEAD/src/images/warning-white.png -------------------------------------------------------------------------------- /src/images/warning-yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JanStevens/angular-growl-2/HEAD/src/images/warning-yellow.png -------------------------------------------------------------------------------- /test/growlMessageServiceTests.js: -------------------------------------------------------------------------------- 1 | describe("growlMessageService Spec", function() { 2 | "use strict"; 3 | 4 | var growlMessages; 5 | 6 | beforeEach(module('angular-growl')); 7 | beforeEach(inject(['growlMessages', function (gm) { 8 | growlMessages = gm; 9 | }])); 10 | 11 | /// TESTS 12 | it('Should be defined', function () { 13 | expect(growlMessages).toBeDefined(); 14 | }); 15 | }); -------------------------------------------------------------------------------- /test/growlDirectiveTests.js: -------------------------------------------------------------------------------- 1 | describe("growlDirective Spec", function() { 2 | "use strict"; 3 | 4 | var $compile, 5 | $rootScope; 6 | 7 | beforeEach(module('angular-growl')); 8 | 9 | beforeEach(inject(function (_$compile_, _$rootScope_) { 10 | $compile = _$compile_; 11 | $rootScope = _$rootScope_; 12 | })); 13 | 14 | it('Replaces the element with the appropriate content', function () { 15 | 16 | var growlElement = $compile('
')($rootScope); 17 | 18 | $rootScope.$digest(); 19 | 20 | expect(growlElement.html()).toContain('
=1.2.1" 32 | }, 33 | "devDependencies": { 34 | "angular-mocks": ">=1.2.1" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-growl-v2", 3 | "version": "0.7.9", 4 | "description": "growl like notifications for angularJS projects, using bootstrap alert classes", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/JanStevens/angular-growl-2" 8 | }, 9 | "main": "./build/angular-growl.min.js", 10 | "author": { 11 | "name": "Jan Stevens" 12 | }, 13 | "license": "MIT", 14 | "dependencies": { 15 | "angular": ">=1.2.1" 16 | }, 17 | "devDependencies": { 18 | "grunt": "~0.4.1", 19 | "grunt-bump": "0.0.2", 20 | "grunt-contrib-clean": "~0.5.0", 21 | "grunt-contrib-concat": "^0.3.0", 22 | "grunt-contrib-copy": "^0.4.1", 23 | "grunt-contrib-cssmin": "^0.6.2", 24 | "grunt-contrib-jshint": "^0.6.5", 25 | "grunt-contrib-uglify": "^0.2.7", 26 | "grunt-contrib-watch": "^0.5.3", 27 | "grunt-karma": "^0.7.3", 28 | "grunt-ngmin": "0.0.2", 29 | "grunt-push-release": "^0.1.9", 30 | "karma": "^0.12.16", 31 | "karma-jasmine": "^0.1.5", 32 | "karma-phantomjs-launcher": "^0.1.4", 33 | "matchdep": "^0.1.2" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/growlFactoryTests.js: -------------------------------------------------------------------------------- 1 | describe("growlFactory Spec", function() { 2 | "use strict"; 3 | 4 | var growlFactory, 5 | growlMessages; 6 | 7 | beforeEach(module('angular-growl')); 8 | beforeEach(inject(['growl', 'growlMessages', function (gf, gm) { 9 | growlFactory = gf; 10 | growlMessages = gm; 11 | }])); 12 | 13 | /// TESTS 14 | it('Should be defined', function () { 15 | expect(growlFactory).toBeDefined(); 16 | expect(growlMessages).toBeDefined(); 17 | }); 18 | 19 | it('Should set proper values on default message types', function () { 20 | var builtinTypes = [ 21 | 'info', 22 | 'error', 23 | 'warning', 24 | 'success' 25 | ]; 26 | 27 | var severity; 28 | var sampleText = 'text'; 29 | for (var i = 0; i < builtinTypes.length; i++) { 30 | 31 | severity = builtinTypes[i]; 32 | expect(growlFactory[severity]).toBeDefined(); 33 | var msg = growlFactory.general(sampleText, null, severity); 34 | 35 | expect(msg).toBeDefined(); 36 | expect(msg.text.toString()).toEqual(sampleText); 37 | expect(msg.referenceId).toEqual(0); 38 | expect(msg.position).toEqual('top-right'); 39 | expect(msg.severity).toEqual(severity); 40 | } 41 | }); 42 | 43 | it('Should add and remove 1 message', function () { 44 | var msg = growlFactory.info('text'); 45 | 46 | expect(growlMessages.getAllMessages().length).toEqual(1); 47 | msg.destroy(); 48 | expect(growlMessages.getAllMessages().length).toEqual(0); 49 | }); 50 | 51 | it('Should be able to destroy all messages', function () { 52 | var messageCount = 10; 53 | for (var i = 0; i < messageCount; i++) { 54 | growlFactory.info('Test ' + i); 55 | } 56 | 57 | expect(growlMessages.getAllMessages().length).toEqual(messageCount); 58 | 59 | growlMessages.destroyAllMessages(); 60 | expect(growlMessages.getAllMessages().length).toEqual(0); 61 | 62 | }); 63 | }); -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Growl Sample 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 |
15 |
16 |
17 |
18 | 19 | 20 |
21 | 22 |
23 | 24 | 25 |
26 | 27 |
28 |
29 | 33 |
34 |
35 | 36 |
37 | 40 |
41 |
42 |
43 |
44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /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 | 'src/growlMessageService.js' 27 | ], 28 | css: [ 29 | 'src/growl.css' 30 | ], 31 | test: ['test/**/*.js'] 32 | }, 33 | 34 | watch: { 35 | 36 | scripts: { 37 | files: ['gruntfile.js', '<%= lib_files.core %>', '<%= lib_files.test %>'], 38 | tasks: ['jshint:all', 'karma:unit'] 39 | }, 40 | 41 | livereload: { 42 | options: { 43 | livereload: true 44 | }, 45 | files: ['src/**/*.*'], 46 | tasks: ['jshint', 'karma:unit'] 47 | } 48 | }, 49 | 50 | jshint: { 51 | options: { 52 | jshintrc: '.jshintrc' 53 | }, 54 | 55 | all: ['gruntfile.js', '<%= lib_files.core %>', '<%= lib_files.test %>'], 56 | 57 | core: { 58 | files: { 59 | src: ['<%= lib_files.core %>'] 60 | } 61 | }, 62 | 63 | test: { 64 | files: { 65 | src: ['<%= lib_files.test %>'] 66 | } 67 | } 68 | }, 69 | 70 | concat: { 71 | banner: { 72 | options: { 73 | banner: '<%= meta.banner %>' 74 | }, 75 | src: '<%= concat.core.dest %>', 76 | dest: '<%= concat.core.dest %>', 77 | }, 78 | 79 | core: { 80 | src: ['<%= lib_files.core %>'], 81 | dest: '<%= build_dir %>/angular-growl.js' 82 | }, 83 | 84 | css: { 85 | options: { 86 | banner: '<%= meta.banner %>' 87 | }, 88 | src: ['<%= lib_files.css %>'], 89 | dest: '<%= build_dir %>/angular-growl.css' 90 | } 91 | }, 92 | 93 | cssmin: { 94 | core: { 95 | files: { 96 | 'build/angular-growl.min.css': '<%= lib_files.css %>' 97 | }, 98 | options: { 99 | 'banner': '<%= meta.banner %>', 100 | 'report': 'gzip' 101 | } 102 | } 103 | }, 104 | 105 | uglify: { 106 | core: { 107 | files: { 108 | '<%= build_dir %>/angular-growl.min.js': '<%= concat.core.dest %>' 109 | }, 110 | options: { 111 | banner: '<%= meta.banner %>', 112 | report: 'gzip' 113 | } 114 | } 115 | }, 116 | 117 | karma: { 118 | unit: { 119 | configFile: 'karma.conf.js', 120 | singleRun: true 121 | } 122 | }, 123 | 124 | ngmin: { 125 | core: { 126 | src: '<%= concat.core.dest %>', 127 | dest: '<%= concat.core.dest %>' 128 | } 129 | }, 130 | push: { 131 | options: { 132 | files: ['package.json', 'bower.json'], 133 | add: true, 134 | addFiles: ['.'], // '.' for all files except ingored files in .gitignore 135 | commit: true, 136 | commitMessage: 'Release v%VERSION%', 137 | 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 138 | createTag: true, 139 | tagName: 'v%VERSION%', 140 | tagMessage: 'Version %VERSION%', 141 | push: true, 142 | pushTo: 'origin', 143 | npm: true, 144 | npmTag: 'Release v%VERSION%', 145 | gitDescribeOptions: '--tags --always --abbrev=1 --dirty=-d' // options to use with '$ git describe' 146 | } 147 | } 148 | }); 149 | 150 | 151 | grunt.registerTask('default', ['jshint:all', 'karma']); 152 | grunt.registerTask('test', ['karma']); 153 | 154 | grunt.registerTask('build', [ 155 | 'jshint:all', 156 | 'karma', 157 | 'build:core' 158 | ]); 159 | 160 | grunt.registerTask('build:core', [ 161 | 'concat:core', 162 | 'concat:css', 163 | 'ngmin:core', 164 | 'concat:banner', 165 | 'uglify:core', 166 | 'cssmin:core' 167 | ]); 168 | 169 | // For development purpose. 170 | grunt.registerTask('dev', ['jshint', 'karma:unit', 'watch:livereload']); 171 | 172 | require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks); 173 | }; 174 | -------------------------------------------------------------------------------- /src/growlDirective.js: -------------------------------------------------------------------------------- 1 | angular.module("angular-growl").directive("growl", [ 2 | function () { 3 | "use strict"; 4 | 5 | return { 6 | restrict: 'A', 7 | templateUrl: 'templates/growl/growl.html', 8 | replace: false, 9 | scope: { 10 | reference: '@', 11 | inline: '=', 12 | limitMessages: '=' 13 | }, 14 | controller: ['$scope', '$interval', 'growl', 'growlMessages', 15 | function ($scope, $interval, growl, growlMessages) { 16 | $scope.referenceId = $scope.reference || 0; 17 | 18 | growlMessages.initDirective($scope.referenceId, $scope.limitMessages); 19 | $scope.growlMessages = growlMessages; 20 | $scope.inlineMessage = angular.isDefined($scope.inline) ? $scope.inline : growl.inlineMessages(); 21 | 22 | $scope.$watch('limitMessages', function (limitMessages) { 23 | var directive = growlMessages.directives[$scope.referenceId]; 24 | if (!angular.isUndefined(limitMessages) && !angular.isUndefined(directive)) { 25 | directive.limitMessages = limitMessages; 26 | } 27 | }); 28 | 29 | //Cancels all promises within message upon deleting message or stop deleting. 30 | $scope.stopTimeoutClose = function (message) { 31 | if (!message.clickToClose) { 32 | angular.forEach(message.promises, function (promise) { 33 | $interval.cancel(promise); 34 | }); 35 | if(message.ttl == undefined || message.ttl == -1 || message.close) { 36 | if (typeof (message.onclick) === 'function') { 37 | message.onclick(); 38 | } 39 | growlMessages.deleteMessage(message); 40 | } else { 41 | message.close = true; 42 | } 43 | } 44 | }; 45 | 46 | $scope.alertClasses = function (message) { 47 | return { 48 | 'alert-success': message.severity === "success", 49 | 'alert-error': message.severity === "error", //bootstrap 2.3 50 | 'alert-danger': message.severity === "error", //bootstrap 3 51 | 'alert-info': message.severity === "info", 52 | 'alert-warning': message.severity === "warning", //bootstrap 3, no effect in bs 2.3 53 | 'icon': message.disableIcons === false, 54 | 'alert-dismissable': !message.disableCloseButton 55 | }; 56 | }; 57 | 58 | $scope.showCountDown = function (message) { 59 | return !message.disableCountDown && message.ttl > 0; 60 | }; 61 | 62 | $scope.wrapperClasses = function () { 63 | var classes = {}; 64 | classes['growl-fixed'] = !$scope.inlineMessage; 65 | classes[growl.position()] = true; 66 | return classes; 67 | }; 68 | 69 | $scope.computeTitle = function (message) { 70 | var ret = { 71 | 'success': 'Success', 72 | 'error': 'Error', 73 | 'info': 'Information', 74 | 'warn': 'Warning' 75 | }; 76 | return ret[message.severity]; 77 | }; 78 | } 79 | ] 80 | }; 81 | } 82 | ]); 83 | 84 | angular.module("angular-growl").run(['$templateCache', function ($templateCache) { 85 | "use strict"; 86 | if ($templateCache.get('templates/growl/growl.html') === undefined) { 87 | $templateCache.put("templates/growl/growl.html", 88 | '
' + 89 | '
' + 90 | '' + 91 | '' + 92 | '

' + 93 | '
' + 94 | '
' + 95 | '
' 96 | ); 97 | } 98 | }]); 99 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ##Changelog 2 | **0.8.0** - TBE TODO 3 | * Code clean up 4 | * Testing 5 | * Latest angular support 6 | * [Finding a solution for this issue](https://github.com/JanStevens/angular-growl-2/issues/54) 7 | 8 | **0.7.5** - 17 Jun 2015 9 | * Fixed #76 Middle vertical alignment (@fmenezes) [pull #88](https://github.com/JanStevens/angular-growl-2/pull/88) 10 | * Added base test suite for karma 11 | * Fixed potential issue described in [issue #93](https://github.com/JanStevens/angular-growl-2/issues/93) 12 | 13 | **0.7.4** - 26 May 2015 14 | * Feature request: Return the configuration object on config methods (@disoney) [pull #73](https://github.com/JanStevens/angular-growl-2/pull/73) 15 | * On responseError data is null, and an error is raised. (@AlexTM84) [pull #87](https://github.com/JanStevens/angular-growl-2/pull/87) 16 | * Fix empty title and text (@asabirov) [pull #86](https://github.com/JanStevens/angular-growl-2/pull/86) 17 | 18 | **0.7.3** - 05 Jan 2015 19 | * Fixes issue 62 where the service was called before the directive was initialized 20 | 21 | **0.7.2** - 20 Nov 2014 22 | * Possibility to toggle the translation of messages (@schoes) [pull #55](https://github.com/JanStevens/angular-growl-2/pull/55) 23 | * Check if the response is undefined (Offline Connections) (@brunoporto) [pull #50](https://github.com/JanStevens/angular-growl-2/pull/50) 24 | * Prevent NPEs when working with server-side messages (@madhead) [pull #45](https://github.com/JanStevens/angular-growl-2/pull/45) 25 | * Added a general method for setting the Growl type based on a server response (@madhead) [pull #41](https://github.com/JanStevens/angular-growl-2/pull/41) 26 | * Split Growl directive in a growl factory added a way to programatically close messages and a setText to update the message text (@chasemgray) [pull #38](https://github.com/JanStevens/angular-growl-2/pull/38) 27 | 28 | **0.7.0** - 10 Aug 2014 29 | * Added new documentation website with examples instead of this readme. 30 | * Growl Containers are now responsive for mobile devices (@tlvince) [pull #17](https://github.com/JanStevens/angular-growl-2/pull/17) 31 | * Add option to reverse order of messages (@MilosMosovsky) [pull #18](https://github.com/JanStevens/angular-growl-2/pull/18) 32 | * Add option to set the message limit of a growl container (@MilosMosovsky) [pull #21](https://github.com/JanStevens/angular-growl-2/pull/21) 33 | * Add new feature to stop the TTL when clicked and remove the message manually when clicked again (@willjk) [pull #27](https://github.com/JanStevens/angular-growl-2/pull/27) 34 | * Fix for issue #22 (@soumya92) [pull #23](https://github.com/JanStevens/angular-growl-2/pull/23) 35 | * Fix for angular 1.3 http interceptor API changes (@vik-singh) [pull #20](https://github.com/JanStevens/angular-growl-2/pull/20) & [pull #29](https://github.com/JanStevens/angular-growl-2/pull/29) 36 | * Fix only add template to cache if it doesn't exist already (@Anaphase) [pull #31](https://github.com/JanStevens/angular-growl-2/pull/31) 37 | 38 | **0.6.1** - 25 May 2014 39 | * Fixes edge case where message test is not a string 40 | * Fixes style issue where close button was floating outside the alert 41 | * Fixes issue [#12](https://github.com/JanStevens/angular-growl-2/issues/12), [#15](https://github.com/JanStevens/angular-growl-2/issues/15), [#16](https://github.com/JanStevens/angular-growl-2/issues/16) 42 | 43 | **0.6.0** - 16 Apr 2014 44 | * [CHANGE] remove enableHtml, `$sce.trustAsHtml` is always run on the message text 45 | * Possible to set global possition for non-inline growl messages (thanks @pauloprea) 46 | * Template can now easily be replace or styled with CSS 47 | * Include icons for the different notifications, can be disabled globally or per notification 48 | * Server side messages can now interpolate variables into the message ([original pull request](https://github.com/marcorinck/angular-growl/pull/19)) 49 | 50 | 51 | **0.5.3** - 19 Mar 2014 52 | * Fixed bug where globalInlineMessage option would not work globally 53 | 54 | **0.5.2** - 19 Mar 2014 55 | * Added an option to show notifications inline instead of growl like behaviour (very handy for forms) 56 | * Added a referenceId field so different inline growl directives can be targeted 57 | * Converted tabs to spaces 58 | * Updated the demo site to show the new changes 59 | 60 | **0.5.0** - 18 Mar 2014 61 | * Manually merged some pull requests from the original branch 62 | * Fixed bower.json file to include itself and the css file 63 | * [BREAK] changed the function names to add growl notifications to be a shorter (success, info, warning, error VS addSuccessMessage, addInfoMessage...) 64 | 65 | **0.4.0** - 19th Nov 2013 66 | 67 | * updated dependency to angularJS 1.2.x, angular-growl does not work with 1.0.x anymore (BREAKING CHANGE) 68 | * new option: only display unique messages, which is the new default, disable to allow same message more than once (BREAKING CHANGE) 69 | * new option: allow html tags in messages, default is off you need to 70 | 71 | **0.3.1** - 1st Oct 2013 72 | 73 | * bugfix: translating of messages works again 74 | * change: also set alert css classes introduced by bootstrap 3 75 | 76 | **0.3.0** - 26th Sept 2013 77 | 78 | * adding css animations support via ngAnimate (for angularJS >= 1.2) 79 | * ability to configure server message keys 80 | 81 | **0.2.0** - 22nd Sept 2013 82 | 83 | * reworking, bugfixing and documenting handling of server sent messages/notifications 84 | * externalizing css styles of growl class 85 | * provide minified versions of js and css files in build folder 86 | 87 | **0.1.3** - 20th Sept 2013 88 | 89 | * introducing ttl config option, fixes #2 -------------------------------------------------------------------------------- /src/growlMessageService.js: -------------------------------------------------------------------------------- 1 | angular.module("angular-growl").service("growlMessages", ['$sce', '$interval', function ($sce, $interval) { 2 | "use strict"; 3 | 4 | var self = this; 5 | this.directives = {}; 6 | var preloadDirectives = {}; 7 | 8 | /** 9 | * Allows for preloading a directive before the directives 10 | * controller is initialized 11 | * @param referenceId 12 | * @returns {*} 13 | */ 14 | function preLoad(referenceId) { 15 | var directive; 16 | if (preloadDirectives[referenceId]) { 17 | directive = preloadDirectives[referenceId]; 18 | } else { 19 | directive = preloadDirectives[referenceId] = { 20 | messages: [] 21 | }; 22 | } 23 | return directive; 24 | } 25 | 26 | function directiveForRefId(referenceId) { 27 | var refId = referenceId || 0; 28 | return (self.directives[refId] || preloadDirectives[refId]); 29 | } 30 | 31 | /** 32 | * Initialize a directive 33 | * We look at the preloaded directive and use this else we 34 | * create a new blank object 35 | * @param referenceId 36 | * @param limitMessages 37 | */ 38 | this.initDirective = function (referenceId, limitMessages) { 39 | // If we already have a directive preloaded use this version 40 | // so our growl notifications are shown. 41 | if (preloadDirectives[referenceId]) { 42 | this.directives[referenceId] = preloadDirectives[referenceId]; 43 | this.directives[referenceId].limitMessages = limitMessages; 44 | } else { 45 | this.directives[referenceId] = { 46 | messages: [], 47 | limitMessages: limitMessages 48 | }; 49 | } 50 | return this.directives[referenceId]; 51 | }; 52 | 53 | this.getAllMessages = function (referenceId) { 54 | referenceId = referenceId || 0; 55 | var messages; 56 | if (directiveForRefId(referenceId)) { 57 | messages = directiveForRefId(referenceId).messages; 58 | } else { 59 | messages = []; 60 | } 61 | return messages; 62 | }; 63 | 64 | this.destroyAllMessages = function (referenceId) { 65 | var messages = this.getAllMessages(referenceId); 66 | for (var i = messages.length - 1; i >= 0; i--) { 67 | messages[i].destroy(); 68 | } 69 | 70 | var directive = directiveForRefId(referenceId); 71 | if (directive) { 72 | directive.messages = []; 73 | } 74 | }; 75 | 76 | this.addMessage = function (message) { 77 | var directive, messages, found, msgText; 78 | 79 | // If we dont found our directive preload it! 80 | if (this.directives[message.referenceId]) { 81 | directive = this.directives[message.referenceId]; 82 | } else { 83 | directive = preLoad(message.referenceId); 84 | } 85 | 86 | messages = directive.messages; 87 | 88 | if (this.onlyUnique) { 89 | angular.forEach(messages, function (msg) { 90 | msgText = $sce.getTrustedHtml(msg.text); 91 | if (message.text === msgText && message.severity === msg.severity && message.title === msg.title) { 92 | found = true; 93 | } 94 | }); 95 | 96 | if (found) { 97 | return; 98 | } 99 | } 100 | 101 | message.text = $sce.trustAsHtml(String(message.text)); 102 | 103 | /**If message closes on timeout, add's promises array for 104 | timeouts to stop close. Also sets message.closeoutTimer to ttl / 1000 105 | **/ 106 | if (message.ttl && message.ttl !== -1) { 107 | message.countdown = message.ttl / 1000; 108 | message.promises = []; 109 | message.close = false; 110 | message.countdownFunction = function () { 111 | if (message.countdown > 1) { 112 | message.countdown--; 113 | message.promises.push($interval(message.countdownFunction, 1000, 1,1)); 114 | } else { 115 | message.countdown--; 116 | } 117 | }; 118 | } 119 | 120 | /** Limit the amount of messages in the container **/ 121 | if (angular.isDefined(directive.limitMessages)) { 122 | var diff = messages.length - (directive.limitMessages - 1); 123 | if (diff > 0) { 124 | messages.splice(directive.limitMessages - 1, diff); 125 | } 126 | } 127 | 128 | /** abillity to reverse order (newest first ) **/ 129 | if (this.reverseOrder) { 130 | messages.unshift(message); 131 | } else { 132 | messages.push(message); 133 | } 134 | 135 | if (typeof (message.onopen) === 'function') { 136 | message.onopen(); 137 | } 138 | 139 | if (message.ttl && message.ttl !== -1) { 140 | //adds message timeout to promises and starts messages countdown function. 141 | var self = this; 142 | message.promises.push($interval(angular.bind(this, function () { 143 | self.deleteMessage(message); 144 | }), message.ttl, 1,1)); 145 | message.promises.push($interval(message.countdownFunction, 1000, 1,1)); 146 | } 147 | 148 | return message; 149 | }; 150 | 151 | this.deleteMessage = function (message) { 152 | var messages = this.getAllMessages(message.referenceId), 153 | index = -1; 154 | 155 | for (var i in messages) { 156 | if (messages.hasOwnProperty(i)) { 157 | index = messages[i] === message ? i : index; 158 | } 159 | } 160 | 161 | if (index > -1) { 162 | messages[index].close = true; 163 | messages.splice(index, 1); 164 | } 165 | 166 | if (typeof (message.onclose) === 'function') { 167 | message.onclose(); 168 | } 169 | }; 170 | }]); 171 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #angular-growl-2 2 | Growl like notifications for angularJS projects, using bootstrap alert classes, originally developed by Marco Rinck and Jan Stevens 3 | 4 | ##Features 5 | 6 | ![Standard bootstrap 2.x styles](doc/screenshot.jpg) 7 | 8 | * growl like notifications like in MacOS X 9 | * using standard bootstrap classes (alert, alert-info, alert-error, alert-success) 10 | * global or per message configuration of a timeout when message will be automatically closed 11 | * automatic translation of messages if [angular-translate](https://github.com/PascalPrecht/angular-translate) filter is 12 | present, you only have to provide keys as messages, angular-translate will translate them 13 | * pre-defined $http-Interceptor to automatically handle $http responses for server-sent messages 14 | * automatic CSS animations when adding/closing notifications (only when using >= angularJS 1.2) 15 | * < 1 kB after GZIP 16 | 17 | ##Changelog 18 | 19 | **0.4.0** - 19th Nov 2013 20 | 21 | * updated dependency to angularJS 1.2.x, angular-growl does not work with 1.0.x anymore (BREAKING CHANGE) 22 | * new option: only display unique messages, which is the new default, disable to allow same message more than once (BREAKING CHANGE) 23 | * new option: allow html tags in messages, default is off, allows to use HTML tags in messages 24 | 25 | **0.3.1** - 1st Oct 2013 26 | 27 | * bugfix: translating of messages works again 28 | * change: also set alert css classes introduced by bootstrap 3 29 | 30 | **0.3.0** - 26th Sept 2013 31 | 32 | * adding css animations support via ngAnimate (for angularJS >= 1.2) 33 | * ability to configure server message keys 34 | 35 | ##Installation 36 | 37 | You can install angular-growl-v2 with bower: 38 | 39 | > bower install angular-growl-v2 40 | 41 | Alternatively you can download the files in the [build folder](build/) manually and include them in your project. 42 | 43 | ````html 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | ```` 54 | 55 | As angular-growl is based on its own angularJS module, you have to alter your dependency list when creating your application 56 | module: 57 | 58 | ````javascript 59 | var app = angular.module('myApp', ['angular-growl']); 60 | ```` 61 | 62 | Finally, you have to include the directive somewhere in your HTML like this: 63 | 64 | ````html 65 | 66 |
67 | 68 | ```` 69 | 70 | ##Usage 71 | 72 | Just let angular inject the growl Factory into your code and call the 4 functions that the factory provides accordingly: 73 | 74 | ````javascript 75 | app.controller("demoCtrl", ['$scope', 'growl', function($scope, growl) { 76 | $scope.addSpecialWarnMessage = function() { 77 | growl.warning("This adds a warn message"); 78 | growl.info("This adds a info message"); 79 | growl.success("This adds a success message"); 80 | growl.error("This adds a error message"); 81 | } 82 | }]); 83 | ```` 84 | 85 | The title must be set as a configuration parameter: 86 | 87 | ````javascript 88 | app.controller("demoCtrl", ['$scope', 'growl', function($scope, growl) { 89 | $scope.addSpecialWarnMessage = function() { 90 | growl.warning("This adds a warn message", {title: 'Warning!'}); 91 | growl.info("This adds a info message", {title: 'Random Information'}); 92 | growl.success("This adds a success message"); //no title here 93 | growl.error("This adds a error message", {title: 'ALERT WE GOT ERROR'}); 94 | } 95 | }]); 96 | ```` 97 | 98 | 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 99 | only the key: 100 | 101 | ````javascript 102 | app.controller("demoCtrl", ['$scope', 'growl', function($scope, growl) { 103 | $scope.addSpecialWarnMessage = function() { 104 | growl.success("SAVE_SUCCESS_MESSAGE"); 105 | growl.error("VALIDATION_ERROR"); 106 | } 107 | }]); 108 | ```` 109 | 110 | ## Configuration/Documentation/Info 111 | For the configuration options, documentation and live examples visit the github pages: 112 | 113 | ## [http://janstevens.github.io/angular-growl-2/](http://janstevens.github.io/angular-growl-2/) 114 | 115 | Live demo's can be found on the following codepen collection: 116 | 117 | ## [Codepen Collection](http://codepen.io/collection/Jhcpi/) 118 | 119 | This stops messages from closing when mouse is over: 120 | 121 | ````javascript 122 | app.config(['growlProvider', function (growlProvider) { 123 | growlProvider.globalPauseOnMouseOver(true); 124 | }]); 125 | ```` 126 | 127 | ## Contributions 128 | * Fork the project 129 | * Change/Fix/Add the stuff you want 130 | * Clone the codepens that have effect on your changes or if you add new features create a codepen that show them 131 | * Create a PR 132 | * Don't forget to add your name to the Thanks section! 133 | 134 | # Thanks 135 | Thanks Marco Rinck for the original code, the following people have contributed to this project: 136 | 137 | * [orangeskins](https://github.com/orangeskins) 138 | * [adamalbrecht](https://github.com/adamalbrecht) 139 | * [m0ppers](https://github.com/m0ppers) 140 | * [lbehnke](https://github.com/lbehnke) 141 | * [rorymadden](https://github.com/rorymadden) 142 | * [pauloprea](https://github.com/pauloprea) 143 | * [tlvince](https://github.com/tlvince) 144 | * [vik-singh](https://github.com/vik-singh) 145 | * [Anaphase](https://github.com/Anaphase) 146 | * [soumya92](https://github.com/soumya92) 147 | * [willjk](https://github.com/willjk) 148 | * [wardvijf](https://github.com/wardvijf) 149 | * [naveenpeterj](https://github.com/naveenpeterj) 150 | 151 | # License 152 | Copyright (C) 2015 Marco Rinck 153 | 154 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 155 | 156 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 157 | 158 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 159 | -------------------------------------------------------------------------------- /src/growlFactory.js: -------------------------------------------------------------------------------- 1 | angular.module("angular-growl").provider("growl", function () { 2 | "use strict"; 3 | 4 | var _ttl = {success: null, error: null, warning: null, info: null}, 5 | _messagesKey = 'messages', 6 | _messageTextKey = 'text', 7 | _messageTitleKey = 'title', 8 | _messageSeverityKey = 'severity', 9 | _messageTTLKey = 'ttl', 10 | _onlyUniqueMessages = true, 11 | _messageVariableKey = 'variables', 12 | _messageReferenceIdKey = 'referenceId', 13 | _referenceId = 0, 14 | _inline = false, 15 | _position = 'top-right', 16 | _disableCloseButton = false, 17 | _disableIcons = false, 18 | _pauseOnMouseOver = false, 19 | _reverseOrder = false, 20 | _disableCountDown = false, 21 | _translateMessages = true; 22 | 23 | /** 24 | * set a global timeout (time to live) after which messages will be automatically closed 25 | * 26 | * @param ttl in seconds 27 | */ 28 | this.globalTimeToLive = function (ttl) { 29 | if (typeof ttl === 'object') { 30 | for (var k in ttl) { 31 | if (ttl.hasOwnProperty(k)) { 32 | _ttl[k] = ttl[k]; 33 | } 34 | } 35 | } else { 36 | for (var severity in _ttl) { 37 | if (_ttl.hasOwnProperty(severity)) { 38 | _ttl[severity] = ttl; 39 | } 40 | } 41 | } 42 | return this; 43 | }; 44 | 45 | /** 46 | * set whether the messages should be translated (default:true) when the translator is available 47 | * 48 | * @param {bool} translateMessages true to to translate all messages 49 | */ 50 | this.globalTranslateMessages = function (translateMessages) { 51 | _translateMessages = translateMessages; 52 | return this; 53 | }; 54 | 55 | /** 56 | * set whether the close button should be displayed (default) or hidden 57 | * 58 | * @param {bool} disableCloseButton true to hide close button on all messages 59 | */ 60 | this.globalDisableCloseButton = function (disableCloseButton) { 61 | _disableCloseButton = disableCloseButton; 62 | return this; 63 | }; 64 | 65 | /** 66 | * set whether the icons will be shown in the message 67 | * 68 | * @param {bool} messageIcons 69 | */ 70 | this.globalDisableIcons = function (disableIcons) { 71 | _disableIcons = disableIcons; 72 | return this; 73 | }; 74 | 75 | /** 76 | * set whether message is paused on mouse-over 77 | * 78 | * @param {bool} pauseOnMouseOver 79 | */ 80 | this.globalPauseOnMouseOver = function (pauseOnMouseOver) { 81 | _pauseOnMouseOver = pauseOnMouseOver; 82 | return this; 83 | }; 84 | 85 | /** 86 | * set whether message ordering is reversed 87 | * 88 | * @param {bool} reverseOrder 89 | */ 90 | this.globalReversedOrder = function (reverseOrder) { 91 | _reverseOrder = reverseOrder; 92 | return this; 93 | }; 94 | 95 | /** 96 | * set whether to show the count down 97 | * 98 | * @param {bool} reverseOrder 99 | */ 100 | this.globalDisableCountDown = function (countDown) { 101 | _disableCountDown = countDown; 102 | return this; 103 | }; 104 | 105 | /** 106 | * set the key in server sent messages the serverMessagesInterecptor is looking for variables to inject in the message 107 | * 108 | * @param {string} messageVariableKey default: variables 109 | */ 110 | this.messageVariableKey = function (messageVariableKey) { 111 | _messageVariableKey = messageVariableKey; 112 | return this; 113 | }; 114 | 115 | /** 116 | * set wheter the notficiation is displayed inline our in growl like fasion 117 | * 118 | * @param {bool} inline true to show only inline notifications 119 | */ 120 | this.globalInlineMessages = function (inline) { 121 | _inline = inline; 122 | return this; 123 | }; 124 | 125 | /** 126 | * set position 127 | * 128 | * @param {string} messageVariableKey default: top-right 129 | */ 130 | this.globalPosition = function (position) { 131 | _position = position; 132 | return this; 133 | }; 134 | /** 135 | * sets the key in $http response the serverMessagesInterecptor is looking for server-sent messages, value of key 136 | * needs to be an array of objects 137 | * 138 | * @param {string} messagesKey default: messages 139 | */ 140 | this.messagesKey = function (messagesKey) { 141 | _messagesKey = messagesKey; 142 | return this; 143 | }; 144 | 145 | /** 146 | * sets the key in server sent messages the serverMessagesInterecptor is looking for text of message 147 | * 148 | * @param {string} messageTextKey default: text 149 | */ 150 | this.messageTextKey = function (messageTextKey) { 151 | _messageTextKey = messageTextKey; 152 | return this; 153 | }; 154 | 155 | /** 156 | * sets the key in server sent messages the serverMessagesInterecptor is looking for title of message 157 | * 158 | * @param {string} messageTextKey default: text 159 | */ 160 | this.messageTitleKey = function (messageTitleKey) { 161 | _messageTitleKey = messageTitleKey; 162 | return this; 163 | }; 164 | 165 | /** 166 | * sets the key in server sent messages the serverMessagesInterecptor is looking for severity of message 167 | * 168 | * @param {string} messageSeverityKey default: severity 169 | */ 170 | this.messageSeverityKey = function (messageSeverityKey) { 171 | _messageSeverityKey = messageSeverityKey; 172 | return this; 173 | }; 174 | 175 | /** 176 | * sets the key in server sent messages the serverMessagesInterecptor is looking for ttl of message 177 | * 178 | * @param {string} messageTTLKey default: ttl 179 | */ 180 | this.messageTTLKey = function (messageTTLKey) { 181 | _messageTTLKey = messageTTLKey; 182 | return this; 183 | }; 184 | 185 | /** 186 | * sets the key in server sent messages the serverMessagesInterecptor is looking for referenceId of message 187 | * 188 | * @param {string} messageReferenceIdKey default: referenceId 189 | */ 190 | this.messageReferenceIdKey = function (messageReferenceIdKey) { 191 | _messageReferenceIdKey = messageReferenceIdKey; 192 | return this; 193 | }; 194 | 195 | 196 | this.onlyUniqueMessages = function (onlyUniqueMessages) { 197 | _onlyUniqueMessages = onlyUniqueMessages; 198 | return this; 199 | }; 200 | 201 | /** 202 | * $http interceptor that can be added to array of $http interceptors during config phase of application 203 | * via $httpProvider.responseInterceptors.push(...) 204 | * 205 | */ 206 | this.serverMessagesInterceptor = ['$q', 'growl', function ($q, growl) { 207 | function checkResponse (response) { 208 | if (response !== undefined && response.data && response.data[_messagesKey] && response.data[_messagesKey].length > 0) { 209 | growl.addServerMessages(response.data[_messagesKey]); 210 | } 211 | } 212 | 213 | return { 214 | 'response': function (response) { 215 | checkResponse(response); 216 | return response; 217 | }, 218 | 'responseError': function (rejection) { 219 | checkResponse(rejection); 220 | return $q.reject(rejection); 221 | } 222 | }; 223 | }]; 224 | 225 | this.$get = ["$rootScope", "$interpolate", "$sce", "$filter", "$interval", "growlMessages", function ($rootScope, $interpolate, $sce, $filter, $interval, growlMessages) { 226 | var translate; 227 | 228 | growlMessages.onlyUnique = _onlyUniqueMessages; 229 | growlMessages.reverseOrder = _reverseOrder; 230 | 231 | try { 232 | translate = $filter("translate"); 233 | } catch (e) { 234 | // 235 | } 236 | 237 | function broadcastMessage (message) { 238 | if (translate && message.translateMessage) { 239 | message.text = translate(message.text, message.variables) || message.text; 240 | message.title = translate(message.title) || message.title; 241 | } else { 242 | var polation = $interpolate(message.text); 243 | message.text = polation(message.variables); 244 | } 245 | var addedMessage = growlMessages.addMessage(message); 246 | $rootScope.$broadcast("growlMessage", message); 247 | $interval(function () { 248 | }, 0, 1); 249 | return addedMessage; 250 | } 251 | 252 | function sendMessage (text, config, severity) { 253 | var _config = config || {}, message; 254 | 255 | message = { 256 | text: text, 257 | title: _config.title, 258 | severity: severity, 259 | ttl: _config.ttl || _ttl[severity], 260 | variables: _config.variables || {}, 261 | disableCloseButton: _config.disableCloseButton === undefined ? _disableCloseButton : _config.disableCloseButton, 262 | disableIcons: _config.disableIcons === undefined ? _disableIcons : _config.disableIcons, 263 | pauseOnMouseOver: _config.pauseOnMouseOver === undefined ? _pauseOnMouseOver : _config.pauseOnMouseOver, 264 | disableCountDown: _config.disableCountDown === undefined ? _disableCountDown : _config.disableCountDown, 265 | position: _config.position || _position, 266 | referenceId: _config.referenceId || _referenceId, 267 | translateMessage: _config.translateMessage === undefined ? _translateMessages : _config.translateMessage, 268 | destroy: function () { 269 | growlMessages.deleteMessage(message); 270 | }, 271 | setText: function (newText) { 272 | message.text = $sce.trustAsHtml(String(newText)); 273 | }, 274 | onclose: _config.onclose, 275 | onopen: _config.onopen 276 | }; 277 | 278 | return broadcastMessage(message); 279 | } 280 | 281 | /** 282 | * add one warning message with bootstrap class: alert 283 | * 284 | * @param {string} text 285 | * @param {{ttl: number}} config 286 | */ 287 | function warning (text, config) { 288 | return sendMessage(text, config, "warning"); 289 | } 290 | 291 | /** 292 | * add one error message with bootstrap classes: alert, alert-error 293 | * 294 | * @param {string} text 295 | * @param {{ttl: number}} config 296 | */ 297 | function error (text, config) { 298 | return sendMessage(text, config, "error"); 299 | } 300 | 301 | /** 302 | * add one info message with bootstrap classes: alert, alert-info 303 | * 304 | * @param {string} text 305 | * @param {{ttl: number}} config 306 | */ 307 | function info (text, config) { 308 | return sendMessage(text, config, "info"); 309 | } 310 | 311 | /** 312 | * add one success message with bootstrap classes: alert, alert-success 313 | * 314 | * @param {string} text 315 | * @param {{ttl: number}} config 316 | */ 317 | function success (text, config) { 318 | return sendMessage(text, config, "success"); 319 | } 320 | 321 | /** 322 | * add one message with specified severity 323 | * 324 | * @param {string} text 325 | * @param {{ttl: number}} config 326 | * @param {string} severity 327 | */ 328 | function general (text, config, severity) { 329 | severity = (severity || "error").toLowerCase(); 330 | return sendMessage(text, config, severity); 331 | } 332 | 333 | /** 334 | * add a indefinite number of messages that a backend server may have sent as a validation result 335 | * 336 | * @param {Array.} messages 337 | */ 338 | function addServerMessages (messages) { 339 | if ((!messages) || (!messages.length)) { 340 | return; 341 | } 342 | var i, message, severity, length; 343 | length = messages.length; 344 | for (i = 0; i < length; i++) { 345 | message = messages[i]; 346 | 347 | if (message[_messageTextKey]) { 348 | severity = (message[_messageSeverityKey] || "error").toLowerCase(); 349 | var config = {}; 350 | config.variables = message[_messageVariableKey] || {}; 351 | config.title = message[_messageTitleKey]; 352 | if (message[_messageTTLKey]) { 353 | config.ttl = message[_messageTTLKey]; 354 | } 355 | if (message[_messageReferenceIdKey]) { 356 | config.referenceId = message[_messageReferenceIdKey]; 357 | } 358 | sendMessage(message[_messageTextKey], config, severity); 359 | } 360 | } 361 | } 362 | 363 | function onlyUnique () { 364 | return _onlyUniqueMessages; 365 | } 366 | 367 | function reverseOrder () { 368 | return _reverseOrder; 369 | } 370 | 371 | function inlineMessages () { 372 | return _inline; 373 | } 374 | 375 | function position () { 376 | return _position; 377 | } 378 | 379 | return { 380 | warning: warning, 381 | error: error, 382 | info: info, 383 | success: success, 384 | general: general, 385 | addServerMessages: addServerMessages, 386 | onlyUnique: onlyUnique, 387 | reverseOrder: reverseOrder, 388 | inlineMessages: inlineMessages, 389 | position: position 390 | }; 391 | }]; 392 | }); 393 | -------------------------------------------------------------------------------- /src/growl.css: -------------------------------------------------------------------------------- 1 | /* 2 | * growl-container styles 3 | */ 4 | .growl-container.growl-fixed { 5 | position: fixed; 6 | float: right; 7 | width: 90%; 8 | max-width: 400px; 9 | z-index: 9999; 10 | } 11 | .growl-container.growl-fixed.top-right { 12 | top: 10px; 13 | right: 15px; 14 | } 15 | .growl-container.growl-fixed.bottom-right { 16 | bottom: 10px; 17 | right: 15px; 18 | } 19 | .growl-container.growl-fixed.middle-right { 20 | top: 49%; 21 | right: 15px; 22 | } 23 | .growl-container.growl-fixed.top-left { 24 | top: 10px; 25 | left: 15px; 26 | } 27 | .growl-container.growl-fixed.bottom-left { 28 | bottom: 10px; 29 | left: 15px; 30 | } 31 | .growl-container.growl-fixed.middle-left { 32 | top: 49%; 33 | left: 15px; 34 | } 35 | .growl-container.growl-fixed.top-center { 36 | top: 10px; 37 | left: 50%; 38 | margin-left: -200px; 39 | } 40 | .growl-container.growl-fixed.bottom-center { 41 | bottom: 10px; 42 | left: 50%; 43 | margin-left: -200px; 44 | } 45 | .growl-container.growl-fixed.middle-center { 46 | top: 49%; 47 | left: 50%; 48 | margin-left: -200px; 49 | } 50 | 51 | /* 52 | * growl-item styles 53 | */ 54 | .growl-container > .growl-item { 55 | padding: 10px; 56 | padding-right: 35px; 57 | margin-bottom: 10px; 58 | cursor: pointer; 59 | } 60 | 61 | .growl-container > button { 62 | border: none; 63 | outline:none; 64 | } 65 | .growl-container > .growl-item.ng-enter, 66 | .growl-container > .growl-item.ng-leave { 67 | -webkit-transition:0.5s linear all; 68 | -moz-transition:0.5s linear all; 69 | -o-transition:0.5s linear all; 70 | transition:0.5s linear all; 71 | } 72 | 73 | .growl-container > .growl-item.ng-enter, 74 | .growl-container > .growl-item.ng-leave.ng-leave-active { 75 | opacity:0; 76 | } 77 | .growl-container > .growl-item.ng-leave, 78 | .growl-container > .growl-item.ng-enter.ng-enter-active { 79 | opacity:1; 80 | } 81 | 82 | .growl-container > div.growl-item { 83 | background-position: 12px center; 84 | background-repeat: no-repeat; 85 | } 86 | 87 | /* 88 | * growl-title styles 89 | */ 90 | .growl-title { 91 | font-size: 16px; 92 | } 93 | .growl-item.icon > .growl-title { 94 | margin: 0 0 0 40px; 95 | } 96 | 97 | /* 98 | * growl-message styles 99 | */ 100 | .growl-item.icon > .growl-message { 101 | margin: 0 0 0 40px; 102 | } 103 | 104 | /* 105 | * growl background images 106 | */ 107 | .growl-container > .alert-info.icon { 108 | /* for the white images 109 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII="); 110 | */ 111 | background-image: url("data:image/png;base64,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"); 112 | } 113 | .growl-container > .alert-error.icon { 114 | /* for the white images 115 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII="); 116 | */ 117 | background-image: url("data:image/png;base64,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"); 118 | } 119 | .growl-container > .alert-success.icon { 120 | /* for the white images 121 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg=="); 122 | */ 123 | background-image: url("data:image/png;base64,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"); 124 | } 125 | .growl-container > .alert-warning.icon { 126 | /* for the white images 127 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII="); 128 | */ 129 | background-image: url("data:image/png;base64,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"); 130 | } 131 | --------------------------------------------------------------------------------