-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathemit.vue
186 lines (158 loc) · 6.42 KB
/
emit.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
<script lang="js" frontend>
// TODO: Create vue-emit and import
var methods = {}; // Method's to glue onto vm.$emit
/**
* Extend the base app.vue.prototye.$emit with app.vue.prototye.$emit.broadcast()
* This is effectively the same as an $emit.down from the root node
* NOTE: This also broadcasts to app.vue
* @param {string} msg The name of the event to emit
* @param {*} [payload...] The payload of the event
*/
app.broadcast = methods.broadcast = (msg, ...payload) => {
app.vue.$emit.call(app.vue, msg, ...payload);
app.vue.$emit.down.call(app.vue, msg, ...payload);
};
/**
* Extend the base app.vue.prototye.$emit with app.vue.prototye.$emit.down() which recurses though all children emitting a message
* NOTE: This does not include the current component level, only the children
* NOTE: Non-injected $emit components (i.e. without the mixin) are ignored
* @param {string} msg The name of the event to emit
* @param {*} [payload...] The payload of the event
* @returns {VueComponent} This chainable VM
*/
methods.down = function(msg, ...payload) {
this.$children.forEach(child => {
child.$emit.call(child, msg, ...payload);
if (child.$emit.down) // Only proceed downwards if the child element actually understands $emit.down
child.$emit.down.call(child, msg, ...payload);
});
return this;
};
/**
* Extend the base methods.$emit with app.vue.prototye.$emit.up() which recurses though all parents emitting a message
* NOTE: This does not include the current component level, only the parents
* @param {string} msg The name of the event to emit
* @param {*} [payload...] The payload of the event
* @returns {VueComponent} This chainable VM
*/
methods.up = function(msg, ...payload) {
if (!this || !this.$parent) throw new Error('Unable to $emit.up() on non Vue component');
var node = this;
while (node.$parent) {
node = node.$parent;
node.$emit.call(node, msg, ...payload);
}
return this;
};
/**
* Operates like vm.$emit.broadcast() but will only be recieved by named components
* This does not recurse upwards or downwards but targets any component matching the specified target
* @param {string|array<string>} target The camelCase / snake_case / kebab-case name of the component(s) to recieve the emitted event (all target names are converted via `_.camelCase()`)
* @param {string} msg The name of the event to emit
* @param {*} [payload...] The payload of the event
* @returns {VueComponent} This chainable VM
*/
methods.to = function(target, msg, ...payload) {
var nameFilters = new Set( // Create lookup set of names to filter by (after kebab casing to match Vue's internal system)
_.castArray(target).map(_.kebabCase)
);
var recurseDown = function(component) {
if (nameFilters.has(component.$options._componentTag))
component.$emit(msg, ...payload);
component.$children.forEach(child => recurseDown(child));
};
recurseDown(app.vue);
return this;
};
/**
* Extend the base app.vue.prototye.$emit with promise support
* Any function returning a promise will be waited on
* NOTE: As of 2019-07-10 (Vue 2.6.10) it is not possible to glue this as vm.$emit.promise for some reason, maybe one day this will change
* @param {string} msg The name of the event to emit
* @param {*} [payload...] The payload of the event
* @returns {Promise} A promise which will resolve with the Promise.all result of all listeners
*/
methods.promise = function(msg, ...payload) {
var node = this;
if (!node || !this._isVue) throw new Error('Unable to $emit.promise() on non Vue component');
if (!node._events[msg] || !this._events[msg].length) return Promise.resolve(); // No listeners anyway
return Promise.all(
node._events[msg].map(e =>
Promise.resolve(
e.apply(node, payload)
)
)
).then(responses => responses[0]);
};
/**
* Return if anything is listening to a given event
* This is useful if the event broadcast would involve some non-trivial processing and it would be beneficial to skip if nothing is listening anyway
* @param {string} msg The name of the event to emit
* @returns {boolean} Boolean true if any matching listener is present
*/
methods.hasListeners = function(msg) {
if (!this || !this.$parent) throw new Error('Unable to $emit.hasSubscribers() on non Vue component');
return (
this._events[msg]
&& this._events[msg].length > 0
);
};
/**
* Auto emit a message on a timer
* If any listener returns a promise it is waited on
* @param {string} msg The name of the event to emit
* @param {*} [payload...] Optional additional payload for the event
* @param {number} interval When to schedule the event, required and must be numeric
* @returns {VueComponent} This chainable VM
*/
methods.schedule = function(msg, ...payload) {
if (!this || !this.$parent) throw new Error('Unable to $emit.schedule() on non Vue component');
if (!this.$emit.scheduleHandles) this.$emit.scheduleHandles = {};
var interval = payload.pop();
if (typeof interval != 'number') throw new Error('Last argument to $emit.auto must be a numeric interval');
this.$emit.scheduleHandles[msg] = {
message: msg,
payload,
interval,
timer: undefined,
reschedule: ()=> {
this.$emit.scheduleHandles[msg].timer = setTimeout(()=> {
if (!this || !this.$emit.scheduleHandles[msg]) return;
this.$emit.promise(this.$emit.scheduleHandles[msg].message, ...this.$emit.scheduleHandles[msg].payload)
.then(()=> this.$emit.scheduleHandles[msg].reschedule)
}, this.$emit.scheduleHandles[msg].interval);
},
};
this.$emit.scheduleHandles[msg].reschedule(); // Schedule initial timer
return this;
};
/**
* Release an scheduled emitter
* If called with no arguments all scheduled timers are released
* This function is called automatically whenever a VM is being destroyed
* @param {string|array<string>} [msgs] Messages to unschedule, if omitted all are assumed
* @returns {VueComponent} This chainable VM
*/
methods.unschedule = function(msgs) {
if (!this.$emit.scheduleHandles) return; // Nothing scheduled anyway
_.castArray(msgs || Object.keys(this.$emit.scheduleHandles))
.forEach(msg => {
clearTimeout(this.$emit.scheduleHandles[msg]);
delete this.$emit.scheduleHandles[msg];
})
};
app.mixin({
beforeCreate() {
this.$emit = this.$emit.bind(this); // Rebind $emit to this vm so we get the right context
// Long-winded version of _.mapValues() where we remap each method to a binding of this VM
Object.assign(this.$emit, Object.fromEntries(
Object.entries(methods).map(i =>
[i[0], i[1].bind(this)]
)
));
},
beforeDestroy() {
this.$emit.unschedule();
},
});
</script>