65bcf1fe44d179ac2f712ff58c7ce41c909b73786eaa22ab4ae338c057c350d6c50900b61361d5210c67c0e9082f3000a9da52d985bd61ecbdfe0b27c0ee51 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. import { Dictionary, WithThisType } from './types';
  2. // Return true to cancel bubble
  3. export type EventCallbackSingleParam<EvtParam = any> = EvtParam extends any
  4. ? (params: EvtParam) => boolean | void
  5. : never
  6. export type EventCallback<EvtParams = any[]> = EvtParams extends any[]
  7. ? (...args: EvtParams) => boolean | void
  8. : never
  9. export type EventQuery = string | Object
  10. type CbThis<Ctx, Impl> = unknown extends Ctx ? Impl : Ctx;
  11. type EventHandler<Ctx, Impl, EvtParams> = {
  12. h: EventCallback<EvtParams>
  13. ctx: CbThis<Ctx, Impl>
  14. query: EventQuery
  15. callAtLast: boolean
  16. }
  17. type DefaultEventDefinition = Dictionary<EventCallback<any[]>>;
  18. export interface EventProcessor<EvtDef = DefaultEventDefinition> {
  19. normalizeQuery?: (query: EventQuery) => EventQuery
  20. filter?: (eventType: keyof EvtDef, query: EventQuery) => boolean
  21. afterTrigger?: (eventType: keyof EvtDef) => void
  22. }
  23. /**
  24. * Event dispatcher.
  25. *
  26. * Event can be defined in EvtDef to enable type check. For example:
  27. * ```ts
  28. * interface FooEvents {
  29. * // key: event name, value: the first event param in `trigger` and `callback`.
  30. * myevent: {
  31. * aa: string;
  32. * bb: number;
  33. * };
  34. * }
  35. * class Foo extends Eventful<FooEvents> {
  36. * fn() {
  37. * // Type check of event name and the first event param is enabled here.
  38. * this.trigger('myevent', {aa: 'xx', bb: 3});
  39. * }
  40. * }
  41. * let foo = new Foo();
  42. * // Type check of event name and the first event param is enabled here.
  43. * foo.on('myevent', (eventParam) => { ... });
  44. * ```
  45. *
  46. * @param eventProcessor The object eventProcessor is the scope when
  47. * `eventProcessor.xxx` called.
  48. * @param eventProcessor.normalizeQuery
  49. * param: {string|Object} Raw query.
  50. * return: {string|Object} Normalized query.
  51. * @param eventProcessor.filter Event will be dispatched only
  52. * if it returns `true`.
  53. * param: {string} eventType
  54. * param: {string|Object} query
  55. * return: {boolean}
  56. * @param eventProcessor.afterTrigger Called after all handlers called.
  57. * param: {string} eventType
  58. */
  59. export default class Eventful<EvtDef extends DefaultEventDefinition = DefaultEventDefinition> {
  60. private _$handlers: Dictionary<EventHandler<any, any, any[]>[]>
  61. protected _$eventProcessor: EventProcessor<EvtDef>
  62. constructor(eventProcessors?: EventProcessor<EvtDef>) {
  63. if (eventProcessors) {
  64. this._$eventProcessor = eventProcessors;
  65. }
  66. }
  67. on<Ctx, EvtNm extends keyof EvtDef>(
  68. event: EvtNm,
  69. handler: WithThisType<EvtDef[EvtNm], CbThis<Ctx, this>>,
  70. context?: Ctx
  71. ): this
  72. on<Ctx, EvtNm extends keyof EvtDef>(
  73. event: EvtNm,
  74. query: EventQuery,
  75. handler: WithThisType<EvtDef[EvtNm], CbThis<Ctx, this>>,
  76. context?: Ctx
  77. ): this
  78. /**
  79. * Bind a handler.
  80. *
  81. * @param event The event name.
  82. * @param Condition used on event filter.
  83. * @param handler The event handler.
  84. * @param context
  85. */
  86. on<Ctx, EvtNm extends keyof EvtDef>(
  87. event: EvtNm,
  88. query: EventQuery | WithThisType<EventCallback<EvtDef[EvtNm]>, CbThis<Ctx, this>>,
  89. handler?: WithThisType<EventCallback<EvtDef[EvtNm]>, CbThis<Ctx, this>> | Ctx,
  90. context?: Ctx
  91. ): this {
  92. if (!this._$handlers) {
  93. this._$handlers = {};
  94. }
  95. const _h = this._$handlers;
  96. if (typeof query === 'function') {
  97. context = handler as Ctx;
  98. handler = query as (...args: any) => any;
  99. query = null;
  100. }
  101. if (!handler || !event) {
  102. return this;
  103. }
  104. const eventProcessor = this._$eventProcessor;
  105. if (query != null && eventProcessor && eventProcessor.normalizeQuery) {
  106. query = eventProcessor.normalizeQuery(query);
  107. }
  108. if (!_h[event as string]) {
  109. _h[event as string] = [];
  110. }
  111. for (let i = 0; i < _h[event as string].length; i++) {
  112. if (_h[event as string][i].h === handler) {
  113. return this;
  114. }
  115. }
  116. const wrap: EventHandler<Ctx, this, unknown[]> = {
  117. h: handler as EventCallback<unknown[]>,
  118. query: query,
  119. ctx: (context || this) as CbThis<Ctx, this>,
  120. // FIXME
  121. // Do not publish this feature util it is proved that it makes sense.
  122. callAtLast: (handler as any).zrEventfulCallAtLast
  123. };
  124. const lastIndex = _h[event as string].length - 1;
  125. const lastWrap = _h[event as string][lastIndex];
  126. (lastWrap && lastWrap.callAtLast)
  127. ? _h[event as string].splice(lastIndex, 0, wrap)
  128. : _h[event as string].push(wrap);
  129. return this;
  130. }
  131. /**
  132. * Whether any handler has bound.
  133. */
  134. isSilent(eventName: keyof EvtDef): boolean {
  135. const _h = this._$handlers;
  136. return !_h || !_h[eventName as string] || !_h[eventName as string].length;
  137. }
  138. /**
  139. * Unbind a event.
  140. *
  141. * @param eventType The event name.
  142. * If no `event` input, "off" all listeners.
  143. * @param handler The event handler.
  144. * If no `handler` input, "off" all listeners of the `event`.
  145. */
  146. off(eventType?: keyof EvtDef, handler?: Function): this {
  147. const _h = this._$handlers;
  148. if (!_h) {
  149. return this;
  150. }
  151. if (!eventType) {
  152. this._$handlers = {};
  153. return this;
  154. }
  155. if (handler) {
  156. if (_h[eventType as string]) {
  157. const newList = [];
  158. for (let i = 0, l = _h[eventType as string].length; i < l; i++) {
  159. if (_h[eventType as string][i].h !== handler) {
  160. newList.push(_h[eventType as string][i]);
  161. }
  162. }
  163. _h[eventType as string] = newList;
  164. }
  165. if (_h[eventType as string] && _h[eventType as string].length === 0) {
  166. delete _h[eventType as string];
  167. }
  168. }
  169. else {
  170. delete _h[eventType as string];
  171. }
  172. return this;
  173. }
  174. /**
  175. * Dispatch a event.
  176. *
  177. * @param {string} eventType The event name.
  178. */
  179. trigger<EvtNm extends keyof EvtDef>(
  180. eventType: EvtNm,
  181. ...args: Parameters<EvtDef[EvtNm]>
  182. ): this {
  183. if (!this._$handlers) {
  184. return this;
  185. }
  186. const _h = this._$handlers[eventType as string];
  187. const eventProcessor = this._$eventProcessor;
  188. if (_h) {
  189. const argLen = args.length;
  190. const len = _h.length;
  191. for (let i = 0; i < len; i++) {
  192. const hItem = _h[i];
  193. if (eventProcessor
  194. && eventProcessor.filter
  195. && hItem.query != null
  196. && !eventProcessor.filter(eventType, hItem.query)
  197. ) {
  198. continue;
  199. }
  200. // Optimize advise from backbone
  201. switch (argLen) {
  202. case 0:
  203. hItem.h.call(hItem.ctx);
  204. break;
  205. case 1:
  206. hItem.h.call(hItem.ctx, args[0]);
  207. break;
  208. case 2:
  209. hItem.h.call(hItem.ctx, args[0], args[1]);
  210. break;
  211. default:
  212. // have more than 2 given arguments
  213. hItem.h.apply(hItem.ctx, args);
  214. break;
  215. }
  216. }
  217. }
  218. eventProcessor && eventProcessor.afterTrigger
  219. && eventProcessor.afterTrigger(eventType);
  220. return this;
  221. }
  222. /**
  223. * Dispatch a event with context, which is specified at the last parameter.
  224. *
  225. * @param {string} type The event name.
  226. */
  227. triggerWithContext(type: keyof EvtDef, ...args: any[]): this {
  228. if (!this._$handlers) {
  229. return this;
  230. }
  231. const _h = this._$handlers[type as string];
  232. const eventProcessor = this._$eventProcessor;
  233. if (_h) {
  234. const argLen = args.length;
  235. const ctx = args[argLen - 1];
  236. const len = _h.length;
  237. for (let i = 0; i < len; i++) {
  238. const hItem = _h[i];
  239. if (eventProcessor
  240. && eventProcessor.filter
  241. && hItem.query != null
  242. && !eventProcessor.filter(type, hItem.query)
  243. ) {
  244. continue;
  245. }
  246. // Optimize advise from backbone
  247. switch (argLen) {
  248. case 0:
  249. hItem.h.call(ctx);
  250. break;
  251. case 1:
  252. hItem.h.call(ctx, args[0]);
  253. break;
  254. case 2:
  255. hItem.h.call(ctx, args[0], args[1]);
  256. break;
  257. default:
  258. // have more than 2 given arguments
  259. hItem.h.apply(ctx, args.slice(1, argLen - 1));
  260. break;
  261. }
  262. }
  263. }
  264. eventProcessor && eventProcessor.afterTrigger
  265. && eventProcessor.afterTrigger(type);
  266. return this;
  267. }
  268. }