diff --git a/README.md b/README.md index 3157667b..8c28f456 100644 --- a/README.md +++ b/README.md @@ -140,6 +140,10 @@ To prevent multiple file uploads set singleFile to true. Note: avoid using `a` and `button` tags as file upload buttons, use span instead. * `.assignDrop(domNodes)` Assign one or more DOM nodes as a drop target. * `.on(event, callback)` Listen for event from Flow.js (see below) +* `.off([event, [callback]])`: + * `.off()` All events are removed. + * `.off(event)` Remove all callbacks of specific event. + * `.off(event, callback)` Remove specific callback of event. `callback` should be a `Function`. * `.upload()` Start or resume uploading. * `.pause()` Pause uploading. * `.resume()` Resume uploading. diff --git a/src/flow.js b/src/flow.js index d53718f6..d84ef03f 100644 --- a/src/flow.js +++ b/src/flow.js @@ -92,11 +92,11 @@ /** * List of events: - * even indexes stand for event names - * odd indexes stands for event callbacks - * @type {Array} + * key stands for event name + * value array list of callbacks + * @type {} */ - this.events = []; + this.events = {}; var $ = this; @@ -145,7 +145,32 @@ * @param {Function} callback */ on: function (event, callback) { - this.events.push(event.toLowerCase(), callback); + event = event.toLowerCase(); + if (!this.events.hasOwnProperty(event)) { + this.events[event] = []; + } + this.events[event].push(callback); + }, + + /** + * Remove event callback + * @function + * @param {string} [event] removes all events if not specified + * @param {Function} [fn] removes all callbacks of event if not specified + */ + off: function (event, fn) { + if (event !== undefined) { + event = event.toLowerCase(); + if (fn !== undefined) { + if (this.events.hasOwnProperty(event)) { + arrayRemove(this.events[event], fn); + } + } else { + delete this.events[event]; + } + } else { + this.events = {}; + } }, /** @@ -159,16 +184,16 @@ fire: function (event, args) { // `arguments` is an object, not array, in FF, so: args = Array.prototype.slice.call(arguments); - // Find event listeners, and support pseudo-event `catchAll` event = event.toLowerCase(); var preventDefault = false; - for (var i = 0; i <= this.events.length; i += 2) { - if (this.events[i] === event) { - preventDefault = this.events[i + 1].apply(this, args.slice(1)) === false || preventDefault; - } - if (this.events[i] === 'catchall') { - preventDefault = this.events[i + 1].apply(null, args) === false || preventDefault; - } + if (this.events.hasOwnProperty(event)) { + each(this.events[event], function (callback) { + preventDefault = callback.apply(this, args.slice(1)) === false || preventDefault; + }); + } + if (event != 'catchall') { + args.unshift('catchAll'); + preventDefault = this.fire.apply(this, args) === false || preventDefault; } return !preventDefault; }, @@ -1360,8 +1385,17 @@ } }; - - + /** + * Remove value from array + * @param array + * @param value + */ + function arrayRemove(array, value) { + var index = array.indexOf(value); + if (index > -1) { + array.splice(index, 1); + } + } /** * Extends the destination object `dst` by copying all of the properties from diff --git a/test/eventsSpec.js b/test/eventsSpec.js index 2ae8785b..24a982f8 100644 --- a/test/eventsSpec.js +++ b/test/eventsSpec.js @@ -8,16 +8,6 @@ describe('events', function() { flow = new Flow(); }); - it('should catch all events', function() { - var valid = false; - flow.on('catchall', function (event) { - expect(event).toBe('test'); - valid = true; - }); - flow.fire('test'); - expect(valid).toBeTruthy(); - }); - it('should catch an event', function() { var valid = false; flow.on('test', function () { @@ -42,6 +32,23 @@ describe('events', function() { expect(valid).toBeTruthy(); }); + it('should throw catchall event last', function() { + var executed = 0; + flow.on('catchall', function (event, one) { + expect(event).toBe('test'); + expect(one).toBe(1); + expect(executed).toBe(1); + executed++; + }); + flow.on('test', function (one) { + expect(one).toBe(1); + expect(executed).toBe(0); + executed++; + }); + flow.fire('test', 1); + expect(executed).toBe(2); + }); + it('should return event value', function() { flow.on('false', function () { return false; @@ -71,4 +78,27 @@ describe('events', function() { }); expect(flow.fire('maybe2')).toBeFalsy(); }); + + describe('off', function () { + var event; + beforeEach(function () { + event = jasmine.createSpy('event'); + flow.on('event', event); + }); + it('should remove event', function () { + flow.off('event'); + flow.fire('event'); + expect(event).not.toHaveBeenCalled(); + }); + it('should remove specific event', function () { + flow.off('event', event); + flow.fire('event'); + expect(event).not.toHaveBeenCalled(); + }); + it('should remove all events', function () { + flow.off(); + flow.fire('event'); + expect(event).not.toHaveBeenCalled(); + }); + }); }); \ No newline at end of file diff --git a/test/resumableFileSpec.js b/test/fileSpec.js similarity index 100% rename from test/resumableFileSpec.js rename to test/fileSpec.js diff --git a/test/setupSpec.js b/test/setupSpec.js index d69656a4..91413e30 100644 --- a/test/setupSpec.js +++ b/test/setupSpec.js @@ -23,7 +23,7 @@ describe('setup', function() { it('events should be empty', function() { expect(flow.events).toBeDefined(); - expect(flow.events.length).toBe(0); + expect(Object.keys(flow.events).length).toBe(0); }); it('set opts', function() {