├── .gitignore ├── license ├── min-signal-spec.js ├── min-signal.js ├── package.json └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules/ 3 | package-lock.json -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | Copyright (c) 2021 Edan Kwan 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 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. -------------------------------------------------------------------------------- /min-signal-spec.js: -------------------------------------------------------------------------------- 1 | describe('MinSignal', function tests() { 2 | 3 | var MinSignal = require('./min-signal'); 4 | var assume = require('assume'); 5 | 6 | describe('#add()', function () { 7 | 8 | it('adds multiple times with same function(without context) once only', function (done) { 9 | var signal = new MinSignal(); 10 | 11 | function foo() {} 12 | 13 | signal.add(foo); 14 | signal.add(foo); 15 | 16 | assume(signal._listeners.length).equals(1); 17 | done(); 18 | 19 | }); 20 | 21 | it('adds multiple times with same function(with context) once only', function (done) { 22 | var signal = new MinSignal(); 23 | 24 | function foo() {} 25 | 26 | var a = {}; 27 | var b = {}; 28 | 29 | signal.add(foo, a); 30 | signal.add(foo, a); 31 | signal.add(foo, b); 32 | signal.add(foo, b); 33 | signal.add(foo, b); 34 | 35 | assume(signal._listeners.length).equals(2); 36 | done(); 37 | 38 | }); 39 | 40 | it('prioritizes the callbacks with first come first serve base', function (done) { 41 | var signal = new MinSignal(); 42 | 43 | function foo() {} 44 | 45 | var a = {}; 46 | var b = {}; 47 | var c = {}; 48 | var d = {}; 49 | var e = {}; 50 | var f = {}; 51 | 52 | signal.add(foo, a, 12); 53 | signal.add(foo, b, 2); 54 | signal.add(foo, c, 5); 55 | signal.add(foo, d); 56 | signal.add(foo, e, 7); 57 | signal.add(foo, f, 12); 58 | assume(signal._listeners[0].c).equals(d); 59 | assume(signal._listeners[1].c).equals(b); 60 | assume(signal._listeners[2].c).equals(c); 61 | assume(signal._listeners[3].c).equals(e); 62 | assume(signal._listeners[4].c).equals(f); 63 | assume(signal._listeners[5].c).equals(a); 64 | done(); 65 | 66 | }); 67 | 68 | it('add prepend argments', function (done) { 69 | var signal = new MinSignal(); 70 | 71 | function foo() {} 72 | 73 | signal.add(foo, null, 0, 1, 2, 3); 74 | assume(signal._listeners[0].a).deep.equals([1, 2, 3]); 75 | done(); 76 | 77 | }); 78 | 79 | }); 80 | 81 | describe('#dispatch()', function () { 82 | 83 | it('dispatchs stack', function (done) { 84 | var signal = new MinSignal(); 85 | var count = 0; 86 | 87 | signal.add(function (step) { 88 | if (step === 0) { 89 | count += 1; 90 | signal.dispatch(1); 91 | } else if (step === 1) { 92 | count += 2; 93 | signal.dispatch(2); 94 | } else if (step === 2) { 95 | count += 3; 96 | assume(count).equals(6); 97 | done(); 98 | } 99 | }) 100 | signal.dispatch(0); 101 | 102 | }); 103 | 104 | it('dispatchs multiple callbacks', function (done) { 105 | var signal = new MinSignal(); 106 | var count = 0; 107 | 108 | function add() { 109 | count += this.val; 110 | } 111 | 112 | var a = {val : 1}; 113 | var b = {val : 2}; 114 | var c = {val : 3}; 115 | 116 | signal.add(add, a); 117 | signal.add(add, b); 118 | signal.add(add, c); 119 | signal.dispatch(); 120 | 121 | assume(count).equals(6); 122 | done(); 123 | 124 | }); 125 | 126 | 127 | it('dispatchs callback with arguments', function (done) { 128 | var signal = new MinSignal(); 129 | var count = 0; 130 | 131 | function add(a) { 132 | count += a; 133 | } 134 | 135 | var a = {}; 136 | var b = {}; 137 | var c = {}; 138 | 139 | signal.add(add, a); 140 | signal.add(add, b); 141 | signal.add(add, c); 142 | signal.dispatch(10); 143 | 144 | assume(count).equals(30); 145 | done(); 146 | 147 | }); 148 | 149 | 150 | it('dispatchs callback with prepend arguments', function (done) { 151 | var signal = new MinSignal(); 152 | var count = 0; 153 | 154 | function add(a, b) { 155 | count += a + b; 156 | } 157 | 158 | var a = {}; 159 | var b = {}; 160 | var c = {}; 161 | 162 | signal.add(add, a, 0, 1); 163 | signal.add(add, b, 0, 2); 164 | signal.add(add, c, 0, 3); 165 | signal.dispatch(10); 166 | 167 | assume(count).equals(36); 168 | done(); 169 | 170 | }); 171 | 172 | 173 | it('dispatchs with correct priority', function (done) { 174 | var signal = new MinSignal(); 175 | var stack = []; 176 | 177 | function add() { 178 | stack.push(this); 179 | } 180 | 181 | var a = {}; 182 | var b = {}; 183 | var c = {}; 184 | var d = {}; 185 | var e = {}; 186 | var f = {}; 187 | 188 | signal.add(add, a, 12); 189 | signal.add(add, b, 2); 190 | signal.add(add, c, 5); 191 | signal.add(add, d); 192 | signal.add(add, e, 7); 193 | signal.add(add, f, 12); 194 | 195 | signal.dispatch(); 196 | assume(stack).deep.equals([a, f, e, c, b, d]); 197 | done(); 198 | 199 | }); 200 | 201 | 202 | it('remove on dispatch', function (done) { 203 | var signal = new MinSignal(); 204 | var sum = 0; 205 | 206 | var a = {}; 207 | var b = {}; 208 | var c = {}; 209 | 210 | function add(val) { 211 | sum += val; 212 | signal.remove(add, b); 213 | } 214 | 215 | signal.add(add, a, 0, 1); 216 | signal.add(add, b, 0, 3); 217 | signal.add(add, c, 0, 5); 218 | 219 | signal.dispatch(); 220 | assume(sum).equals(6); 221 | done(); 222 | 223 | }); 224 | 225 | 226 | it('add on dispatch', function (done) { 227 | var signal = new MinSignal(); 228 | var sum = 0; 229 | 230 | var a = {}; 231 | var b = {}; 232 | var c = {}; 233 | 234 | function add(val) { 235 | sum += val; 236 | signal.add(add, c, 0, 5); 237 | } 238 | 239 | signal.add(add, a, 0, 1); 240 | signal.add(add, b, 0, 3); 241 | 242 | signal.dispatch(); 243 | assume(sum).equals(6); 244 | done(); 245 | 246 | }); 247 | 248 | }); 249 | 250 | }); 251 | -------------------------------------------------------------------------------- /min-signal.js: -------------------------------------------------------------------------------- 1 | var MinSignal = (function(undef){ 2 | 3 | function MinSignal() { 4 | this._listeners = []; 5 | this.dispatchCount = 0; 6 | } 7 | 8 | var _p = MinSignal.prototype; 9 | 10 | _p.add = add; 11 | _p.addOnce = addOnce; 12 | _p.remove = remove; 13 | _p.dispatch = dispatch; 14 | 15 | var ERROR_MESSAGE_MISSING_CALLBACK = 'Callback function is missing!'; 16 | 17 | var _slice = Array.prototype.slice; 18 | 19 | function _sort(list) { 20 | list.sort(function(a, b){ 21 | a = a.p; 22 | b = b.p; 23 | return b < a ? 1 : b > a ? -1 : 0; 24 | }); 25 | } 26 | 27 | /** 28 | * Adding callback to the signal 29 | * @param {Function} the callback function 30 | * @param {object} the context of the callback function 31 | * @param {number} priority in the dispatch call. The higher priority it is, the eariler it will be dispatched. 32 | * @param {any...} additional argument prefix 33 | */ 34 | function add (fn, context, priority, args) { 35 | 36 | if(!fn) { 37 | throw ERROR_MESSAGE_MISSING_CALLBACK; 38 | } 39 | 40 | priority = priority || 0; 41 | var listeners = this._listeners; 42 | var listener, realFn, sliceIndex; 43 | var i = listeners.length; 44 | while(i--) { 45 | listener = listeners[i]; 46 | if(listener.f === fn && listener.c === context) { 47 | return false; 48 | } 49 | } 50 | if(typeof priority === 'function') { 51 | realFn = priority; 52 | priority = args; 53 | sliceIndex = 4; 54 | } 55 | listeners.unshift({f: fn, c: context, p: priority, r: realFn || fn, a: _slice.call(arguments, sliceIndex || 3), j: 0}); 56 | _sort(listeners); 57 | } 58 | 59 | /** 60 | * Adding callback to the signal but it will only trigger once 61 | * @param {Function} the callback function 62 | * @param {object} the context of the callback function 63 | * @param {number} priority in the dispatch call. The higher priority it is, the eariler it will be dispatched. 64 | * @param {any...} additional argument prefix 65 | */ 66 | function addOnce (fn, context, priority, args) { 67 | 68 | if(!fn) { 69 | throw ERROR_MESSAGE_MISSING_CALLBACK; 70 | } 71 | 72 | var self = this; 73 | var realFn = function() { 74 | self.remove.call(self, fn, context); 75 | return fn.apply(context, _slice.call(arguments, 0)); 76 | }; 77 | args = _slice.call(arguments, 0); 78 | if(args.length === 1) { 79 | args.push(undef); 80 | } 81 | args.splice(2, 0, realFn); 82 | add.apply(self, args); 83 | } 84 | 85 | /** 86 | * Remove callback from the signal 87 | * @param {Function} the callback function 88 | * @param {object} the context of the callback function 89 | * @return {boolean} return true if there is any callback was removed 90 | */ 91 | function remove (fn, context) { 92 | if(!fn) { 93 | this._listeners.length = 0; 94 | return true; 95 | } 96 | var listeners = this._listeners; 97 | var listener; 98 | var i = listeners.length; 99 | while(i--) { 100 | listener = listeners[i]; 101 | if(listener.f === fn && (!context || (listener.c === context))) { 102 | listener.j = 0; 103 | listeners.splice(i, 1); 104 | return true; 105 | } 106 | } 107 | return false; 108 | } 109 | 110 | 111 | /** 112 | * Dispatch the callback 113 | * @param {any...} additional argument suffix 114 | */ 115 | function dispatch(args) { 116 | args = _slice.call(arguments, 0); 117 | this.dispatchCount++; 118 | var dispatchCount = this.dispatchCount; 119 | var listeners = this._listeners; 120 | var listener, context, stoppedListener; 121 | var i = listeners.length; 122 | while(i--) { 123 | listener = listeners[i]; 124 | if(listener && (listener.j < dispatchCount)) { 125 | listener.j = dispatchCount; 126 | if(listener.r.apply(listener.c, listener.a.concat(args)) === false) { 127 | stoppedListener = listener; 128 | break; 129 | } 130 | } 131 | } 132 | listeners = this._listeners; 133 | i = listeners.length; 134 | while(i--) { 135 | listeners[i].j = 0; 136 | } 137 | return stoppedListener; 138 | } 139 | 140 | if (typeof module !== 'undefined') { 141 | module.exports = MinSignal; 142 | } 143 | 144 | }()); 145 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "min-signal", 3 | "version": "1.0.2", 4 | "description": "lightweight signal with priority support.", 5 | "main": "min-signal.js", 6 | "author": "Edan Kwan", 7 | "license": "MIT", 8 | "repository": { 9 | "type": "git", 10 | "url": "git://github.com/edankwan/min-signal.git" 11 | }, 12 | "keywords": [ 13 | "events", 14 | "signals", 15 | "priority", 16 | "signal" 17 | ], 18 | "bugs": { 19 | "url": "https://github.com/edankwan/min-signal/issues" 20 | }, 21 | "homepage": "https://github.com/edankwan/min-signal", 22 | "scripts": { 23 | "test": "mocha min-signal-spec.js" 24 | }, 25 | "devDependencies": { 26 | "assume": "~1.4.1", 27 | "mocha": "^8.4.0" 28 | }, 29 | "dependencies": {} 30 | } 31 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # min-signal 2 | 3 | [**min-signal**](http://edankwan.github.io/min-signal) is a lightweight version of [**js-signals**](https://millermedeiros.github.io/js-signals/) which is heavily inspired by [**Robert Penner’s AS3-Signals**](https://github.com/robertpenner/as3-signals). Signal is an alternative to `Events`/`Promises`/`Callbacks`. To know more about the differences, you can check out the [**Wiki page**](https://github.com/millermedeiros/js-signals/wiki/Comparison-between-different-Observer-Pattern-implementations) of js-signals. 4 | 5 | Unlike other trimmed version js-signals, min-signal preverses some useful features like **priorty** and **arguments injection** which comes in handy during the development. 6 | 7 | Dispatch the same signal within the signal callback is not recommended. If it happens the new signal execution will kick in and stop the unfinished execution. 8 | 9 | Examples 10 | --- 11 | 12 | ### Basic Example 13 | Example of using the basic functions: `add()`, `dispatch()` and `remove()` with **min-signal** 14 | ````js 15 | var onStarted = new MinSignal(); 16 | 17 | function callback(param1, param2) { 18 | console.log(param1 + ' ' + param2); 19 | } 20 | 21 | onStarted.add(callback); //add listener 22 | onStarted.dispatch('foo', 'bar'); //dispatch signal passing custom parameters 23 | onStarted.remove(callback); //remove a single listener 24 | ```` 25 | 26 | ### addOnce() 27 | If you want the callback to be dispatched only once, you can use `addOnce()`. 28 | ````js 29 | var onStarted = new MinSignal(); 30 | 31 | function callback() { 32 | console.log('hello'); 33 | } 34 | 35 | onStarted.addOnce(callback); 36 | onStarted.dispatch(); // log : hello 37 | onStarted.dispatch(); // do nothing 38 | ```` 39 | 40 | ### Priority 41 | The third argument of the `add()` and `addOnce()` is the priority, the higher it is, the earlier it will be dispatched. The default value is 0, and order by first comes first served principle. 42 | ````js 43 | var onStarted = new MinSignal(); 44 | 45 | function callback1() { 46 | console.log('1'); 47 | } 48 | 49 | function callback2() { 50 | console.log('2'); 51 | } 52 | 53 | function callback3() { 54 | console.log('3'); 55 | } 56 | 57 | onStarted.add(callback1); 58 | onStarted.add(callback2); 59 | onStarted.add(callback3, null, 10); 60 | onStarted.dispatch(); // log : 3, 1, 2 61 | ```` 62 | 63 | ### Context 64 | Like function binding, you can provide the context for the callback binding. Same context with the same function will be ignored. 65 | ````js 66 | var onStarted = new MinSignal(); 67 | 68 | function callback() { 69 | console.log(this.id); 70 | } 71 | 72 | var a = {id: 'a'}; 73 | var b = {id: 'b'}; 74 | 75 | onStarted.add(callback, a); 76 | onStarted.add(callback, a); 77 | onStarted.add(callback, b); 78 | onStarted.dispatch(); // log : a, b 79 | ```` 80 | 81 | ### Argument prefix 82 | You can also add argument prefix with the `add()` and `addOnce()` like in `Function.bind()`. One thing need to keep in mind is that, for the duplicated callback checking, it only check the function and the context, same function with the same context with new argument prefix will be rejected instead of overriding. 83 | ````js 84 | var onStarted = new MinSignal(); 85 | 86 | function callback(param1, param2, param3, param4) { 87 | console.log(param1 + param2 + param3 + param4); 88 | } 89 | 90 | onStarted.add(callback, null, 0, 'a', 'b'); 91 | onStarted.dispatch('c', 'd'); // log : abcd 92 | ```` 93 | 94 | ### Stop Propagation 95 | You can stop propagation by returning `false` in the callback function. By stopping propagation, in the `dispatch()` call it will return the listener which triggered the stop propagation. 96 | ````js 97 | var onStarted = new MinSignal(); 98 | 99 | onStarted.add(function(){ 100 | return false; // returns "false" to stop propagation 101 | }); 102 | onStarted.add(function(){ 103 | console.log('wont be triggered'); // this function won't be trigger 104 | }); 105 | 106 | onStarted.dispatch(); // log : {...} // will return the listener that stopped the propagation 107 | ```` 108 | 109 | 110 | Installation 111 | --- 112 | ### Manually 113 | Download min-signal [**here**](https://raw.githubusercontent.com/edankwan/min-signal/master/min-signal.js). 114 | 115 | ### Npm 116 | Check out the npm page [**here**](https://www.npmjs.com/package/min-signal). 117 | 118 | 119 | 120 | Testing 121 | --- 122 | run `npm run test` to run the test suite. 123 | 124 | TODO 125 | --- 126 | - additional features? 127 | - add more tests 128 | 129 | License 130 | --- 131 | MIT --------------------------------------------------------------------------------