dlueth/qoopido.nucleus

View on GitHub
src/emitter.js

Summary

Maintainability
B
5 hrs
Test Coverage
/**
 * @use /demand/weakmap
 */

(function() {
    'use strict';

    function definition(Weakmap, iterate) {
        var regexMatchExcludedMethods      = /^(_|((get|has|is)([A-Z]|$))|(on|one|off|emit|constructor)$)/,
            objectDefineProperty           = Object.defineProperty,
            objectGetOwnPropertyNames      = Object.getOwnPropertyNames,
            objectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor,
            objectGetPrototypeOf           = Object.getPrototypeOf,
            objectPrototype                = Object.prototype,
            storage                        = new Weakmap();

        function getStorage(context) {
            return storage.get(context) || (storage.set(context, {}) && storage.get(context));
        }

        function getPropertyNames(object) {
            var prototype = object,
                keys      = [],
                names, i, name;

            try {
                while(prototype !== objectPrototype) {
                    for(names = objectGetOwnPropertyNames(prototype), i = 0; (name = names[i]); i++) {
                        keys.indexOf(name) === -1 && keys.push(name);
                    }

                    prototype = prototype.__proto__;
                }
            } catch(error) {
                // required for IE compatibility
            }

            return keys;
        }

        function getPropertyDescriptor(object, property) {
            var prototype = object,
                descriptor;

            while(prototype && !(descriptor = objectGetOwnPropertyDescriptor(prototype, property))) {
                prototype = prototype.__proto__;
            }

            return descriptor;
        }

        function wrap() {
            var self       = this,
                prototype  = self.constructor.prototype,
                properties = getPropertyNames(prototype);

            properties.forEach(function(property) {
                var descriptor = getPropertyDescriptor(prototype, property),
                    event;

                if(!descriptor.get && typeof self[property] === 'function' && regexMatchExcludedMethods.test(property) === false) {
                    event = property.charAt(0).toUpperCase() + property.slice(1);

                    descriptor.value = function() {
                        var result;

                        self.emit('pre' + event, arguments);

                        result = prototype[property].apply(self, arguments);

                        self.emit('post' + event, arguments, result);

                        return result;
                    };

                    objectDefineProperty(self, property, descriptor);
                }
            });
        }

        function Emitter() {
            if(objectGetPrototypeOf(self) !== Emitter.prototype) {
                wrap.call(this);
            }

            return this;
        }

        Emitter.prototype = {
            on: function(events, fn) {
                var self    = this,
                    pointer = getStorage(self),
                    i = 0, event;

                events = events.split(' ');

                for(; (event = events[i]) !== undefined; i++) {
                    (pointer[event] = pointer[event] || []).push(fn);
                }

                return self;
            },
            one: function(events, fn, each) {
                var self = this;

                each = (each !== false);

                self.on(events, function listener(event) {
                    self.off(((each === true) ? event : events), listener);

                    fn.apply(this, arguments);
                });

                return self;
            },
            off: function(events, fn) {
                var self    = this,
                    pointer = getStorage(self),
                    i = 0, event, j, listener;

                if(events) {
                    events = events.split(' ');

                    for(; (event = events[i]) !== undefined; i++) {
                        pointer[event] = pointer[event] || [];

                        if(fn) {
                            for(j = 0; (listener = pointer[event][j]) !== undefined; j++) {
                                if(listener === fn) {
                                    pointer[event].splice(j, 1);

                                    j--;
                                }
                            }
                        } else {
                            pointer[event].length = 0;
                        }
                    }
                } else {
                    iterate(self.listener, function(event) {
                        pointer[event].length = 0;
                    });
                }

                return self;
            },
            emit: function(event) {
                var self = this,
                    pointer, temp, i = 0, listener;

                if(event) {
                    pointer = getStorage(self);

                    pointer[event] = pointer[event] || [];
                    temp           = pointer[event].slice();

                    for(; (listener = temp[i]) !== undefined; i++) {
                        listener.apply(self, arguments);
                    }
                }

                return self;
            }
        };

        return Emitter;
    }

    provide([ '/demand/weakmap', '/demand/function/iterate' ], definition);
}());