API Docs for:
Show:

File: lib/Events/Events.js

!function(){
    /**
     * @module Events
     */

    var compat = 'createEvent' in document,
        pseudo_regex = /([^:]+)(?:\:([^(]*)(?:\((.*)\))?)?/,
        addEvent, addEvents, fireEvent, removeEvent, addEventOnce, Events, fireLatchedEvent;

    //=================
    //    UTILITIES
    //=================

    //utility function for cross-browser
    function indexOf(arr, target){
        var i, item;
        if (arr.indexOf) return arr.indexOf(target);

        for(i=0; item = arr[i]; i++) if (item == target) return i;

        return -1;
    }

    //handles warnings set by the library
    function warn(error){
        if (Events.strict){
            throw new Error(error);
        }else if ('console' in window){
            if (console.error) console.error(error);
            else if (console.warn) console.warn(error);
            else console.log(error);
        }
    }

    /**
     * removes the on* prefix from event names
     * @method Events.removeOn
     * @static
     *
     * @param {string} type
     *
     * @return {string}
     */
    function removeOn(string){
        return string.replace(/^on([A-Z])/, function(full, first){
            return first.toLowerCase();
        });
    }

    /**
     * returns a structured data object about a type's pseudo-events
     *
     * @method getPseudo
     * @private
     * @static
     *
     * @param {string} type
     *
     * @return {Object} data
     */
    function getPseudo(string){
        var match = string.match(pseudo_regex);

        if (string.split(':').length > 2) warn("Library does not support multiple pseudo events");

        return {
            name : match[1],
            pseudo : match[2],
            args : match[3]
        };
    }

    /**
     * proccesses an event type, returning a valid data object from that name
     * @method processType
     * @static
     * @private
     *
     * @param {string} type
     *
     * @return {Object} data
     */
    function processType(type){
        return getPseudo(removeOn(type));
    }

    /**
     * cross-browser function to create event object for fire method
     *
     * Created object will always have following properties:
     *  - dispatcher: a reference to dispatching object (since we can't use 'this')
     *  - args: arguments passed alongside the event
     *  - object_event: a flag set to true to easily check if this is an object event or a normal DOM event.
     *
     * @method Events.createEvent
     * @static
     * @private
     *
     * @param {string} type
     * @param {object} dispatcher
     * @param {mixed} args
     *
     * @return event object
     */
    function createEvent(type, dis, args){
        var ev;

        if (compat){
            ev = document.createEvent('UIEvents');
            ev.initUIEvent(type, false, false, window, 1);
        }else{
            ev = {};
        }

        ev.dispatcher = dis;
        ev.args = args;
        ev.object_event = true;

        return ev;
    }



    /**
     * Events Provider.
     *
     * Can function either as a standalone or a Mixin
     *
     * @class Events
     * @constructor
     *
     * @param {Element} el element to use as event target. Optional
     */
    Events = function Events(el){
        var $this = this;

        if (!compat){
            this.$events = {};
        }else{
            this.$event_element = el || document.createElement('events');
        }

        this.$latched = {};
        this.$once    = {};

        this.addEvent = addEvent;
        this.addEvents = addEvents;
        this.fireEvent = fireEvent;
        this.removeEvent = removeEvent;
        this.addEventOnce = addEventOnce;
        this.fireLatchedEvent = fireLatchedEvent;

        //since this code removes the reference to the events provider,
        //we want to make sure it runs after the rest of the loop is done.
        this.addEvent('destroy:delay(0)',function(){
            var names = "$event_element $latched $events $once addEvent removeEvent addEventOnce fireLatchedEvent".split(' '),
                i, name;

            for (i=0; name = names[i]; i++) $this[name] = null;
        });
    };

    //In case someone want to use these
    Events.removeOn = removeOn;
    Events.getPseudos = getPseudo;
    Events.processType = processType;
    Events.createEvent = createEvent;
    Events.strict = false;

    /*
     * Events.Pesudoes allows you to add pseudo behaviors
     *
     * Each object in the collection can hold both addEvent and fireEvent Methods
     *
     * The addEvent method will be fired *instead* of the normal behavior, and will be passed
     * the event type and fn
     *
     * The fireEvent method will be fired *after* the fireEvent method, and will be passed
     * the event name and the event object created
     *
     * Look at examples to see how it can be used
     *
     * @property Events.Pseudoes
     * @type {object}
     * @static
     */
    Events.Pseudoes = {
        once : {
            addEvent : function(type,fn){
                return this.addEventOnce(type, fn);
            }
        },

        latched : {
            fireEvent : function(type, args){
                return this.fireLatchedEvent(type,args);
            }
        },

        times : {
            addEvent : function(type, fn, ammount){
                var count = 0, $this = this;

                this.addEvent(type, function times(){
                    fn.apply(null, arguments);
                    count+=1;
                    if (count == ammount) $this.removeEvent(type,times);
                });
            }
        },

        delay : {
            addEvent : function(type, fn, delay){
                this.addEvent(type, function(){
                    setTimeout(fn,delay);
                });
            },
            fireEvent : function(type, args, delay){
                var $this = this;
                setTimeout(function(){
                    $this.fireEvent(type, args);
                }, delay);
            }
        }
    };

    //========================
    // cross-browser utilities
    //========================

    function register(obj, type, fn){
        if (compat){
            obj.$event_element.addEventListener(type,fn,false);
        }else{
            if (!obj.$events[type]) obj.$events[type] = [fn];
            else if (obj.$events[type].indexOf(fn)==-1){
                obj.$evetns[type].push(fn);
            }
        }
    }


    function dispatch(obj,type, ev){
        var i, fn;

        if (compat){
            obj.$event_element.dispatchEvent(ev);
        }else{
            for (i=0; fn = obj.$events[type]; i++){
                fn.apply(null,[ev]);
            }
        }
    }

    function remove(obj, type, fn){
        var index;

        if (compat){
            obj.$event_element.removeEventListener(type,fn,false);
        }else{
            if (!obj.$events[type]) return;

            index = indexOf(obj.$events[type],fn);

            if (index <0) return;

            obj.$events[type].splice(index,1);
        }
    }

    //=======================
    // Function Declarations
    //=======================


    /**
     * Adds an event
     *
     * @method addEvent
     *
     * @param {String}    the event type
     * @param {Function}  a function to add
     *
     * @chainable
     */
    addEvent = function addEvent(type,fn){
        var data = processType(type),
            pseudo_fn = Events.Pseudoes[data.pseudo] && Events.Pseudoes[data.pseudo].addEvent,
            args = this.$latched[data.name] && this.$latched[data.name].args,
            ev;

        if (pseudo_fn){
            return pseudo_fn.apply(this,[data.name,fn,data.args]);
        }

        register(this,data.name, fn);

        if (this.$latched && this.$latched[data.name]){
            ev = createEvent(data.name, this, args);
            fn.apply(null,[ev]);
        }

        return this;
     };

    /**
     * Helper to add multiple events at once
     *
     * @method addEvents
     *
     * @param {Object} literal object of event types => callbacks
     *
     * @chainable
     */
    addEvents = function addEvents(events){
        var type;

        for (type in events) if (events.hasOwnProperty(type)){
            this.addEvent(type, events[type]);
        }

        return this;
    };

    /**
     * dispatches an event
     *
     * @method fireEvent
     *
     * @param {String} event type
     * @param {Mixed}  arguments to pass with the event
     *
     * @chainable
     */
    fireEvent = function fireEvent(type, args){
        var data = processType(type),
            pseudo_fn = Events.Pseudoes[data.pseudo] && Events.Pseudoes[data.pseudo].fireEvent,
            ev, fn,
            once_arr,
            temp_arr;

        if (pseudo_fn){
            return pseudo_fn.call(this,data.name,args);
        }

        //in case one of the callbacks will try and add another once event,
        //we keep a reference of the once stack, and then empty it
        once_arr = this.$once[data.name];
        this.$once[data.name] = null;

        ev = createEvent(data.name, this, args);

        dispatch(this,data.name,ev);

        if (!once_arr) return this;

        while (fn = once_arr.pop()){
            this.removeEvent(data.name, fn, true);
        }

        return this;
    };

    /**
     * removes a function from an event
     *
     * @method removeEvent
     *
     * @param {String}   event type
     * @param {Function} function to remove from stack
     *
     * @chainable
     */
    removeEvent = function removeEvent(type, fn,no_once){
        var data = processType(type),
            index;

        remove(this,data.name, fn);

        if (!no_once && this.$once[data.name] && (index = this.$once[data.name].indexOf(fn))>-1){
            this.$once[data.name].splice(index,1);
        }

        return this;
    };

    /**
     * Adds an event for one execution, then removes it
     *
     * @method addEventOnce
     *
     * @param {String}    the event type
     * @param {Function}  a function to add
     *
     * @chainable
     */
    addEventOnce = function addEventOnce(type, fn){
        var $this = this,
            data = processType(type);

        if (!this.$once[data.name]) this.$once[data.name] = [];
        if (this.$once[data.name].indexOf(fn) == -1){
            this.$once[data.name].push(fn);
        }

        return this.addEvent(data.name, fn);
    };

    /**
     * Fires a latched event
     *
     * @method fireLatchedEvent
     *
     * @param {String} the event type
     * @param {Mixed}  arguments to pass with the event
     *
     * @chainable
     */
    fireLatchedEvent = function fireLatchedEvent(type, args){
        if (!this.$latched) this.$latched = {};

        this.$latched[type] = {args : args};
        this.fireEvent(type,args);

        return this;
    };     

    //expose Mixin to provided namespace
    this.Events = Events;     
}.call(this);