├── .gitignore ├── .jscs.json ├── .jshintrc ├── .jshintrc-test ├── .npmignore ├── .travis.yml ├── Gruntfile.js ├── LICENSE ├── README.md ├── bower.json ├── dist ├── angular-tiny-eventemitter.js └── angular-tiny-eventemitter.min.js ├── package.json ├── src └── tiny-eventemitter.js └── test ├── configs └── unit.conf.js └── tiny-eventemitter.js /.gitignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | /node_modules 3 | /bower_components 4 | /libpeerconnection.log 5 | /unit-results.xml 6 | /e2e-results.xml 7 | 8 | npm-debug.log 9 | .idea 10 | -------------------------------------------------------------------------------- /.jscs.json: -------------------------------------------------------------------------------- 1 | { 2 | "requireCurlyBraces": ["if", "else", "for", "while", "do", "try", "catch"], 3 | "requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch"], 4 | "requireParenthesesAroundIIFE": true, 5 | "requireSpacesInFunctionExpression": { 6 | "beforeOpeningCurlyBrace": true 7 | }, 8 | "requireSpacesInAnonymousFunctionExpression": { 9 | "beforeOpeningRoundBrace": true 10 | }, 11 | "disallowSpacesInNamedFunctionExpression": { 12 | "beforeOpeningRoundBrace": true 13 | }, 14 | "disallowSpacesInFunctionDeclaration": { 15 | "beforeOpeningRoundBrace": true 16 | }, 17 | "disallowMultipleVarDecl": true, 18 | "requireSpacesInsideObjectBrackets": "all", 19 | "disallowQuotedKeysInObjects": "allButReserved", 20 | "disallowSpaceAfterObjectKeys": true, 21 | "requireCommaBeforeLineBreak": true, 22 | "disallowSpaceBeforeBinaryOperators": [","], 23 | "disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~", "!"], 24 | "disallowSpaceBeforePostfixUnaryOperators": ["++", "--"], 25 | "requireSpaceBeforeBinaryOperators": ["+", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="], 26 | "requireSpaceAfterBinaryOperators": [",", "+", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="], 27 | "validateQuoteMarks": true, 28 | "validateIndentation": 4, 29 | "disallowTrailingWhitespace": true, 30 | "disallowKeywordsOnNewLine": ["else"], 31 | "requireCapitalizedConstructors": true, 32 | "safeContextKeyword": "self", 33 | "requireDotNotation": true, 34 | "disallowYodaConditions": true 35 | } 36 | -------------------------------------------------------------------------------- /.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 | "white": true, 13 | "proto": true, 14 | "predef": [ 15 | "$", 16 | "angular", 17 | "console" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /.jshintrc-test: -------------------------------------------------------------------------------- 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 | "node": true, 13 | "white": true, 14 | "predef": [ 15 | "angular", 16 | "describe", 17 | "it", 18 | "beforeEach", 19 | "afterEach", 20 | "module", 21 | "inject", 22 | "assert" 23 | ] 24 | } 25 | 26 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /Gruntfile.js 2 | /.travis.yml 3 | /test 4 | /src 5 | /node_modules 6 | /bower_components 7 | /libpeerconnection.log 8 | /*-results.xml 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "node" 4 | sudo: false 5 | before_install: 6 | - npm install -g grunt-cli bower 7 | - bower install 8 | - export DISPLAY=:99.0 9 | - sh -e /etc/init.d/xvfb start 10 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | grunt.loadNpmTasks('grunt-bump'); 3 | grunt.loadNpmTasks('grunt-contrib-clean'); 4 | grunt.loadNpmTasks('grunt-contrib-concat'); 5 | grunt.loadNpmTasks('grunt-contrib-jshint'); 6 | grunt.loadNpmTasks('grunt-contrib-uglify'); 7 | grunt.loadNpmTasks('grunt-contrib-watch'); 8 | grunt.loadNpmTasks('grunt-jscs'); 9 | grunt.loadNpmTasks('grunt-karma'); 10 | grunt.loadNpmTasks('grunt-ng-annotate'); 11 | 12 | grunt.initConfig({ 13 | config: { 14 | name: 'angular-tiny-eventemitter', 15 | e2ePort: 9000 16 | }, 17 | 18 | jshint: { 19 | lib: { 20 | options: { 21 | jshintrc: '.jshintrc' 22 | }, 23 | files: { 24 | src: ['src/**/*.js'] 25 | } 26 | }, 27 | test: { 28 | options: { 29 | jshintrc: '.jshintrc-test' 30 | }, 31 | files: { 32 | src: ['*.js', 'test/**/*.js'] 33 | } 34 | } 35 | }, 36 | 37 | jscs: { 38 | src: { 39 | options: { 40 | config: '.jscs.json' 41 | }, 42 | files: { 43 | src: ['*.js', '{src,test}/**/*.js'] 44 | } 45 | }, 46 | }, 47 | 48 | concat: { 49 | dist: { 50 | files: { 51 | 'dist/<%= config.name %>.js': ['src/*.js'] 52 | } 53 | } 54 | }, 55 | 56 | uglify: { 57 | dist: { 58 | files: { 59 | 'dist/<%= config.name %>.min.js': 'dist/<%= config.name %>.js' 60 | } 61 | } 62 | }, 63 | 64 | clean: { 65 | all: ['dist'] 66 | }, 67 | 68 | watch: { 69 | all: { 70 | files: ['src/**.js', 'test/*{,/*}'], 71 | tasks: ['build', 'karma:unit:run'] 72 | } 73 | }, 74 | 75 | ngAnnotate: { 76 | dist: { 77 | files: { 78 | 'dist/<%= config.name %>.js': 'dist/<%= config.name %>.js' 79 | } 80 | } 81 | }, 82 | 83 | bump: { 84 | options: { 85 | files: ['package.json', 'bower.json'], 86 | commitFiles: ['-a'], 87 | pushTo: 'origin' 88 | } 89 | }, 90 | 91 | karma: { 92 | unit: { 93 | configFile: 'test/configs/unit.conf.js', 94 | browsers: ['Chrome'], 95 | background: true 96 | }, 97 | unitci_firefox: { 98 | configFile: 'test/configs/unit.conf.js', 99 | browsers: ['Firefox', 'PhantomJS'], 100 | singleRun: true 101 | } 102 | } 103 | }); 104 | 105 | grunt.registerTask('default', ['test']); 106 | grunt.registerTask('build', ['clean', 'jshint', 'jscs', 'concat', 'ngAnnotate', 'uglify']); 107 | grunt.registerTask('test', ['build', 'karma:unit', 'watch:all']); 108 | grunt.registerTask('ci', ['build', 'karma:unitci_firefox']); 109 | }; 110 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2014-2016 by Ruben Vermeersch 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angular-tiny-eventemitter 2 | 3 | > Tiny event emitter for Angular.JS. 4 | 5 | [![Build Status](https://travis-ci.org/rubenv/angular-tiny-eventemitter.png?branch=master)](https://travis-ci.org/rubenv/angular-tiny-eventemitter) 6 | 7 | ## Installation 8 | Add angular-tiny-eventemitter to your project: 9 | 10 | ``` 11 | bower install --save angular-tiny-eventemitter 12 | ``` 13 | 14 | Add it to your HTML file: 15 | 16 | ```html 17 | 18 | ``` 19 | 20 | Reference it as a dependency for your app module: 21 | 22 | ```js 23 | angular.module('myApp', ['rt.eventemitter']); 24 | ``` 25 | 26 | ## Usage 27 | 28 | Simply inject it as a service: 29 | 30 | ```js 31 | angular.module('myApp').factory('MyType', function (eventEmitter) { 32 | function MyType(something) { 33 | this.value = something; 34 | } 35 | 36 | // Add event emitter methods to MyType. 37 | eventEmitter.inject(MyType); 38 | 39 | return MyType; 40 | }); 41 | ``` 42 | 43 | You can then use all the normal event emitter methods: 44 | 45 | ```js 46 | angular.module('myApp').controller('TestCtrl', function ($scope, MyType) { 47 | var thing = new MyType(123); 48 | 49 | function logTheEvent(arg1, arg2) { 50 | console.log('Got event: ' + arg1 + ', ' + arg2); 51 | } 52 | 53 | thing.on('event', logTheEvent); 54 | 55 | $scope.$on('$destroy', function () { 56 | // Remember to avoid memory leaks! 57 | thing.off('event', logTheEvent); 58 | }); 59 | 60 | thing.emit('event', 4, 8); 61 | }); 62 | ``` 63 | 64 | Alternatively, you can pass a $scope as the first parameter to on() or once() and the listener will be automatically unregistered if the $scope is destroyed. 65 | 66 | ```js 67 | angular.module('myApp').controller('TestCtrl', function ($scope, MyType) { 68 | var thing = new MyType(123); 69 | 70 | function logTheEvent(arg1, arg2) { 71 | console.log('Got event: ' + arg1 + ', ' + arg2); 72 | } 73 | 74 | // Passing $scope will register a $destroy event for you. 75 | thing.on($scope, 'event', logTheEvent); 76 | 77 | thing.emit('event', 4, 8); 78 | }); 79 | ``` 80 | 81 | ## License 82 | 83 | (The MIT License) 84 | 85 | Copyright (C) 2014-2016 by Ruben Vermeersch 86 | 87 | Permission is hereby granted, free of charge, to any person obtaining a copy 88 | of this software and associated documentation files (the "Software"), to deal 89 | in the Software without restriction, including without limitation the rights 90 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 91 | copies of the Software, and to permit persons to whom the Software is 92 | furnished to do so, subject to the following conditions: 93 | 94 | The above copyright notice and this permission notice shall be included in 95 | all copies or substantial portions of the Software. 96 | 97 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 98 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 99 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 100 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 101 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 102 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 103 | THE SOFTWARE. 104 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-tiny-eventemitter", 3 | "version": "1.2.0", 4 | "main": "dist/angular-tiny-eventemitter.js", 5 | "ignore": [ 6 | "**/.*", 7 | "src", 8 | "node_modules", 9 | "bower_components", 10 | "test", 11 | "Gruntfile.coffee" 12 | ], 13 | "dependencies": { 14 | "angular": "*" 15 | }, 16 | "devDependencies": { 17 | "angular-mocks": "*" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /dist/angular-tiny-eventemitter.js: -------------------------------------------------------------------------------- 1 | angular.module('rt.eventemitter', []).factory('eventEmitter', function () { 2 | var key = '$$tinyEventListeners'; 3 | 4 | function on($scope, event, fn) { 5 | if (typeof $scope === 'string') { 6 | fn = event; 7 | event = $scope; 8 | $scope = null; 9 | } 10 | 11 | if (!this[key]) { 12 | this[key] = {}; 13 | } 14 | 15 | var events = this[key]; 16 | if (!events[event]) { 17 | events[event] = []; 18 | } 19 | 20 | events[event].push(fn); 21 | 22 | var self = this; 23 | if ($scope) { 24 | $scope.$on('$destroy', function () { 25 | self.off(event, fn); 26 | }); 27 | } 28 | 29 | return this; 30 | } 31 | 32 | function once($scope, event, fn) { 33 | if (typeof $scope === 'string') { 34 | fn = event; 35 | event = $scope; 36 | $scope = null; 37 | } 38 | 39 | var self = this; 40 | var cb = function () { 41 | fn.apply(this, arguments); 42 | self.off(event, cb); 43 | }; 44 | 45 | this.on($scope, event, cb); 46 | return this; 47 | } 48 | 49 | function off(event, fn) { 50 | if (!this[key] || !this[key][event]) { 51 | return this; 52 | } 53 | 54 | var events = this[key]; 55 | if (!fn) { 56 | delete events[event]; 57 | } else { 58 | var listeners = events[event]; 59 | var index = listeners.indexOf(fn); 60 | if (index > -1) { 61 | listeners.splice(index, 1); 62 | } 63 | } 64 | return this; 65 | } 66 | 67 | function emit(event) { 68 | if (!this[key] || !this[key][event]) { 69 | return; 70 | } 71 | 72 | // Making a copy here to allow `off` in listeners. 73 | var listeners = this[key][event].slice(0); 74 | var params = [].slice.call(arguments, 1); 75 | for (var i = 0; i < listeners.length; i++) { 76 | listeners[i].apply(null, params); 77 | } 78 | return this; 79 | } 80 | 81 | return { 82 | inject: function (cls) { 83 | var proto = cls.prototype || cls; 84 | proto.on = on; 85 | proto.once = once; 86 | proto.off = off; 87 | proto.emit = emit; 88 | } 89 | }; 90 | }); 91 | -------------------------------------------------------------------------------- /dist/angular-tiny-eventemitter.min.js: -------------------------------------------------------------------------------- 1 | angular.module("rt.eventemitter",[]).factory("eventEmitter",function(){function a(a,b,c){"string"==typeof a&&(c=b,b=a,a=null),this[e]||(this[e]={});var d=this[e];d[b]||(d[b]=[]),d[b].push(c);var f=this;return a&&a.$on("$destroy",function(){f.off(b,c)}),this}function b(a,b,c){"string"==typeof a&&(c=b,b=a,a=null);var d=this,e=function(){c.apply(this,arguments),d.off(b,e)};return this.on(a,b,e),this}function c(a,b){if(!this[e]||!this[e][a])return this;var c=this[e];if(b){var d=c[a],f=d.indexOf(b);f>-1&&d.splice(f,1)}else delete c[a];return this}function d(a){if(this[e]&&this[e][a]){for(var b=this[e][a].slice(0),c=[].slice.call(arguments,1),d=0;d -1) { 61 | listeners.splice(index, 1); 62 | } 63 | } 64 | return this; 65 | } 66 | 67 | function emit(event) { 68 | if (!this[key] || !this[key][event]) { 69 | return; 70 | } 71 | 72 | // Making a copy here to allow `off` in listeners. 73 | var listeners = this[key][event].slice(0); 74 | var params = [].slice.call(arguments, 1); 75 | for (var i = 0; i < listeners.length; i++) { 76 | listeners[i].apply(null, params); 77 | } 78 | return this; 79 | } 80 | 81 | return { 82 | inject: function (cls) { 83 | var proto = cls.prototype || cls; 84 | proto.on = on; 85 | proto.once = once; 86 | proto.off = off; 87 | proto.emit = emit; 88 | } 89 | }; 90 | }); 91 | -------------------------------------------------------------------------------- /test/configs/unit.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function (config) { 2 | return config.set({ 3 | basePath: '../..', 4 | frameworks: ['mocha', 'chai'], 5 | files: [ 6 | 'bower_components/angular/angular.js', 7 | 'bower_components/angular-mocks/angular-mocks.js', 8 | 'dist/angular-tiny-eventemitter.js', 9 | 'test/*.js' 10 | ], 11 | port: 9877 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /test/tiny-eventemitter.js: -------------------------------------------------------------------------------- 1 | describe('EventEmitter', function () { 2 | var eventEmitter = null; 3 | var Person = null; 4 | var joe = null; 5 | var $rootScope = null; 6 | 7 | beforeEach(module('rt.eventemitter')); 8 | 9 | beforeEach(inject(function ($injector) { 10 | $rootScope = $injector.get('$rootScope'); 11 | eventEmitter = $injector.get('eventEmitter'); 12 | Person = function Person() {}; 13 | eventEmitter.inject(Person); 14 | joe = new Person(); 15 | })); 16 | 17 | it('Has an inject function', function () { 18 | assert.isFunction(eventEmitter.inject); 19 | }); 20 | 21 | it('Injects event methods on the class', function () { 22 | assert.isFunction(joe.on); 23 | assert.isFunction(joe.off); 24 | assert.isFunction(joe.emit); 25 | }); 26 | 27 | it('Emits events', function () { 28 | var called = false; 29 | joe.on('test', function () { called = true; }); 30 | joe.emit('test'); 31 | assert.equal(called, true); 32 | }); 33 | 34 | it('Subscribes with on', function () { 35 | var calls = 0; 36 | joe.emit('test'); 37 | joe.on('test', function () { calls += 1; }); 38 | joe.emit('test'); 39 | assert.equal(calls, 1); 40 | }); 41 | 42 | it('Unsubscribes with off', function () { 43 | var calls = 0; 44 | var listener = function () { calls += 1; }; 45 | joe.on('test', listener); 46 | joe.emit('test'); 47 | joe.off('test', listener); 48 | joe.emit('test'); 49 | assert.equal(calls, 1); 50 | }); 51 | 52 | it('Unsubscribes with off (all)', function () { 53 | var calls = 0; 54 | var calls2 = 0; 55 | var listener = function () { calls += 1; }; 56 | var listener2 = function () { calls2 += 1; }; 57 | joe.on('test', listener); 58 | joe.on('test', listener2); 59 | joe.emit('test'); 60 | joe.off('test'); 61 | joe.emit('test'); 62 | assert.equal(calls, 1); 63 | assert.equal(calls2, 1); 64 | }); 65 | 66 | it('Params', function () { 67 | var param = null; 68 | joe.on('test', function (p) { param = p; }); 69 | joe.emit('test', 3); 70 | assert.equal(param, 3); 71 | }); 72 | 73 | it('Calls correct listeners', function () { 74 | var calls = 0; 75 | var calls2 = 0; 76 | var listener = function () { calls += 1; }; 77 | var listener2 = function () { calls2 += 1; }; 78 | joe.on('test', listener); 79 | joe.on('test2', listener2); 80 | joe.emit('test'); 81 | assert.equal(calls, 1); 82 | assert.equal(calls2, 0); 83 | }); 84 | 85 | it('Can call off in listeners', function () { 86 | var calls = 0; 87 | var calls2 = 0; 88 | var listener = function () { 89 | calls += 1; 90 | joe.off('test', listener); 91 | }; 92 | var listener2 = function () { calls2 += 1; }; 93 | joe.on('test', listener); 94 | joe.on('test', listener2); 95 | joe.emit('test'); 96 | joe.emit('test'); 97 | assert.equal(calls, 1); 98 | assert.equal(calls2, 2); 99 | }); 100 | 101 | it('Unsubscribes automatically with once', function () { 102 | var calls = 0; 103 | joe.once('test', function () { calls += 1; }); 104 | joe.emit('test'); 105 | joe.emit('test'); 106 | assert.equal(calls, 1); 107 | }); 108 | 109 | it('should inject in objects to', function () { 110 | var __object = {}; 111 | eventEmitter.inject(__object); 112 | assert.isFunction(__object.on); 113 | assert.isFunction(__object.off); 114 | assert.isFunction(__object.emit); 115 | var called = false; 116 | __object.on('test', function () { called = true; }); 117 | __object.emit('test'); 118 | assert.equal(called, true); 119 | }); 120 | 121 | it('is chainable', function () { 122 | var calls = 0; 123 | joe.on('test', function () { calls += 1; }). 124 | once('test', function () { calls += 1; }); 125 | joe.emit('test'); 126 | joe.emit('test'); 127 | assert.equal(calls, 3); 128 | }); 129 | 130 | describe('auto-off', function () { 131 | 132 | var $scopeMock; 133 | 134 | beforeEach(function () { 135 | $scopeMock = $rootScope.$new(); 136 | }); 137 | 138 | it('optionally accepts a $scope as the first parameter', function () { 139 | var called = false; 140 | joe.on($scopeMock, 'test', function () { called = true; }); 141 | joe.emit('test'); 142 | assert.equal(called, true); 143 | }); 144 | 145 | it('clears listeners on $scope $destroy', function () { 146 | var called = false; 147 | joe.on($scopeMock, 'test', function () { called = true; }); 148 | $scopeMock.$emit('$destroy'); 149 | joe.emit('test'); 150 | assert.equal(called, false); 151 | }); 152 | 153 | }); 154 | }); 155 | --------------------------------------------------------------------------------