/********************************************************************** * * * * XXX is types/events the right place for this??? * XXX should we have .pre/.post events??? * XXX should we propogate event handling to parent/overloaded events??? * **********************************************/ /* c8 ignore next 2 */ ((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define) (function(require){ var module={} // make module AMD/node compatible... /*********************************************************************/ var object = require('ig-object') /*********************************************************************/ // Event method wrappers... // Create an "eventfull" method... // // The resulting method can be either called directly or via .trigger(..). // Handlrs can be bound to it via .on(..) and unbound via .off(..) and // calling it will trigger the handlers either after the user func(..) // return or when the user calles the passed handler(..) function. // // bareEventMethod(name[, options]) // -> method // // bareEventMethod(name, func[, options]) // -> method // // // method(...args) // -> .. // // // func(handle, ...args) // -> .. // // // NOTE: calling handle(false) will exiplicitly disable calling the // handlers for that call... var bareEventMethod = module.bareEventMethod = function(name, func, options={}){ var hidden var method options = func && typeof(func) != 'function' ? func : options return object.mixinFlat( method = function(...args){ var handlers = // hidden... options.handlerLocation == 'hidden' ? (hidden || []) // function... : options.handlerLocation == 'method' ? (method.__event_handlers__ || []) // context (default)... : ((this.__event_handlers__ || {})[name] || []) // NOTE: this will stop event handling if one of the handlers // explicitly returns false... // NOTE: if the user does not call handle() it will be called // after the event action is done but before it returns... // NOTE: to explicitly disable calling the handlers func must // call handle(false) // XXX should he user be able to control the args passed to // the handlers??? var did_handle = false var handle = function(skip=false){ did_handle = skip === true return skip ? undefined : handlers .reduce(function(res, handler){ return res === true && handler(name, ...args) !== false }, true) } var res = func ? func.call(this, handle, ...args) : undefined // call the handlers if the user either didn't call handle() // or explicitly called handle(false)... !did_handle && handle() return res }, { __event__: 'bare', get __event_handler_location__(){ return ['hidden', 'method'].includes(options.handlerLocation) ? options.handlerLocation : 'context' }, __event_handler_add__: function(context, func){ var handlers = // hidden... options.handlerLocation == 'hidden' ? (hidden = hidden || []) // function... : options.handlerLocation == 'method' ? (method.__event_handlers__ = method.__event_handlers__ || []) // context (default)... : ((context.__event_handlers__ = context.__event_handlers__ || {})[name] = context.__event_handlers__[name] || []) // add handler... handlers.push(func) return this }, // XXX should this support the 'all' key -- remove all handlers??? __event_handler_remove__: function(context, func){ var handlers = (options.handlerLocation == 'hidden' ? hidden : options.handlerLocation == 'method' ? method.__event_handlers__ : (context.__event_handlers__ || {})[name]) || [] handlers.splice(0, handlers.length, ...handlers.filter(function(h){ return h !== func && h.__event_original_handler__ !== func })) return this }, toString: function(){ return func.toString() .replace(/^(function[^(]*\()[^,)]*, ?/, '$1') }, }) } // Extends bareEventMethod(..) adding ability to bind events via the // resulting method directly by passing it a function... // // eventMethod(name[, options]) // -> method // // eventMethod(name, func[, options]) // -> method // // // Bind handler... // method(handler) // -> this // // Unbind handler... // method(handler, false) // -> this // // Trigger handlers... // method(...args) // -> this // // // func(handle, ...args) // // var eventMethod = module.eventMethod = function(name, func, options={}){ var method options = typeof(func) != 'function' ? func : options return Object.assign( method = bareEventMethod(name, function(handle, ...args){ // add handler... // XXX handle handler tags... if(typeof(args[0]) == 'function'){ method.__event_handler_add__(this, args[0]) // call the action... } else { func && func.call(this, handle, ...args) } return this }, options), { __event__: 'full', // NOTE: this is a copy of bareEventMethod's .toString() as we // still need to base the doc on the user's func... toString: function(){ return func.toString() .replace(/^(function[^(]*\()[^,)]*, ?/, '$1') }, }) } //--------------------------------------------------------------------- // Mixins... // XXX might be nice to add support to pre/post handlers... // XXX still not sure about the builtin-local event control flow... var EventHandlerMixin = module.EventHandlerMixin = { __event_handlers__: null, // XXX do we need to be able to force global handler??? on: function(evt, func){ // event... if(evt in this && this[evt].__event_handler_add__){ this[evt].__event_handler_add__(this, func) // non-event... } else { ;((this.__event_handlers__ = this.__event_handlers__ || {})[evt] = this.__event_handlers__[evt] || []) .push(func) } return this }, one: function(evt, func){ var handler this.on(evt, handler = Object.assing( function(handle, ...args){ this.off(evt, handler) return func.call(this, handle, ...args) }, {__event_original_handler__: func})) return this }, // XXX do we need .off(evt, 'all') off: function(evt, func){ // event... if(evt in this && this[evt].__event_handler_remove__){ this[evt].__event_handler_remove__(this, func) // non-event... } else { var handlers = this.__event_handlers__ && (this.__event_handlers__[evt] || []) handlers && handlers.splice(0, handlers.length, ...handlers.filter(function(h){ return h !== func && h.__event_original_handler__ !== func })) } return this }, // XXX add support for stopping handler execution... trigger: function(evt, ...args){ // local handler... evt in this && this[evt](...args) // global events... this.__event_handlers__ && (this.__event_handlers__[evt] || []) .forEach(function(h){ h(evt, ...args) }) return this }, } // NOTE: this can't be added via Object.assign(..), use object.mixinFlat(..) // instead... var EventDocMixin = module.EventDocMixin = { get eventfull(){ return object.deepKeys(this) .filter(function(n){ // avoid triggering props... return !object.values(this, n, function(){ return object.STOP }, true)[0].get && (this[n] || {}).__event__ == 'bare'}.bind(this)) }, get events(){ return object.deepKeys(this) .filter(function(n){ // avoid triggering props... return !object.values(this, n, function(){ return object.STOP }, true)[0].get && (this[n] || {}).__event__ == 'full' }.bind(this)) }, } var EventMixin = module.EventMixin = object.mixinFlat( EventHandlerMixin, EventDocMixin) /********************************************************************** * vim:set ts=4 sw=4 : */ return module })