eliace/ergo-js

View on GitHub
js/core/events.js

Summary

Maintainability
F
3 days
Test Coverage

//= require object


/**
 * @name Ergo.events
 * @namespace
 */


/**
 *
 * @class
 * @name Ergo.events.Event
 * @extends Ergo.core.Object
 */
// Ergo.defineClass('Ergo.core.Event', 'Ergo.core.Object', /** @lends Ergo.events.Event.prototype */{
//
//     _initialize: function(baseEvent) {
//         this.base = baseEvent;
//         this._queue = [];
//     },


Ergo.core.Event = function(baseEvent) {
    this.base = baseEvent;
    this._queue = [];
}

Ergo.merge(Ergo.core.Event.prototype, {

    stop: function(stopHtmlEvent) {
//        if(this.base) this.base.stopPropagation(); //FIXME
        var e = this.base;
        while(e && stopHtmlEvent !== false) {
            if(e.stopPropagation) {
                e.stopPropagation();
                break;
            }
            e = e.base;
        }
        this.stopped = true;
        // if(immediate)
        //     this.stopedImmediate = true;
    },
    interrupt: function(interruptHtmlEvent) {
        var e = this.base;
        while(e && interruptHtmlEvent !== false) {
            if(e.stopImmediatePropagation) {
                e.stopImmediatePropagation();
                break;
            }
            e = e.base;
        }
//        if(this.base) this.base.stopImmediatePropagation(); //FIXME
        this.stopped = true;
        this.stopedImmediate = true;
    },
    yield: function(callback) {

        while(this._queue.length) {
            var h = this._queue.pop();
            h.callback.call(h.target, this);
            if(this.stopedImmediate) break;
        }

        // if( !this._yielded ) {
        //     this._yielded = [];
        // }
        // this._yielded.push(callback);
    },
    cancel: function() {
        this.canceled = true;
    }

});


// Ergo.defineClass('Ergo.events.Event', Ergo.core.Object, /** @lends Ergo.events.Event.prototype */{
//
//     /**
//      * @param {Object} overrides
//      * @param {Object} baseEvent
//      */
//     _initialize: function(overrides, baseEvent) {
//         this._super();
// //        Ergo.events.Event.superclass._initialize.call(this);
//
//         if(overrides) Ergo.merge(this, overrides);
//
// //        this.is_stopped = false;
//         this.baseEvent = baseEvent;
//         this.canceled = false;
//         this.stopped = false;
//     },
//
//
//     cancel: function(){
//         this.canceled = true;
//     },
//
//     stop: function() {
//         if(this.baseEvent) this.baseEvent.stopPropagation();
//         this.stopped = true;
//     }
//
//
//
// });


// Ergo.defineClass('Ergo.events.CancelEvent', 'Ergo.events.Event', /** @lends Ergo.events.CancelEvent.prototype */{
//
    // /**
     // * @constructs
      // * @extends Ergo.events.Event
     // * @param {Object} overrides
     // * @param {Object} baseEvent
     // */
    // _initialize: function(overrides, baseEvent) {
        // this._super(overrides, baseEvent);
// //        Ergo.events.CancelEvent.superclass._initialize.apply(this, arguments);
        // this.isCanceled = false;
    // },
//
    // cancel: function(){
        // this.isCanceled = true;
    // }
//
// });




/**
 * Диспетчер событий
 *
 * @class
 * @name Ergo.core.Observer
 * @extends Ergo.core.Object
 */
// Ergo.defineClass('Ergo.core.Observer', 'Ergo.core.Object', /** @lends Ergo.core.Observer.prototype */{
//
//     _initialize: function(target) {
//         this.events = {};
//         this.target = target;
//     },


Ergo.core.Observer = function(target) {
    this.events = {};
    this.target = target;
}

Ergo.merge(Ergo.core.Observer.prototype, /** @lends Ergo.core.Observer.prototype */{



    /**
     * Регистрируем событие.
     *
     * on(type, callback, target)
     */
    on: function(type, callback, target) {
        if(!(type in this.events)) this.events[type] = [];//new Ergo.core.Array();
        var h_arr = this.events[type];
        h_arr.push({'callback': callback, 'target': target || this.target});
        return this;
    },

    /**
     * Однократная обработка события
     */
    once: function(type, callback, target) {
        if(!(type in this.events)) this.events[type] = [];
        var h_arr = this.events[type];
        h_arr.push({'callback': callback, 'target': target || this.target, 'once': true});
        return this;
    },

    /**
     * Убираем регистрацию события.
     *
     * off(type)
     * off(callback)
     * off(type, callback)
     * off(target)
     * off();
     */
    off: function(arg, arg2) {

        var events = this.events;

        if(arguments.length == 0) {
            this.events = {};
        }
        else if(arguments.length == 2){
            events[arg] = events[arg].filter( function(h) { return h.callback != arg2; } );
        }
        else if( $.isString(arg) ){
            // удаляем все обработчики с данным именем
            delete events[arg];
        }
        else if( $.isFunction(arg) ){
            // удаляем указанный обработчик
//            for(var i = 0; i < events.length; i++) {
            for(var i in events) {
                events[i] = events[i].filter( function(h) { return h.callback != arg; } );
            }
        }
        else {
            // удаляем все обработчики для указанного объекта
//            for(var i = 0; i < e.length; i++) {
            for(var i in events) {
                events[i] = events[i].filter( function(h) { return h.target != arg; } );
            }
        }

        return this;
    },

    // unreg_all: function() {
    //     this.events = {};
    // },

    /**
     * Генерация события
     * @param {string} name наименование события
     * @param {Any} data данные события
     * @param {Ergo.core.Event} baseEvent инициирующее событие
     *
     * Обработка данных:
     *  * Object - "слияние" с объектом события
     *  * Event - "проброс" события
     *
     *  Остальные типы данных будут записаны в поле $data
     *
     */
    fire: function(type, _event, baseEvent) {

        // "ленивая" генерация базового события
        // var _event = {
        //     base: baseEvent,
        //     stop: function(immediate) {
        //         if(this.base) this.base.stopPropagation(); //FIXME
        //         this.stopped = true;
        //         if(immediate)
        //             this.stopedImmediate = true;
        //     },
        //     interrupt: function() {
        //         if(this.base) this.base.stopImmediatePropagation(); //FIXME
        //         this.stopped = true;
        //         this.stopedImmediate = true;
        //     },
        //     yield: function() {
        //         this._yielded = true;
        //     }
        // };
        var e = new Ergo.core.Event(baseEvent);

        if(arguments.length == 1) {
//            _event = new Ergo.events.Event();
//            e = new Ergo.events.Event();
        }
        else if(_event.constructor === Object) {
            $ergo.merge(e, _event);
        }
        else if(_event instanceof Ergo.core.Event) {
            e = _event;
        }
        else {
            e.$data = _event;
        }
//         else if( _event.constructor === Object ){
//             Ergo.merge(e, _event);
// //            _event.baseEvent = baseEvent;
// //            e = new Ergo.events.Event(e, baseEvent);
//         }
//         else {
//             e = _event;
//         }



//        var self = this;
        var h_arr = this.events[type];
        if(h_arr && h_arr.length) {

//            var yielded = [];

            e._queue = h_arr.slice();

            while(e._queue.length) {
                var h = e._queue.pop();
                h.callback.call(h.target, e, type);
                if(e.stopedImmediate) break;
            }

            // for(var i = h_arr.length-1; i >= 0; i--) {
            //     var h = h_arr[i];
            //     // if(e._yielded) {
            //     //     yielded.push(h);
            //     // }
            //     // else {
            //     h.callback.call(h.target, e, type);
            //     if(e.stopedImmediate) break;
            //     // }
            // }
            //
            //
            // if(e._yielded) {
            //     e._yielded.reverse().forEach(function(callback) {
            //         callback(e);
            //     });
            // }

            // for(var i = yielded.length-1; i >= 0; i--) {
            //     var h = yielded[i];
            //     h.callback.call(h.target, e, type);
            // }

//             h_arr.forEach( function(h){
//                 // вызываем обработчик события
//                 h.callback.call(h.target, e, type);
//                 // если усьановлен флаг остановки, то прекращаем обработку событий
// //                if(e.stopped) return false;
//             });
            // ?

            // удаляем обработчики, имеющие статус `once`
            if(this.events[type])
                this.events[type] = this.events[type].filter( function(h) { return !h.once; } );
        }

        if(e.after && !e.stopped)
            e.after.call(this.target, e, type);

//        self.on_fire(type, e, baseEvent);

        return e;
    },


    emit: function() {
        this.fire.apply(this, arguments);
    }


    // bubble: function() {
//
    // }
//
//
    // /**
     // * Метод, вызываемый после отрабатывания метода fire
     // *
     // */
    // on_fire: function(type, e, base_event) {
//
    // }


});



/**
 * @mixin observable
 *
 */
Ergo.alias('mixins:observable', /** @lends observable */ {

    /**
     * Обработчик событий
     * @returns {Ergo.core.Observer}
     */
    get events() {
        if(!this.__evt) {
            this.__evt = new Ergo.core.Observer(this);
        }
        return this.__evt;
    },


    /**
     * Подписка на события, указанные в опции `events`
     *
     * Подписка может быть выполнена на события полей объекта
     *
     * @param {string} [targetProperty] имя целевого поля
     *
     */
    _bindEvents: function(targetProperty) {

        var o = this.options;

        var target = targetProperty ? this[targetProperty] : this;

        if(!target)
            throw new Error('Target property "'+targetProperty+'" not found');

        if('events' in o) {

            for(var i in o.events){

                var name_a = i.split(':');

                var eventName = i;

                if(name_a[0] == targetProperty && name_a.length > 1) {
                    eventName = name_a[1];
                }
                else if(name_a.length > 1 || targetProperty) {
                    // инициализируем свойство
                    this[targetProperty || name_a[0]];
                    continue;
                }


                var callback_a = o.events[i];
                callback_a = Array.isArray(callback_a) ? callback_a : [callback_a]; //FIXME
                for(var j in callback_a) {
                    var callback = callback_a[j];

                    if( typeof callback == 'string' ) {
                        var a = callback.split(':');
                        // action
                        var action = $ergo.alias('actions:'+a[0]) || this[a[0]];
                        if( action ==  null ) {
                            //TODO missed action
                            callback = this._missedAction.bind(this, callback);
                        }
                        else {
                            callback = action.bind(this, a[1]);
                        }
//                        callback = (a.length == 1) ? this[callback].bind(this, null) : this[a[0]]/*.rcurry(a[1])*/.bind(this, a[1]);
                    }

                    target.events.on(eventName, callback, this);
                }
            }
        }


        if( target == this ) {

            var regexp = /^on\S/;
            for(var i in o){
                if( i[0] == 'o' && i[1] == 'n' && regexp.test(i)){
                    var name = i.charAt(2).toLowerCase() + i.slice(3);
                    var chain = ( !Array.isArray(o[i]) ) ? [o[i]] : o[i];
                    for(var j = 0; j < chain.length; j++) {
                        var callback = chain[j];
                        if( typeof callback == 'string' ) {
                            var a = callback.split(':');
//                            callback = (a.length == 1) ? this[callback].bind(this, null) : this[a[0]]/*.rcurry(a[1])*/.bind(this, a[1]);
                            // action
                            var action = $ergo.alias('actions:'+a[0]) || this[a[0]];
                            if( action ==  null ) {
                                //TODO missed action
                                callback = this._missedAction.bind(this, callback);
                            }
                            else {
                                callback = action.bind(this, a[1]);
                            }

                        }
                        this.events.on( name, callback );
                    }
                }
            }


        }

    },

    /**
     * Подписка на событие
     * @see {@link Ergo.core.Observer#on}
     */
    on: function() {
        this.events.on.apply(this.events, arguments);
    },

    /**
     * Единовременная подписка на событие
     * @see {@link Ergo.core.Observer#once}
     */
    once: function() {
        this.events.once.apply(this.events, arguments);
    },

    /**
     * Отключение подписки на событие
     * @see {@link Ergo.core.Observer#off}
     */
    off: function() {
        this.events.off.apply(this.events, arguments);
    },

    /**
     * Генерация события
     * @deprecated
     */
    fire: function() {
        this.events.fire.apply(this.events, arguments);
    },

    /**
     * Генерация события
     *
     * @see {@link Ergo.core.Observer#fire}
     */
    emit: function() {
        this.events.fire.apply(this.events, arguments);
    }

    // off: function(type, callback) {
    //     var name_a = type.split(':');
    //     var obj = this;
    //     if(name_a.length > 1) {
    //         obj = this[name_a[0]];
    //         type = name_a[1];
    //     }
    //     obj.events.on(type, callback, this);
    // }



});






/**
 *
 *
 */
Ergo.alias('includes:observable', {

    _construct: function(o) {

        this.events = new Ergo.core.Observer(this);

    },




    _postConstruct: function(o) {


        if('events' in o){
            for(var i in o.events){

                var name_a = i.split(':');

                // вложенные события игнорируем
                if( name_a.length > 1 ) continue;

                var callback_a = o.events[i];
                callback_a = Array.isArray(callback_a) ? callback_a : [callback_a]; //FIXME
                for(var j in callback_a) {
                    var callback = callback_a[j];

                    if( $.isString(callback) ) {
                        var a = callback.split(':');
                        callback = (a.length == 1) ? this[callback] : this[a[0]].rcurry(a[1]).bind(this);
                    }

                    // if( name_a.length == 2 && this[name_a[0]] && this[name_a[0]].events ) {
                    //     console.log(i);
                    //     this[name_a[0]].events.on( name_a[1], callback, this );
                    // }
                    // else {
                    this.events.on(i, callback, this);
                    // }

                    // if(i.indexOf('ctx:') == 0) {
                    //     // Context
                    //     (this._context || Ergo.context).events.on(i.substr(4), callback, this);
                    // }
                    // else if(i.indexOf('jquery:') == 0) {
                    //     // jQuery
                    //     self.el.on(i.substr(7), callback.rcurry('jquery').bind(this));
                    // }
                    // else {
                    //     // Widget
                    //     this.events.on(i, callback, this);
                    // }
                }
            }
        }


        var regexp = /^on\S/;
        for(var i in o){
            if( i[0] == 'o' && i[1] == 'n' && regexp.test(i)){
                var name = i.charAt(2).toLowerCase() + i.slice(3);
                var chain = ( !Array.isArray(o[i]) ) ? [o[i]] : o[i];
                for(var j = 0; j < chain.length; j++) {
                    var callback = chain[j];
                    if( $.isString(callback) ) {
                        var a = callback.split(':');
                        callback = (a.length == 1) ? this[callback] : this[a[0]].rcurry(a[1]).bind(this);
                    }
                    this.events.on( name, callback );
                }
            }
        }


    }


});