diff --git a/.gitignore b/.gitignore index ab05030..75981ef 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,77 @@ node_modules -*.log \ No newline at end of file +*.log + +### SublimeText template +# cache files for sublime text +*.tmlanguage.cache +*.tmPreferences.cache +*.stTheme.cache + +# workspace files are user-specific +*.sublime-workspace + +# project files should be checked into the repository, unless a significant +# proportion of contributors will probably not be using SublimeText +# *.sublime-project + +# sftp configuration file +sftp-config.json + +# Package control specific files +Package Control.last-run +Package Control.ca-list +Package Control.ca-bundle +Package Control.system-ca-bundle +Package Control.cache/ +Package Control.ca-certs/ +bh_unicode_properties.cache + +# Sublime-github package stores a github token in this file +# https://packagecontrol.io/packages/sublime-github +GitHub.sublime-settings +### Windows template +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +.idea/ + + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + diff --git a/README.md b/README.md index 7d7eb57..183c8d5 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ### In a browser -Download [eventbus.min.js](https://raw.githubusercontent.com/krasimir/EventBus/master/lib/eventbus.min.js) and add it to your page. +Download [eventbus.min.js](https://raw.githubusercontent.com/bonashen/EventBus/master/lib/eventbus.min.js) and add it to your page. ### In Node @@ -27,6 +27,17 @@ var EventBus = require('eventbusjs'); // @callback - function // @scope - the scope where the @callback is defined EventBus.addEventListener(type, callback, scope) +//or +EventBus.on(type,callback,scope) +//or +EventBus.bind(type,callback,scope) +//or +EventBus.once(type,callback,scope) +//or +//support regex and multi event types +EventBus.on(['click','change',/\w_click/],function(event,value) { + +}); ``` ### `removeEventListener` @@ -36,6 +47,10 @@ EventBus.addEventListener(type, callback, scope) // @callback - function // @scope - the scope where the @callback is defined EventBus.removeEventListener(type, callback, scope) +//or +EventBus.off(type,callback,scope) +//or +EventBus.unbind(type,callback,scope) ``` ### `hasEventListener` @@ -45,6 +60,8 @@ EventBus.removeEventListener(type, callback, scope) // @callback - function // @scope - the scope where the @callback is defined EventBus.hasEventListener(type, callback, scope) +//or +EventBus.has(type,calback,scope) ``` ### `dispatch` @@ -54,24 +71,67 @@ EventBus.hasEventListener(type, callback, scope) // @target - the caller // @args - pass as many arguments as you want EventBus.dispatch(type, target, args ...) +//or +EventBus.trigger(type,target,args...) +//or +EventBus.emit(type,target,args...) ``` -### `getEvents` +### redirect +```javascript +//@origin is event type or event type array +//@endpoint is target event type or event type array +//@condition redirect condition check +//@processor reset trigger endpoint event arguments +EventBus.redirect(origin,endpoint); -For debugging purpose, it prints out the added listeners. +EventBus.redirect(origin,endpoint,condition); -```js -EventBus.getEvents() +EventBus.redirect(origin,endpoint,condition,processor); + +``` +### flow +```javascript +EventBus.flow({from:'click',to:'onClick'}); +EventBus.flow({from:'click',to:'onClick'},{from:'onClick',to:'labelClick'}); +EventBus.flow({from:'click',to:'onClick'},{from:'onClick',to:'labelClick',processor:function(event) { + event.setEmitArgs(EventBus.slice(arguments,1)); +}}); +EventBus.flow({from:'click',to:'onClick'},{from:'onClick',to:'labelClick',where:function(event) { + return event.getLevel()>0;//only receive redirect +}}); ``` + ## Usage ```js function myFunction(event) { console.log("myFunction type=" + event.type); + //can stop event + event.stop(); + //can obtain listener arguments + console.log(event.args); + + //can access scope by this + console.log(this); + } -EventBus.addEventListener("my_function_event", myFunction); +var scope ={ + +}; +EventBus.addEventListener("my_function_event", myFunction,scope,1,2,3...); +//or +EventBus.on("my_function_event",myFunction,scope,1,2,3...); + +// dispatch event EventBus.dispatch("my_function_event"); +//or +EventBus.trigger("my_function_event"); +//or +EventBus.emit("my_function_event"); +//or +EventBus("my_function_event"); ``` ## Keeping the scope @@ -141,3 +201,126 @@ var handler = function() { EventBus.addEventListener('EXAMPLE_EVENT', handler); EventBus.removeEventListener('EXAMPLE_EVENT', handler); ``` +## Example of usage EventBus.redirect +```javascript +var TestClass1 = function() { + this.className = "TestClass1"; + this.doSomething = function(event, param1, param2) { + console.log(this.className + ".doSomething"); + console.log("type=" + event.type); + console.log("params=" + param1 + param2); + console.log("coming from=" + event.target.className); + } +}; + +var TestClass2 = function() { + this.className = "TestClass2"; + this.ready = function() { + EventBus.dispatch("custom_event", this, "javascript events", " are really useful"); + } +}; + +var t1 = new TestClass1(); +var t2 = new TestClass2(); + +EventBus.redirect("custom_event","ready"); + +EventBus.on("ready",t1.doSomething,t1); + +t2.ready(); + +``` +## Example of usage EventBus.flow +**flow** method like **redirect**,this method objective for quick and intuitive configuration event flow. +```javascript + +var printEventStack = function (event) { + var p = event; + console.log("==>current stage:",event.type," event id:",event.id); + console.log("event flow :",event.flow.getEventIdsPath()); +}; + +EventBus.on("start",function(event) { + console.log("The game is start..."); + }) + .on("chase",function(event) { + console.log("overtaken"); + printEventStack(event);//event flow : ready#1012==>start#1013==>chase#1015 + EventBus.emit("overtaken"); + }) + .flow( + {from:"ready",to:"start"}, + {from:"start",to:"take-flight"},{from:"start",to:"chase"}, + {from:"overtaken",to:"end"} ) + .on("end",function(event) { + printEventStack(event);//event flow : ready#1012==>start#1013==>chase#1015==>overtaken#1016==>end#1017 + console.log("The game is end."); + }); + +EventBus.emit("ready"); + +``` + +## Example of Error event handling +focus all error event handling. +```javascript +EventBus.on(EventBus.DEFAULT.ERROR_EVENT_TYPE,function(event,error,listener,triggerArguments) { + console.log(error); +}); + +EventBus.on("click",function(event) { + throw "We are not ready!"; +}); + +EventBus.emit("click"); +``` + +## Example of usage event object +For debugging purpose, print event objects tree. +```javascript +EventBus.redirect("click","onClick"); +EventBus.on("onClick",function(event) { + var e = event; + while(e){ + console.log(e.getLevel()," event id:",e.id," event object:",e); + e.stop(); // stop event dispatch. + e = e.flow.getClosestEvent();//get prior event. + } +}); + +EventBus.emit("click"); +``` + +## Example of usage EventBus.plugin +EventBus support define plugin. +```javascript +EventBus.plugin(function(eBus) { + eBus.fn.newFeature = function() { + return this; + }; + + //define static method + eBus.newStaticMethod = function() { + + }; + +}); +``` + +## Example of usage EventBus aspect +EventBus support before after and around aspect. The aspect code from dojo/aspect module. +```javascript +EventBus.plugin(function(eBus){ + //advice EventBus emit + eBus.before("emit",function(event){ + if(event=="ready") + return eBus.slice(arguments).concat([{name:"new object"}]);//append new object to emit + }); +}); +//aspect utils example +var scope = {name:"bona",sayHello:function(message){console.log(this.name,message)}}; +var handler = EventBus.aspect.before(scope,"sayHello",function(message) { + return [[",",message,"!"].join("")]; +}); +scope.sayHello("hello world"); +``` \ No newline at end of file diff --git a/example/browser/example.html b/example/browser/example.html index 44aae0b..4cbc546 100644 --- a/example/browser/example.html +++ b/example/browser/example.html @@ -5,10 +5,10 @@ - +
- +
\ No newline at end of file diff --git a/example/node/aspect-test.js b/example/node/aspect-test.js new file mode 100644 index 0000000..1f4d5e8 --- /dev/null +++ b/example/node/aspect-test.js @@ -0,0 +1,31 @@ +/** + * Created by bona on 2016/12/28. + */ + +var ebus = require('../../lib/eventbus.min'); + +ebus.before("emit", function (event) { + console.log("before-emit,event type:", event); + console.log("closest event path: ", this.getCurrentEvent() ? this.getCurrentEvent().flow.getEventIdsPath() : undefined); + if(event=="ready")return [event,{name:"new object"}]; +}); + +ebus.after("emit",function (event) { + console.log("after-emit,event type:",event); +},true); + +ebus.on("ready",function (event,o) { + console.log("ready",o); +}); + +ebus.redirect("ready","end"); + +ebus.on("end",function (event,o) { + console.log("end event,",event,o); +}); + +ebus.beforeTriggerListener(/end/,function (listener,event,o) { + console.log("beforeTriggerListener,",event,o); +}); + +ebus("ready"); \ No newline at end of file diff --git a/example/node/cs-test.js b/example/node/cs-test.js new file mode 100644 index 0000000..a02bcab --- /dev/null +++ b/example/node/cs-test.js @@ -0,0 +1,153 @@ +var EventBus = require('../../lib/eventbus.min'); + +EventBus.DEFAULT.SHOW_LOG = false; + +var Order = function (id, name, consumer) { + this.id = id; + this.name = name; + this.consumer = consumer; +}; + +var Producer = function (order) { + this.order = order; +}; + +Producer.prototype = { + + make: function () { + console.log("============================", "start make ", this.order.id, "=============================="); + EventBus.emit("make/" + this.order.consumer, this.order); + console.log("============================", "end make ", this.order.id, "=============================="); + }, + package: function () { + console.log("============================", "start package ", this.order.id, "=============================="); + EventBus.emit("package/" + this.order.consumer, this.order); + console.log("============================", "end package ", this.order.id, "=============================="); + }, + send: function () { + console.log("============================", "start send ", this.order.id, "=============================="); + EventBus.emit("send/" + this.order.consumer, this.order); + console.log("============================", "end send ", this.order.id, "=============================="); + } +}; + +var Consumer = function (name, once) { + this.name = name; + this.init(once); +}; + +Consumer.prototype = { + onMake: function (event, order) { + console.log(this.name, "'s order is making. order id=", order.id, " order name:" + order.name); + }, + onPackage: function (event, order) { + console.log(this.name, "'s order is packaging. order id=", order.id, " order name:" + order.name); + }, + onReceive: function (event, order) { + /*event.flow.getAllEvents().forEach(function (v, i) { + console.log(v); + });*/ + var p = event; + while (p) {//print event flow + console.log("event level ", p.getLevel(), " level object:", p); + p = p.flow.getClosestEvent(); + } + // console.log(this.name, "is ready to receive order goods. order id=", order.id, " order name:" + order.name); + throw "Not enough space for storage of goods in order " + order.id + "!"; + }, + + init: function (once) { + var method = "on"; + if (once) method = 'once'; + EventBus[method]("make/" + this.name, this.onMake, this); + EventBus[method]("package/" + this.name, this.onPackage, this); + EventBus[method]("receive/" + this.name, this.onReceive, this); + } + +}; + +EventBus.on(/QC\/\w*/, function (event, order) { + console.log(order.id, " quality testing"); +}); + +EventBus.on(/\w*\/\w*/, monitor, {name: "kerry"}, /make\/\w*/); + +EventBus.on(/\w*\/\w*/, monitor, {name: "peter"}, /package\/\w*/); + +EventBus.on("make package receive", function (event, order) { + console.log(order.id, /*" ",order.name,*/" state is ", event.type); +}); + +function monitor(event, order) { + var state = event.getArg(3); + if (state.test(event.type) && order.consumer == this.name) { + console.log(event.type, " is dispatched! "); + } +} + +EventBus.redirect(/(\w*)\/(\w*)/, function (event) { + return /(\w*)\/(\w*)/.exec(event.type)[1]; +}); + +EventBus.redirect(/trans\/(\w*)/, function (event) {//map one to one + return "receive/" + /(\w*)\/(\w*)/.exec(event.type)[2]; +}); + +EventBus.redirect(/send\/(\w*)/, function (event) { //map one to more + var consumer = /(\w*)\/(\w*)/.exec(event.type)[2]; + return ["print/" + consumer, "QC/" + consumer]; +}); + +//map more to one +EventBus.redirect( + [/QC\/\w*/],//origin event type + "qc-report",//endpoint + undefined,//condition + function (event, order) { //processor + console.log("redirect processor for ", event.id, "==>", + "origin:", event.getOriginType(), " endpoint:", event.getEndpoint()); + if (order.consumer == "bona") event.setEmitArgs([order, "passed"]); + } +); + +EventBus.redirect( + "qc-report", function (event, order) { + return "trans/" + order.consumer; + }, function (event, order, state) { + return state === "passed"; + } +); + +var qcReport = { + orders: [], + passed: [], + print: function () { + console.log("qc checked order count :", this.orders.length); + console.log("qc passed order count :", this.passed.length); + } +}; + +EventBus.on("qc-report", function (event, order, checkState) { + this.orders.push(order); + if (checkState == "passed") this.passed.push(order); +}, qcReport); + + +//exception handling +EventBus.on(EventBus.DEFAULT.ERROR_EVENT_TYPE, function (event, exception, listener, listenerArgs) { + console.log("found exception:", exception, ", listener is ", listener, " ,args:", listenerArgs); +}); + +new Consumer("peter"); +new Consumer("kerry"); +new Consumer("bona", true); + +for (var i = 0; i < 4; i++) { + var order = new Order("order-" + (i + 1), "wa-li robot", ["peter", "kerry", "bona", "bona"][i]); + var maker = new Producer(order); + maker.make(); + maker.package(); + maker.send(); +} + +qcReport.print(); \ No newline at end of file diff --git a/example/node/flow-test.js b/example/node/flow-test.js new file mode 100644 index 0000000..5b672f8 --- /dev/null +++ b/example/node/flow-test.js @@ -0,0 +1,30 @@ +/** + * Created by bona on 2016/12/20. + */ + +var ebus = require('../../lib/eventbus.min'); + +var printEventStack = function (event) { + var p = event; + console.log("==>current stage:", event.type, " event id:", event.id); + console.log("event flow :", event.flow.getEventIdsPath()); +}; + +ebus.on("start", function (event) { + console.log("The game is start..."); +}) + .on("chase", function (event) { + console.log("overtaken"); + printEventStack(event); + ebus("overtaken"); + }) + .flow( + {from: "ready", to: "start"}, + {from: "start", to: "take-flight"}, {from: "start", to: "chase"}, + {from: "overtaken", to: "end"}) + .on("end", function (event) { + console.log("The game is end."); + printEventStack(event); + }); + +ebus("ready"); \ No newline at end of file diff --git a/lib/eventbus.min.js b/lib/eventbus.min.js index 002ea87..a50c22e 100644 --- a/lib/eventbus.min.js +++ b/lib/eventbus.min.js @@ -1 +1,3 @@ -(function(root,factory){if(typeof exports==="object"&&typeof module==="object")module.exports=factory();else if(typeof define==="function"&&define.amd)define("EventBus",[],factory);else if(typeof exports==="object")exports["EventBus"]=factory();else root["EventBus"]=factory()})(this,function(){var EventBusClass={};EventBusClass=function(){this.listeners={}};EventBusClass.prototype={addEventListener:function(type,callback,scope){var args=[];var numOfArgs=arguments.length;for(var i=0;i3?args.splice(3,args.length-1):[];if(typeof this.listeners[type]!="undefined"){this.listeners[type].push({scope:scope,callback:callback,args:args})}else{this.listeners[type]=[{scope:scope,callback:callback,args:args}]}},removeEventListener:function(type,callback,scope){if(typeof this.listeners[type]!="undefined"){var numOfCallbacks=this.listeners[type].length;var newArray=[];for(var i=0;i0}for(var i=0;i2?args.splice(2,args.length-1):[];args=[event].concat(args);if(typeof this.listeners[type]!="undefined"){var numOfCallbacks=this.listeners[type].length;for(var i=0;in.length){return[]}return[].slice.call(n,m===e?0:m,l===e?n.length:l)||[]}function c(p,m,o,l){if(typeof o=="string"||i(o)){var n=o;if(typeof o=="string"){n=o.trim().split(j.DEFAULT.EVENT_TYPE_SPLIT_EXP)}if(n.length>1){g(n,function(q,r){l[0]=r;m.apply(p,l)});return p}else{o=n[0]}}return o}var b=(function(){var l=1000;return function(){return ++l}})();var j={};j=function(){this.listeners={};this.regexListeners=[]};j.prototype={on:function(o,r,n){o=c(this,this.on,o,k(arguments));if(o==this){return this}var q=o instanceof RegExp;var m=q?o.toString():o;var l=k(arguments);if(j.DEFAULT.SHOW_LOG){console.log("on=>listener args is ",l)}var p={id:b(),scope:n||{},callback:r,args:l,regExp:o,eventType:m};if(!q){if(typeof this.listeners[m]!="undefined"){this.listeners[m].push(p)}else{this.listeners[m]=[p]}}else{this.regexListeners.push(p)}return this},off:function(p,q,t){p=c(this,this.off,p,[e,q,t]);if(p==this){return this}var n=p instanceof RegExp;var m=n?p.toString():p;var r=[];if(typeof q=="object"){t=q;q=e}var o=q==e;var s=t==e;if(j.DEFAULT.SHOW_LOG){console.log("off=>off event type:",p," all callback:",o," all scope:",s)}function l(u){return(o?true:u.callback==q)&&(s?true:u.scope==t)}if(!n){if(typeof this.listeners[m]!="undefined"){if(!(o&&s)){g(this.listeners[m],function(u,v){if(!l(v)){r.push(v)}else{}})}this.listeners[m]=r}}else{g(this.regexListeners,function(u,v){if(!(v.eventType===m&&l(v))){r.push(v)}else{}});this.regexListeners=r}return this},has:function(p,s,o){var r=p instanceof RegExp;var m=r?p.toString():p;var n=[].concat(this.listeners[m]);if(typeof s=="object"){o=s;s=e}if(n.length>0&&!r){var q=n.length;if(s===e&&o===e){return q>0}var l=g(n,function(t,v,w,u){if((o?v.scope==o:true)&&(s?v.callback==s:true)){u.stop(true)}});if(l){return true}}else{if(r){n=[].concat(this.regexListeners);var l=g(n,function(t,v,w,u){if(v.regExp.toString()==m&&(o?v.scope==o:true)&&(s?v.callback==s:true)){u.stop(true)}});if(l){return true}}}return false},emit:function(q){var m=this.eventFlow=this.eventFlow||[];var n=k(arguments);q=c(this,this.emit,q,n);if(q==this){return this}var r={id:q+"#"+b(),type:q,target:n.length>1?n[1]:{}};n=[r].concat(k(arguments,1));var p=[].concat(typeof this.listeners[q]=="undefined"?[]:this.listeners[q]);var l=this;function s(v){var t=[].concat(m);var u=false;r.stop=function(){u=true};r.isStopped=function(){return u};r.getArg=function(w){if(!i(r.args)||r.args.length-1")}};r.getLevel=function(){return t.length-1};g(v,function(x,C,A,z){if(C&&C.callback){var B=[].concat(n);if(j.DEFAULT.SHOW_LOG){console.log("emit=>event listener call arguments:",B)}r.args=[].concat(C.args);if(j.DEFAULT.SHOW_LOG){console.log("emit=>fire event listener:",C)}try{if(j.DEFAULT.SHOW_LOG){console.log("event flow:",r.flow.getEventIdsPath())}var w=l.beforeTriggerListener;if(d(w)){w.apply(l,[C].concat(B))}C.callback.apply(C.scope,B);w=l.afterTriggerListener;if(d(w)){w.apply(l,[C].concat(B))}}catch(y){u=true;l.emit(j.DEFAULT.ERROR_EVENT_TYPE,y,C,B)}if(u){z.stop()}}})}var o=[].concat(this.regexListeners);g(o,function(t,u){if(u.regExp.test(q)){p.push(u)}});m.push(r);s(p);m.pop();return this}};var f=new j();var h=function(l){return f.emit.apply(f,k(arguments))};h.fn=j.prototype;h.DEFAULT=j.DEFAULT={SHOW_LOG:false,EVENT_TYPE_SPLIT_EXP:/,|\s/,ERROR_EVENT_TYPE:"EMIT_ERROR",EXCLUDE_NAMES:["aspect","before","after","around","isFunction","isArray","hit","slice","iterator"]};h.plugin=function(l){l(h);g(j.prototype,function(m,n){if(n&& +/*!EventBus["hasOwnProperty"](key) &&*/ +(!{}["hasOwnProperty"](m))&&d(n)&&j.DEFAULT.EXCLUDE_NAMES.indexOf(m)<0){h[m]=a(f,n)}})};h.plugin(function(l){l.fn.bind=l.fn.addEventListener=l.fn.on;l.fn.unbind=l.fn.removeEventListener=l.fn.off;l.fn.trigger=l.fn.dispatch=l.fn.emit;l.fn.hasEventListener=l.fn.has});h.plugin(function(l){l.fn.getCurrentEvent=function(){return[].concat(this.eventFlow).pop()};l.fn.getEarlyEvent=function(){return[].concat(this.eventFlow).shift()};l.fn.getClosestEvent=function(){return this.getCurrentEvent()?this.getCurrentEvent().flow.getClosestEvent():e}});h.plugin(function(l){l.fn.once=function(q,r,p){var m=this;var n=function(s){r.apply(this,k(arguments));m.off(q,n,p)};var o=k(arguments);o[1]=n;return m.on.apply(m,o)}});h.plugin(function(l){l.fn.flow=function(n){var o=k(arguments);if(i(n)){o=n}var m=this;g(o,function(p,q){if(q instanceof Object&&typeof q=="object"&&q.from!=e&&q.to!=e){m.redirect(q.from,q.to,q.where,q.processor)}});return m}});h.plugin(function(l){l.fn.redirect=function(n,q,r,p){var m=this;if(n==e||q==e||n==q){return m}var o=this.redirectScope=this.redirectScope||{};p=d(p)?p:function(s){s.setEmitArgs(h.slice(arguments,1))};this.on(n,(function(v,w,t){if(w==e){w=function(){return true}}if(w instanceof RegExp){var u=w;w=function(x){return u.test(x.type)}}if(d(w)){var s=[];return function(z){var x=k(arguments);if(w.apply(o,x)){var y=d(v)?v.apply(o,x):v;if(s.indexOf(t)<0&&y!=z.type&&y!=e){s.push(t);try{if(typeof y=="string"){y=y.trim().split(j.DEFAULT.EVENT_TYPE_SPLIT_EXP)}if(!(i(y))){y=[y]}g(y,function(A,D,E,C){if(D!=z.type){var B=k([].concat(x),1);z.setEmitArgs=function(F){B=F};z.getEmitArgs=function(){return[].concat(B)};z.getOriginType=function(){return n};z.getEndpoint=function(){return D};z.isRedirect=function(){return true};z.endpoints=z.endpoints||[];z.endpoints.push(D);p.apply(o,x);B=(i(B))?[""].concat(B):[].concat(x);B[0]=D;if(j.DEFAULT.SHOW_LOG){console.log("redirect=>redirect id:",z.id," origin:",n," ==> endpoint:",D)}m.emit.apply(m,B);if(z.isStopped()){C.stop(true)}}})}finally{s.pop()}}else{throw"redirect origin:"+n.toString()+" => endpoint:"+v.toString()+" is looping!"}}}}else{return function(x){if(j.DEFAULT.SHOW_LOG){console.log("redirect=>redirect condition must set function or RegExp!")}}}})(q,r,b()),o);return m}});h.plugin(function(n){function m(v,u,s,t){var x=v[u];var w=u=="around";var y;if(w){var r=s(function(){return x.advice(this,arguments)});y={remove:function(){if(r){r=v=s=null}},advice:function(A,z){return r?r.apply(A,z):x.advice(A,z)}}}else{y={remove:function(){if(y.advice){var A=y.previous;var z=y.next;if(!z&&!A){delete v[u]}else{if(A){A.next=z}else{v[u]=z}if(z){z.previous=A}}v=s=y.advice=null}},id:v.nextId++,advice:s,receiveArguments:t}}if(x&&!w){if(u=="after"){while(x.next&&(x=x.next)){}x.next=y;y.previous=x}else{if(u=="before"){v[u]=y;y.next=x;x.previous=y}}}else{v[u]=y}return y}function l(r){return function(y,s,t,v){var x=y[s],w;if(!x||x.target!=y){y[s]=w=function(){var B=w.nextId;var z=arguments;var C=w.before;while(C){if(C.advice){z=C.advice.apply(this,z)||z}C=C.next}if(w.around){var A=w.around.advice(this,z)}var D=w.after;while(D&&D.id 3 ? args.splice(3, args.length-1) : []; - if(typeof this.listeners[type] != "undefined") { - this.listeners[type].push({scope:scope, callback:callback, args:args}); - } else { - this.listeners[type] = [{scope:scope, callback:callback, args:args}]; - } - }, - removeEventListener:function(type, callback, scope) { - if(typeof this.listeners[type] != "undefined") { - var numOfCallbacks = this.listeners[type].length; - var newArray = []; - for(var i=0; i 0; - } - for(var i=0; i 2 ? args.splice(2, args.length-1) : []; - args = [event].concat(args); - if(typeof this.listeners[type] != "undefined") { - var numOfCallbacks = this.listeners[type].length; - for(var i=0; i array.length)return []; + return [].slice.call(array, start === undefined ? 0 : start, end === undefined ? array.length : end) || []; + } + + function processMultiTypes(busObject, busMethod, type, args) { + if (typeof type == "string" || isArray(type)) { + var types = type; + if (typeof type == "string") types = type.trim().split(EventBusClass.DEFAULT.EVENT_TYPE_SPLIT_EXP); + if (types.length > 1) { + iterator(types, function (index, type) { + args[0] = type; + busMethod.apply(busObject, args); + }); + return busObject; + } else { + type = types[0]; + } + } + return type; + } + + /** + * id generator + */ + var nextId = (function () { + var _id = 1000; + return function () { + return ++_id; + } + })(); + + var EventBusClass = {}; + EventBusClass = function () { + this.listeners = {}; + this.regexListeners = []; + }; + EventBusClass.prototype = { + /** + * EventBus.on("click",callback); + * EventBus.on("click",callback,buttonScope); + * @param type + * @param callback + * @param scope + * @returns {EventBusClass} + */ + on: function (type, callback, scope) { + type = processMultiTypes(this, this.on, type, slice(arguments)); + if (type == this)return this; + + var isRegExpType = type instanceof RegExp; + var eventType = isRegExpType ? type.toString() : type; + var args = slice(arguments); + if (EventBusClass.DEFAULT.SHOW_LOG) + console.log("on=>listener args is ", args); + var listener = {//create listener stub + id: nextId(), + scope: scope || {}, + callback: callback, + args: args, + regExp: type, + eventType: eventType + }; + if (!isRegExpType) { + if (typeof this.listeners[eventType] != "undefined") { + this.listeners[eventType].push(listener); + } else { + this.listeners[eventType] = [listener]; + } + } else + this.regexListeners.push(listener); + return this; + }, + /** + * EventBus.off("click"); //remove all click listeners + * EventBus.off("click",onClick); + * EventBus.off("click",onClick,buttonScope); + * EventBus.off("click",buttonScope); + * EventBus.off("click").off("mousemove"); + * @param type + * @param callback + * @param scope + * @returns {EventBusClass} + */ + off: function (type, callback, scope) { + //support EventBus.off(['click',/click/]); + type = processMultiTypes(this, this.off, type, [undefined, callback, scope]); + if (type == this)return this; + + var isRegExpType = type instanceof RegExp; + var eventType = isRegExpType ? type.toString() : type; + var newArray = []; + if (typeof callback == "object") { + scope = callback; + callback = undefined; + } + var allCallback = callback == undefined; + var allScope = scope == undefined; + if (EventBusClass.DEFAULT.SHOW_LOG) + console.log("off=>off event type:", type, " all callback:", allCallback, " all scope:", allScope); + // console.log("callback:", callback, " scope:", scope); + function isRemove(listener) { + return (allCallback ? true : listener.callback == callback) && + (allScope ? true : listener.scope == scope) + } + + if (!isRegExpType) { + if (typeof this.listeners[eventType] != "undefined") { + if (!(allCallback && allScope)) + iterator(this.listeners[eventType], function (i, listener) { + if (!isRemove(listener)) newArray.push(listener); + else { + // console.log("remove event listener:", type, listener); + } + }); + this.listeners[eventType] = newArray; + } + } else { + iterator(this.regexListeners, function (index, listener) { + if (!(listener.eventType === eventType && isRemove(listener))) newArray.push(listener); + else { + // console.log("remove listener:", type, listener); + } + }); + this.regexListeners = newArray; + } + return this; + }, + /** + * EventBus.has("click") + * EventBus.has("click",buttonScope); + * EventBus.has("click",callback); + * EventBus.has("click",callback,buttonScope); + * @param type + * @param callback + * @param scope + * @returns {boolean} + */ + has: function (type, callback, scope) { + var isRegExpType = type instanceof RegExp; + var eventType = isRegExpType ? type.toString() : type; + var listeners = [].concat(this.listeners[eventType]); + if (typeof callback == "object") { + scope = callback; + callback = undefined; + } + if (listeners.length > 0 && !isRegExpType) { + + var numOfCallbacks = listeners.length; + if (callback === undefined && scope === undefined) { + return numOfCallbacks > 0; + } + + var result = iterator(listeners, function (index, listener, array, iterator) { + if ((scope ? listener.scope == scope : true) + && (callback ? listener.callback == callback : true)) { + iterator.stop(true); + } + }); + + if (result)return true; + } else if (isRegExpType) { + listeners = [].concat(this.regexListeners); + var result = iterator(listeners, function (index, listener, array, iterator) { + if (listener.regExp.toString() == eventType + && (scope ? listener.scope == scope : true) + && (callback ? listener.callback == callback : true)) { + iterator.stop(true); + } + }); + if (result)return true; + } + return false; + }, + /** + * EventBus.emit("click"); + * EventBus.emit("click",argument1,...); + * @param type + * @returns {EventBusClass} + */ + emit: function (type) { + var eventFlow = this.eventFlow = this.eventFlow || [];//event stack. + var args = slice(arguments); + type = processMultiTypes(this, this.emit, type, args); + if (type == this)return this; + var event = { + id: type + "#" + nextId(), + type: type, + target: args.length > 1 ? args[1] : {}//compatibility with older versions + }; + args = [event].concat(slice(arguments, 1)); + var listeners = [].concat(typeof this.listeners[type] == "undefined" ? [] : this.listeners[type]); + var bus = this; + + function dispatchEvent(listeners) { + var stack = [].concat(eventFlow); + var isStop = false; + event.stop = function () { + isStop = true; + }; + event.isStopped = function () { + return isStop; + }; + event.getArg = function (index) { + if (!isArray(event.args) || event.args.length - 1 < index)return undefined; + return event.args[index]; + }; + event.flow = { + getEarlyEvent: function () { + return stack.slice(0, 1).shift(); + }, + getClosestEvent: function () { + return stack.slice(0, stack.length - 1).pop(); + }, + getAllEvents: function () { + return stack.slice(0); + }, + getEventIdsPath: function (separator) { + return stack.map(function (event) { + return event.id; + }).join(separator || "==>"); + } + }; + + event.getLevel = function () { + return stack.length - 1; + }; + + iterator(listeners, function (index, listener, listeners, iterator) { + if (listener && listener.callback) { + var listenerArgs = [].concat(args); + if (EventBusClass.DEFAULT.SHOW_LOG) + console.log("emit=>event listener call arguments:", listenerArgs); + event.args = [].concat(listener.args); + if (EventBusClass.DEFAULT.SHOW_LOG) + console.log("emit=>fire event listener:", listener); + try { + if (EventBusClass.DEFAULT.SHOW_LOG) + console.log("event flow:", event.flow.getEventIdsPath()); + var emitAspect = bus["beforeTriggerListener"]; + if (isFunction(emitAspect)) emitAspect.apply(bus, [listener].concat(listenerArgs)); + listener.callback.apply(listener.scope, listenerArgs); + emitAspect = bus["afterTriggerListener"]; + if (isFunction(emitAspect)) emitAspect.apply(bus, [listener].concat(listenerArgs)); + } catch (exception) { + isStop = true; + bus.emit(EventBusClass.DEFAULT.ERROR_EVENT_TYPE, exception, listener, listenerArgs); + } + if (isStop) iterator.stop(); + } + }); + } + + var regexListeners = [].concat(this.regexListeners); + + iterator(regexListeners, function (i, listener) { + if (listener.regExp.test(type)) { + listeners.push(listener); + } + }); + eventFlow.push(event); + dispatchEvent(listeners); + eventFlow.pop(); + return this; + } + }; + + + var _EventBus_ = new EventBusClass(); + + var EventBus = function (event) { + return _EventBus_.emit.apply(_EventBus_, slice(arguments)); + }; + + //support extend. + EventBus.fn = EventBusClass.prototype; + + //default config + EventBus.DEFAULT = EventBusClass.DEFAULT = { + SHOW_LOG: false, + EVENT_TYPE_SPLIT_EXP: /,|\s/, + ERROR_EVENT_TYPE: "EMIT_ERROR", + EXCLUDE_NAMES: ["aspect", "before", "after", "around", "isFunction", "isArray", "hit", "slice", "iterator"] + }; + + + /** + * define EventBus plugin extend + * examples: + * EventBus.plugin(function(eBus){ + * + * eBus.fn.newExtend = function(){ + * + * }; + * + * eBus.newStaticMethod = function(){ + * + * } + * }); + * @param f + */ + + EventBus.plugin = function (f) { + f(EventBus); + iterator(EventBusClass.prototype, function (key, fn) { + if (fn && /*!EventBus["hasOwnProperty"](key) &&*/(!{}["hasOwnProperty"](key)) && + isFunction(fn) && + EventBusClass.DEFAULT.EXCLUDE_NAMES.indexOf(key) < 0) + EventBus[key] = hit(_EventBus_, fn); + }); + }; + + EventBus.plugin(function (eBus) { + //alis + eBus.fn.bind = eBus.fn.addEventListener = eBus.fn.on; + eBus.fn.unbind = eBus.fn.removeEventListener = eBus.fn.off; + eBus.fn.trigger = eBus.fn.dispatch = eBus.fn.emit; + eBus.fn.hasEventListener = eBus.fn.has; + }); + + EventBus.plugin(function (eBus) { + + eBus.fn.getCurrentEvent = function () { + return [].concat(this.eventFlow).pop(); + }; + + eBus.fn.getEarlyEvent = function () { + return [].concat(this.eventFlow).shift(); + }; + + eBus.fn.getClosestEvent = function () { + return this.getCurrentEvent() ? this.getCurrentEvent().flow.getClosestEvent() : undefined; + } + }); + + EventBus.plugin(function (eBus) { + /** + * EventBus.once("click",callback); + * EventBus.once("click",callback,buttonScope); + * @param type + * @param callback + * @param scope + * @returns {EventBusClass} + */ + eBus.fn.once = function (type, callback, scope) { + var bus = this; + var proxyCallback = function (event) { + callback.apply(this, slice(arguments)); + bus.off(type, proxyCallback, scope); + }; + var args = slice(arguments); + args[1] = proxyCallback; + return bus.on.apply(bus, args); + } + }); + + EventBus.plugin(function (eBus) { + /** + * build event flow + * EventBus.flow({from:'click',to:'onClick'}) + * EventBus.flow({from:'ready',to:'start'},{from:'start',to:'end'}) + * EventBus.flow({from:'click',to:'onClick',where:function(event){event.getLevel()>1}}) + * @param node is event flow node map config,maybe node map array + * @return {EventBusClass} + */ + eBus.fn.flow = function (node) { + var nodeMap = slice(arguments); + if (isArray(node)) nodeMap = node; + var bus = this; + iterator(nodeMap, function (index, node) { + if (node instanceof Object && typeof node == "object" && node['from'] != undefined && node['to'] != undefined) { + bus.redirect(node['from'], node['to'], node['where'], node['processor']); + } + }); + return bus; + }; + }); + + EventBus.plugin(function (eBus) { + /** + * redirect("click","onClick",function(event){return true;}); + * redirect("click","onClick"); + * redirect("click",function(event){return "onClick"},function(event){return true;}); + * redirect(/\w*_click/,"onClick",/btn[0-9]+_click/); + * @param origin + * @param endpoint + * @param condition + * @param processor processor be called before event redirection + * @return {EventBusClass} + */ + eBus.fn.redirect = function (origin, endpoint, condition, processor) { + var bus = this; + if (origin == undefined || endpoint == undefined || origin == endpoint)return bus; + + var scope = this.redirectScope = this.redirectScope || {}; + + processor = isFunction(processor) ? processor : function (event) { + event.setEmitArgs(EventBus.slice(arguments, 1));//example: set emit endpoint event arguments + }; + + this.on(origin, (function (endpoint, condition, nextId) {//Unified exception handling by emit method. + if (condition == undefined) + condition = function () { //default all origin event to endpoint event + return true; + }; + if (condition instanceof RegExp) {//support reg exp as condition + var exp = condition; + condition = function (event) { + return exp.test(event.type); + } + } + if (isFunction(condition)) { + var stack = []; + return function (event) {//trigger redirect + var args = slice(arguments); + if (condition.apply(scope, args)) {//trigger condition check + var eventType = isFunction(endpoint) ? endpoint.apply(scope, args) : endpoint;//dynamic get endpoint + if (stack.indexOf(nextId) < 0 && eventType != event.type && eventType != undefined)//check redirect looping + { + stack.push(nextId); + try { + if (typeof eventType == "string") eventType = + eventType.trim().split(EventBusClass.DEFAULT.EVENT_TYPE_SPLIT_EXP);//support string split by SPACE,COMMA char + if (!(isArray(eventType))) { + eventType = [eventType]; + } + iterator(eventType, function (index, type, array, iterator) {//support endpoint array + if (type != event.type) { + var emitArgs = slice([].concat(args), 1); + event.setEmitArgs = function (args) { + emitArgs = args; + }; + event.getEmitArgs = function () { + return [].concat(emitArgs); + }; + event.getOriginType = function () { + return origin; + }; + event.getEndpoint = function () { + return type; + }; + event.isRedirect = function () { + return true; + }; + event.endpoints = event.endpoints || []; + event.endpoints.push(type); + processor.apply(scope, args); + emitArgs = (isArray(emitArgs)) ? [""].concat(emitArgs) : [].concat(args); + emitArgs[0] = type; + if (EventBusClass.DEFAULT.SHOW_LOG) + console.log("redirect=>redirect id:", event.id, " origin:", origin, " ==> endpoint:", type); + bus.emit.apply(bus, emitArgs);//dispatch endpoint event + if (event.isStopped()) iterator.stop(true); + } + } + ); + } finally { + stack.pop(); + } + } else { + throw "redirect origin:" + origin.toString() + " => endpoint:" + endpoint.toString() + + " is looping!"; + } + } + } + } + else { + return function (event) { + if (EventBusClass.DEFAULT.SHOW_LOG) + console.log("redirect=>redirect condition must set function or RegExp!") + } + } + })(endpoint, condition, nextId()), scope); + return bus; + } + }); + + EventBus.plugin(function (eBus) { + //remember: aspect code from dojo/aspect module + function advise(dispatcher, type, advice, receiveArguments) { + var previous = dispatcher[type]; + var around = type == "around"; + var signal; + if (around) { + var advised = advice(function () { + return previous.advice(this, arguments); + }); + signal = { + remove: function () { + if (advised) { + advised = dispatcher = advice = null; + } + }, + advice: function (target, args) { + return advised ? + advised.apply(target, args) : // called the advised function + previous.advice(target, args); // cancelled, skip to next one + } + }; + } else { + // create the remove handler + signal = { + remove: function () { + if (signal.advice) { + var previous = signal.previous; + var next = signal.next; + if (!next && !previous) { + delete dispatcher[type]; + } else { + if (previous) { + previous.next = next; + } else { + dispatcher[type] = next; + } + if (next) { + next.previous = previous; + } + } + + // remove the advice to signal that this signal has been removed + dispatcher = advice = signal.advice = null; + } + }, + id: dispatcher.nextId++, + advice: advice, + receiveArguments: receiveArguments + }; + } + if (previous && !around) { + if (type == "after") { + // add the listener to the end of the list + // note that we had to change this loop a little bit to workaround a bizarre IE10 JIT bug + while (previous.next && (previous = previous.next)) { + } + previous.next = signal; + signal.previous = previous; + } else if (type == "before") { + // add to beginning + dispatcher[type] = signal; + signal.next = previous; + previous.previous = signal; + } + } else { + // around or first one just replaces + dispatcher[type] = signal; + } + return signal; + } + + function aspect(type) { + return function (target, methodName, advice, receiveArguments) { + var existing = target[methodName], dispatcher; + if (!existing || existing.target != target) { + // no dispatcher in place + target[methodName] = dispatcher = function () { + var executionId = dispatcher.nextId; + // before advice + var args = arguments; + var before = dispatcher.before; + while (before) { + if (before.advice) { + args = before.advice.apply(this, args) || args; + } + before = before.next; + } + // around advice + if (dispatcher.around) { + var results = dispatcher.around.advice(this, args); + } + // after advice + var after = dispatcher.after; + while (after && after.id < executionId) { + if (after.advice) { + if (after.receiveArguments) { + var newResults = after.advice.apply(this, args); + // change the return value only if a new value was returned + results = newResults === undefined ? results : newResults; + } else { + results = after.advice.call(this, results, args); + } + } + after = after.next; + } + return results; + }; + if (existing) { + dispatcher.around = { + advice: function (target, args) { + return existing.apply(target, args); + } + }; + } + dispatcher.target = target; + dispatcher.nextId = dispatcher.nextId || 0; + } + var results = advise((dispatcher || existing), type, advice, receiveArguments); + advice = null; + return results; + }; + } + + var after = aspect("after"); + /*===== + after = function(target, methodName, advice, receiveArguments){ + // summary: + // The "after" export of the aspect module is a function that can be used to attach + // "after" advice to a method. This function will be executed after the original method + // is executed. By default the function will be called with a single argument, the return + // value of the original method, or the the return value of the last executed advice (if a previous one exists). + // The fourth (optional) argument can be set to true to so the function receives the original + // arguments (from when the original method was called) rather than the return value. + // If there are multiple "after" advisors, they are executed in the order they were registered. + // target: Object + // This is the target object + // methodName: String + // This is the name of the method to attach to. + // advice: Function + // This is function to be called after the original method + // receiveArguments: Boolean? + // If this is set to true, the advice function receives the original arguments (from when the original mehtod + // was called) rather than the return value of the original/previous method. + // returns: + // A signal object that can be used to cancel the advice. If remove() is called on this signal object, it will + // stop the advice function from being executed. + }; + =====*/ + + var before = aspect("before"); + /*===== + before = function(target, methodName, advice){ + // summary: + // The "before" export of the aspect module is a function that can be used to attach + // "before" advice to a method. This function will be executed before the original method + // is executed. This function will be called with the arguments used to call the method. + // This function may optionally return an array as the new arguments to use to call + // the original method (or the previous, next-to-execute before advice, if one exists). + // If the before method doesn't return anything (returns undefined) the original arguments + // will be preserved. + // If there are multiple "before" advisors, they are executed in the reverse order they were registered. + // target: Object + // This is the target object + // methodName: String + // This is the name of the method to attach to. + // advice: Function + // This is function to be called before the original method + }; + =====*/ + + var around = aspect("around"); + /*===== + around = function(target, methodName, advice){ + // summary: + // The "around" export of the aspect module is a function that can be used to attach + // "around" advice to a method. The advisor function is immediately executed when + // the around() is called, is passed a single argument that is a function that can be + // called to continue execution of the original method (or the next around advisor). + // The advisor function should return a function, and this function will be called whenever + // the method is called. It will be called with the arguments used to call the method. + // Whatever this function returns will be returned as the result of the method call (unless after advise changes it). + // example: + // If there are multiple "around" advisors, the most recent one is executed first, + // which can then delegate to the next one and so on. For example: + // | around(obj, "foo", function(originalFoo){ + // | return function(){ + // | var start = new Date().getTime(); + // | var results = originalFoo.apply(this, arguments); // call the original + // | var end = new Date().getTime(); + // | console.log("foo execution took " + (end - start) + " ms"); + // | return results; + // | }; + // | }); + // target: Object + // This is the target object + // methodName: String + // This is the name of the method to attach to. + // advice: Function + // This is function to be called around the original method + }; + =====*/ + + /** + * The provided advising function will be called before the main method is called + * @param methodName + * @param advisingFunction + * @return {Handler} + */ + eBus.before = function (methodName, advisingFunction) { + return before(_EventBus_, methodName, advisingFunction); + }; + + /** + * The provided advising function will be called after the main method is called + * @param methodName + * @param advisingFunction + * @param receiveArguments + * @return {Handler} + */ + + eBus.after = function (methodName, advisingFunction, receiveArguments) { + return after(_EventBus_, methodName, advisingFunction, receiveArguments); + }; + + /** + * the advisingFactory called before and after named method invocation + * @param name + * @param advisingFactory + * @return {Handler} + */ + eBus.around = function (name, advisingFactory) { + return eBus.aspect(_EventBus_, name, advisingFactory); + }; + + eBus.aspect = { + around: around, + before: before, + after: after + } + }); + + EventBus.plugin(function (eBus) { + /** + * the stub called before listener trigger + * @param listener + * @param event + * @param otherListenerArg.. + */ + eBus.fn.beforeTriggerListener = function (listener, event, otherListenerArg) { + + }; + /** + * the stub called after listener trigger + * @param listener + * @param event + * @param otherListenerArg.. + */ + eBus.fn.afterTriggerListener = function (listener, event, otherListenerArg) { + + }; + + eBus.DEFAULT.EXCLUDE_NAMES.push("beforeTriggerListener", "afterTriggerListener"); + + function isMatch(eventType, event, condition, args) { + if (typeof eventType == "string") { + eventType = new RegExp(eventType); + } + return eBus.isFunction(condition) ? + (eventType.test(event.type) && condition.apply(this, args)) : + eventType.test(event.type) + } + + eBus.beforeTriggerListener = function (eventType, fn, condition) { + return eBus.before("beforeTriggerListener", function (listener, event) { + if (isMatch(eventType, event, condition, slice(arguments))) + return fn.apply(this, eBus.slice(arguments)); + }); + }; + + eBus.afterTriggerListener = function (eventType, fn, condition) { + return eBus.before("afterTriggerListener", function (listener, event) { + if (isMatch(eventType, event, condition, slice(arguments))) + return fn.apply(this, eBus.slice(arguments)); + }); + }; + }); + + EventBus.plugin(function (eBus) { + //common utils + eBus.slice = slice; + eBus.nextId = nextId; + eBus.iterator = iterator; + eBus.hit = hit; + eBus.isFunction = isFunction; + eBus.isArray = isArray; + }); + + return EventBus; }); \ No newline at end of file