UNPKG

7.02 kBPlain TextView Raw
1
2import {Logger} from "./logger";
3import {LoggerFactory} from "./logger";
4import {Utils as _} from './utils';
5import {Bean} from "./context/context";
6import {Qualifier} from "./context/context";
7import {IEventEmitter} from "./interfaces/iEventEmitter";
8import {GridOptionsWrapper} from "./gridOptionsWrapper";
9import {AgEvent, Events} from "./events";
10
11@Bean('eventService')
12export class EventService implements IEventEmitter {
13
14 private allSyncListeners: {[key: string]: Function[]} = {};
15 private allAsyncListeners: {[key: string]: Function[]} = {};
16
17 private globalSyncListeners: Function[] = [];
18 private globalAsyncListeners: Function[] = [];
19
20 private logger: Logger;
21
22 private asyncFunctionsQueue: Function[] = [];
23 private scheduled = false;
24
25 // this is an old idea niall had, should really take it out, was to do with ordering who gets to process
26 // events first, to give model and service objects preference over the view
27 private static PRIORITY = '-P1';
28
29 // because this class is used both inside the context and outside the context, we do not
30 // use autowired attributes, as that would be confusing, as sometimes the attributes
31 // would be wired, and sometimes not.
32 //
33 // the global event servers used by ag-Grid is autowired by the context once, and this
34 // setBeans method gets called once.
35 //
36 // the times when this class is used outside of the context (eg RowNode has an instance of this
37 // class) then it is not a bean, and this setBeans method is not called.
38 public setBeans(@Qualifier('loggerFactory') loggerFactory: LoggerFactory,
39 @Qualifier('gridOptionsWrapper') gridOptionsWrapper: GridOptionsWrapper,
40 @Qualifier('globalEventListener') globalEventListener: Function = null) {
41 this.logger = loggerFactory.create('EventService');
42
43 if (globalEventListener) {
44 let async = gridOptionsWrapper.useAsyncEvents();
45 this.addGlobalListener(globalEventListener, async);
46 }
47 }
48
49 private getListenerList(eventType: string, async: boolean): Function[] {
50 let listenerMap = async ? this.allAsyncListeners : this.allSyncListeners;
51 let listenerList = listenerMap[eventType];
52 if (!listenerList) {
53 listenerList = [];
54 listenerMap[eventType] = listenerList;
55 }
56 return listenerList;
57 }
58
59 public addEventListener(eventType: string, listener: Function, async = false): void {
60 let listenerList = this.getListenerList(eventType, async);
61 if (listenerList.indexOf(listener)<0) {
62 listenerList.push(listener);
63 }
64 }
65
66 // for some events, it's important that the model gets to hear about them before the view,
67 // as the model may need to update before the view works on the info. if you register
68 // via this method, you get notified before the view parts
69 public addModalPriorityEventListener(eventType: string, listener: Function, async = false): void {
70 this.addEventListener(eventType + EventService.PRIORITY, listener, async);
71 }
72
73 public addGlobalListener(listener: Function, async = false): void {
74 if (async) {
75 this.globalAsyncListeners.push(listener);
76 } else {
77 this.globalSyncListeners.push(listener);
78 }
79 }
80
81 public removeEventListener(eventType: string, listener: Function, async = false): void {
82 let listenerList = this.getListenerList(eventType, async);
83 _.removeFromArray(listenerList, listener);
84 }
85
86 public removeGlobalListener(listener: Function, async = false): void {
87 if (async) {
88 _.removeFromArray(this.globalAsyncListeners, listener);
89 } else {
90 _.removeFromArray(this.globalSyncListeners, listener);
91 }
92 }
93
94 // why do we pass the type here? the type is in ColumnChangeEvent, so unless the
95 // type is not in other types of events???
96 public dispatchEvent(event: AgEvent): void {
97 // console.log(`dispatching ${eventType}: ${event}`);
98 this.dispatchToListeners(event, true);
99 this.dispatchToListeners(event, false);
100 }
101
102 private dispatchToListeners(event: AgEvent, async: boolean) {
103
104 let globalListeners = async ? this.globalAsyncListeners : this.globalSyncListeners;
105 let eventType = event.type;
106
107 // this allows the columnController to get events before anyone else
108 let p1ListenerList = this.getListenerList(eventType + EventService.PRIORITY, async);
109 _.forEachSnapshotFirst(p1ListenerList, listener => {
110 if (async) {
111 this.dispatchAsync( () => listener(event) );
112 } else {
113 listener(event);
114 }
115 });
116
117 let listenerList = this.getListenerList(eventType, async);
118 _.forEachSnapshotFirst(listenerList,listener => {
119 if (async) {
120 this.dispatchAsync( () => listener(event) );
121 } else {
122 listener(event);
123 }
124 });
125
126 _.forEachSnapshotFirst(globalListeners, listener => {
127 if (async) {
128 this.dispatchAsync( () => listener(eventType, event) );
129 } else {
130 listener(eventType, event);
131 }
132 });
133 }
134
135 // this gets called inside the grid's thread, for each event that it
136 // wants to set async. the grid then batches the events into one setTimeout()
137 // because setTimeout() is an expensive operation. ideally we would have
138 // each event in it's own setTimeout(), but we batch for performance.
139 private dispatchAsync(func: Function): void {
140
141 // add to the queue for executing later in the next VM turn
142 this.asyncFunctionsQueue.push(func);
143
144 // check if timeout is already scheduled. the first time the grid calls
145 // this within it's thread turn, this should be false, so it will schedule
146 // the 'flush queue' method the first time it comes here. then the flag is
147 // set to 'true' so it will know it's already scheduled for subsequent calls.
148 if (!this.scheduled) {
149 // if not scheduled, schedule one
150 setTimeout(this.flushAsyncQueue.bind(this), 0);
151 // mark that it is scheduled
152 this.scheduled = true;
153 }
154 }
155
156 // this happens in the next VM turn only, and empties the queue of events
157 private flushAsyncQueue(): void {
158 this.scheduled = false;
159
160 // we take a copy, because the event listener could be using
161 // the grid, which would cause more events, which would be potentially
162 // added to the queue, so safe to take a copy, the new events will
163 // get executed in a later VM turn rather than risk updating the
164 // queue as we are flushing it.
165 let queueCopy = this.asyncFunctionsQueue.slice();
166 this.asyncFunctionsQueue = [];
167
168 // execute the queue
169 queueCopy.forEach( func => func() );
170 }
171}