1
|
|
|
(function (jQuery) { |
2
|
|
|
'use strict'; |
3
|
|
|
|
4
|
|
|
jQuery = jQuery && jQuery.hasOwnProperty('default') ? jQuery['default'] : jQuery; |
5
|
|
|
|
6
|
|
|
var AlwaysData = /** @class */ (function () {
|
7
|
|
|
function AlwaysData() {
|
8
|
|
|
this.instance = null;
|
9
|
|
|
this.lastOperation = null;
|
10
|
|
|
}
|
11
|
|
|
AlwaysData.OPERATION_INSERTION = 0;
|
12
|
|
|
AlwaysData.OPERATION_REMOVAL = 1;
|
13
|
|
|
return AlwaysData;
|
14
|
|
|
}()); |
15
|
|
|
|
16
|
|
|
var Always = /** @class */ (function () {
|
17
|
|
|
/**
|
18
|
|
|
* Constructor.
|
19
|
|
|
*
|
20
|
|
|
* @param {HTMLElement} element
|
21
|
|
|
*/
|
22
|
|
|
function Always(element) {
|
23
|
|
|
var _this = this;
|
24
|
|
|
this.insertedCallbacks = {};
|
25
|
|
|
this.removedCallbacks = {};
|
26
|
|
|
this.element = element;
|
27
|
|
|
this.observer = new MutationObserver(function (mutations) { return mutations.forEach(function (mutation) {
|
28
|
|
|
if ('childList' !== mutation.type) {
|
29
|
|
|
return;
|
30
|
|
|
}
|
31
|
|
|
// NodeList does not support forEach directly due to a bug in Google Chrome
|
32
|
|
|
[].forEach.call(mutation.addedNodes, function (node) {
|
33
|
|
|
if (!(node instanceof HTMLElement)) {
|
34
|
|
|
return;
|
35
|
|
|
}
|
36
|
|
|
_this.notifyInserted(node);
|
37
|
|
|
});
|
38
|
|
|
[].forEach.call(mutation.removedNodes, function (node) {
|
39
|
|
|
if (!(node instanceof HTMLElement)) {
|
40
|
|
|
return;
|
41
|
|
|
}
|
42
|
|
|
_this.notifyRemoved(node);
|
43
|
|
|
});
|
44
|
|
|
}); });
|
45
|
|
|
this.observer.observe(this.element, {
|
46
|
|
|
childList: true,
|
47
|
|
|
subtree: true
|
48
|
|
|
});
|
49
|
|
|
}
|
50
|
|
|
/**
|
51
|
|
|
* Retrieve a jQuery Always specific data object assigned to the specified element, or create one if it does
|
52
|
|
|
* not already exist.
|
53
|
|
|
*
|
54
|
|
|
* @param {HTMLElement} element
|
55
|
|
|
* @returns {AlwaysData}
|
56
|
|
|
*/
|
57
|
|
|
Always.data = function (element) {
|
58
|
|
|
if (!element.hasOwnProperty('jQueryAlways')) {
|
59
|
|
|
Object.defineProperty(element, 'jQueryAlways', {
|
60
|
|
|
value: new AlwaysData(),
|
61
|
|
|
configurable: true
|
62
|
|
|
});
|
63
|
|
|
}
|
64
|
|
|
return element.jQueryAlways;
|
65
|
|
|
};
|
66
|
|
|
/**
|
67
|
|
|
* Attaches a new Always instance to the specified element and returns it, or returns a previously attached
|
68
|
|
|
* instance.
|
69
|
|
|
*
|
70
|
|
|
* @param {HTMLElement} element
|
71
|
|
|
* @returns {Always}
|
72
|
|
|
*/
|
73
|
|
|
Always.attach = function (element) {
|
74
|
|
|
var data = this.data(element);
|
75
|
|
|
if (!data.instance) {
|
76
|
|
|
data.instance = new Always(element);
|
77
|
|
|
}
|
78
|
|
|
return data.instance;
|
79
|
|
|
};
|
80
|
|
|
/**
|
81
|
|
|
* Detaches a previously attached Always instance from the specified element & also removes the mutation
|
82
|
|
|
* observer.
|
83
|
|
|
*
|
84
|
|
|
* @param {HTMLElement} element
|
85
|
|
|
*/
|
86
|
|
|
Always.detach = function (element) {
|
87
|
|
|
Always.attach(element).observer.disconnect();
|
88
|
|
|
delete element.jQueryAlways;
|
89
|
|
|
};
|
90
|
|
|
/**
|
91
|
|
|
* Normalizes similar selectors.
|
92
|
|
|
* E.g. "a, b" and "b,a" are the same thing, both will be normalized to "a,b".
|
93
|
|
|
*
|
94
|
|
|
* @param {string} selector
|
95
|
|
|
* @returns {string}
|
96
|
|
|
*/
|
97
|
|
|
Always.normalizeSelector = function (selector) {
|
98
|
|
|
return selector.split(',').map(function (part) {
|
99
|
|
|
return part.trim();
|
100
|
|
|
}).sort().join(',');
|
101
|
|
|
};
|
102
|
|
|
/**
|
103
|
|
|
* Attaches the specified inserted / removed listeners for elements matching the specified selector on the
|
104
|
|
|
* specified parent element.
|
105
|
|
|
*
|
106
|
|
|
* @param {HTMLElement} element
|
107
|
|
|
* @param {string} selector
|
108
|
|
|
* @param {() => void} onInserted
|
109
|
|
|
* @param {() => void} onRemoved
|
110
|
|
|
*/
|
111
|
|
|
Always.always = function (element, selector, onInserted, onRemoved) {
|
112
|
|
|
var instance = Always.attach(element);
|
113
|
|
|
// register inserted callbacks
|
114
|
|
|
if ('function' === typeof onInserted) {
|
115
|
|
|
instance.addInsertedCallback(selector, onInserted);
|
116
|
|
|
// invoke the inserted callback on all matching elements already in the dom
|
117
|
|
|
// this invocation is always forced even if the element's last operation was an insertion
|
118
|
|
|
// we still need to mark the element, though, to avoid future duplicate notifies
|
119
|
|
|
[].forEach.call(element.querySelectorAll(selector), function (node) {
|
120
|
|
|
Always.data(node).lastOperation = AlwaysData.OPERATION_INSERTION;
|
121
|
|
|
onInserted.call(node);
|
122
|
|
|
});
|
123
|
|
|
}
|
124
|
|
|
// register removed callbacks
|
125
|
|
|
if ('function' === typeof onRemoved) {
|
126
|
|
|
instance.addRemovedCallback(selector, onRemoved);
|
127
|
|
|
}
|
128
|
|
|
};
|
129
|
|
|
/**
|
130
|
|
|
* Detaches the specified inserted / removed listener(s) for elements matching the specified selector on the
|
131
|
|
|
* specified element as parent.
|
132
|
|
|
*
|
133
|
|
|
* @param {HTMLElement} element
|
134
|
|
|
* @param {string} selector
|
135
|
|
|
* @param {() => void} onInserted
|
136
|
|
|
* @param {() => void} onRemoved
|
137
|
|
|
*/
|
138
|
|
|
Always.never = function (element, selector, onInserted, onRemoved) {
|
139
|
|
|
// if no selector is specified, quickest way to remove all listeners is to just detach
|
140
|
|
|
if (!selector) {
|
141
|
|
|
Always.detach(element);
|
142
|
|
|
return;
|
143
|
|
|
}
|
144
|
|
|
var instance = Always.attach(element);
|
145
|
|
|
// if no specific callback is requested, remove all listeners that match the selector
|
146
|
|
|
if (!onInserted && !onRemoved) {
|
147
|
|
|
instance.removeInsertedCallback(selector);
|
148
|
|
|
instance.removeRemovedCallback(selector);
|
149
|
|
|
return;
|
150
|
|
|
}
|
151
|
|
|
// remove only specific listeners
|
152
|
|
|
if (onInserted) {
|
153
|
|
|
instance.removeInsertedCallback(selector, onInserted);
|
154
|
|
|
}
|
155
|
|
|
if (onRemoved) {
|
156
|
|
|
instance.removeRemovedCallback(selector, onRemoved);
|
157
|
|
|
}
|
158
|
|
|
};
|
159
|
|
|
/**
|
160
|
|
|
* Adds a new callback for the specified selector.
|
161
|
|
|
*
|
162
|
|
|
* @param {{[p: string]: (() => void)[]}} callbacks
|
163
|
|
|
* @param {string} selector
|
164
|
|
|
* @param {() => void} callback
|
165
|
|
|
* @returns {Always}
|
166
|
|
|
*/
|
167
|
|
|
Always.prototype.addCallback = function (callbacks, selector, callback) {
|
168
|
|
|
selector = Always.normalizeSelector(selector);
|
169
|
|
|
if (!callbacks.hasOwnProperty(selector)) {
|
170
|
|
|
callbacks[selector] = [];
|
171
|
|
|
}
|
172
|
|
|
callbacks[selector].push(callback);
|
173
|
|
|
return this;
|
174
|
|
|
};
|
175
|
|
|
/**
|
176
|
|
|
* Removes the specified callback for the specified selector.
|
177
|
|
|
* If no callback is specified, removes all callbacks for the selector.
|
178
|
|
|
*
|
179
|
|
|
* @param {{[p: string]: (() => void)[]}} callbacks
|
180
|
|
|
* @param {string} selector
|
181
|
|
|
* @param {() => void} callback
|
182
|
|
|
* @returns {Always}
|
183
|
|
|
*/
|
184
|
|
|
Always.prototype.removeCallback = function (callbacks, selector, callback) {
|
185
|
|
|
selector = Always.normalizeSelector(selector);
|
186
|
|
|
if (!callbacks.hasOwnProperty(selector)) {
|
187
|
|
|
return this;
|
188
|
|
|
}
|
189
|
|
|
if (callback) {
|
190
|
|
|
for (var index = -1; -1 < (index = callbacks[selector].indexOf(callback));) {
|
|
|
|
|
191
|
|
|
callbacks[selector].splice(index, 1);
|
192
|
|
|
}
|
193
|
|
|
}
|
194
|
|
|
else {
|
195
|
|
|
delete callbacks[selector];
|
196
|
|
|
}
|
197
|
|
|
return this;
|
198
|
|
|
};
|
199
|
|
|
/**
|
200
|
|
|
* Notifies the specified callbacks for the specified operation on the specified element.
|
201
|
|
|
* This is a convenience method for notifyInserted and notifyRemoved.
|
202
|
|
|
*
|
203
|
|
|
* @param {HTMLElement} element
|
204
|
|
|
* @param {{[p: string]: (() => void)[]}} callbacks
|
205
|
|
|
* @param {number} operation
|
206
|
|
|
* @returns {Always}
|
207
|
|
|
*/
|
208
|
|
|
Always.prototype.notifyCallbacks = function (element, callbacks, operation) {
|
209
|
|
|
// make the element manageable & prevent duplicate invocations
|
210
|
|
|
// even making every single node in the Dom manageable is still much faster that matching it against a selector
|
211
|
|
|
var data = Always.data(element);
|
212
|
|
|
if (operation === data.lastOperation) {
|
213
|
|
|
return this;
|
214
|
|
|
}
|
215
|
|
|
data.lastOperation = operation;
|
216
|
|
|
// traverse requested callbacks & invoke those for which the element matches the corresponding selector
|
217
|
|
|
Object.keys(callbacks).forEach(function (selector) {
|
218
|
|
|
if (element.matches(selector)) {
|
219
|
|
|
callbacks[selector].forEach(function (callback) {
|
220
|
|
|
callback.call(element);
|
221
|
|
|
});
|
222
|
|
|
}
|
223
|
|
|
});
|
224
|
|
|
return this;
|
225
|
|
|
};
|
226
|
|
|
/**
|
227
|
|
|
* Notifies all registered callbacks about an insertion, if the corresponding selector matches the node.
|
228
|
|
|
*
|
229
|
|
|
* @param {HTMLElement} element
|
230
|
|
|
* @returns {Always}
|
231
|
|
|
*/
|
232
|
|
|
Always.prototype.notifyInserted = function (element) {
|
233
|
|
|
// callbacks for insertions are invoked for parents first
|
234
|
|
|
this.notifyCallbacks(element, this.insertedCallbacks, AlwaysData.OPERATION_INSERTION);
|
235
|
|
|
// we need to manually cascade notify all child nodes as the observer won't do it automatically
|
236
|
|
|
if (element.children) {
|
237
|
|
|
for (var _i = 0, _a = element.children; _i < _a.length; _i++) {
|
238
|
|
|
var node = _a[_i];
|
239
|
|
|
this.notifyInserted(node);
|
240
|
|
|
}
|
241
|
|
|
}
|
242
|
|
|
return this;
|
243
|
|
|
};
|
244
|
|
|
/**
|
245
|
|
|
* Notifies all registered callbacks about a removal, if the corresponding selector matches the node.
|
246
|
|
|
*
|
247
|
|
|
* @param {HTMLElement} element
|
248
|
|
|
* @returns {Always}
|
249
|
|
|
*/
|
250
|
|
|
Always.prototype.notifyRemoved = function (element) {
|
251
|
|
|
// we need to manually cascade notify all child nodes as the observer won't do it automatically
|
252
|
|
|
if (element.children) {
|
253
|
|
|
for (var _i = 0, _a = element.children; _i < _a.length; _i++) {
|
254
|
|
|
var node = _a[_i];
|
255
|
|
|
this.notifyRemoved(node);
|
256
|
|
|
}
|
257
|
|
|
}
|
258
|
|
|
// callbacks for removals are invoked for deepest children first
|
259
|
|
|
this.notifyCallbacks(element, this.removedCallbacks, AlwaysData.OPERATION_REMOVAL);
|
260
|
|
|
return this;
|
261
|
|
|
};
|
262
|
|
|
/**
|
263
|
|
|
* Adds a new inserted callback for the specified selector.
|
264
|
|
|
*
|
265
|
|
|
* @param {string} selector
|
266
|
|
|
* @param {() => void} callback
|
267
|
|
|
* @returns {Always}
|
268
|
|
|
*/
|
269
|
|
|
Always.prototype.addInsertedCallback = function (selector, callback) {
|
270
|
|
|
return this.addCallback(this.insertedCallbacks, selector, callback);
|
271
|
|
|
};
|
272
|
|
|
/**
|
273
|
|
|
* Adds a new removed callback for the specified selector.
|
274
|
|
|
*
|
275
|
|
|
* @param {string} selector
|
276
|
|
|
* @param {() => void} callback
|
277
|
|
|
* @returns {Always}
|
278
|
|
|
*/
|
279
|
|
|
Always.prototype.addRemovedCallback = function (selector, callback) {
|
280
|
|
|
return this.addCallback(this.removedCallbacks, selector, callback);
|
281
|
|
|
};
|
282
|
|
|
/**
|
283
|
|
|
* Removes the specified inserted callback for the specified selector.
|
284
|
|
|
* If no callback is specified, removes all callbacks for the selector.
|
285
|
|
|
*
|
286
|
|
|
* @param {string} selector
|
287
|
|
|
* @param {() => void} callback
|
288
|
|
|
* @returns {Always}
|
289
|
|
|
*/
|
290
|
|
|
Always.prototype.removeInsertedCallback = function (selector, callback) {
|
291
|
|
|
return this.removeCallback(this.insertedCallbacks, selector, callback);
|
292
|
|
|
};
|
293
|
|
|
/**
|
294
|
|
|
* Removes the specified removed callback for the specified selector.
|
295
|
|
|
* If no callback is specified, removes all callbacks for the selector.
|
296
|
|
|
*
|
297
|
|
|
* @param {string} selector
|
298
|
|
|
* @param {() => void} callback
|
299
|
|
|
* @returns {Always}
|
300
|
|
|
*/
|
301
|
|
|
Always.prototype.removeRemovedCallback = function (selector, callback) {
|
302
|
|
|
return this.removeCallback(this.removedCallbacks, selector, callback);
|
303
|
|
|
};
|
304
|
|
|
return Always;
|
305
|
|
|
}()); |
306
|
|
|
|
307
|
|
|
function bindingNative() {
|
308
|
|
|
window.Always = {
|
309
|
|
|
always: Always.always,
|
310
|
|
|
never: Always.never
|
311
|
|
|
};
|
312
|
|
|
} |
313
|
|
|
|
314
|
|
|
function bindingJQuery() {
|
315
|
|
|
if ('undefined' === typeof jQuery) {
|
316
|
|
|
return;
|
317
|
|
|
}
|
318
|
|
|
(function ($) {
|
319
|
|
|
$.extend($.fn, {
|
320
|
|
|
always: function (selector, onInserted, onRemoved) {
|
321
|
|
|
return $(this).each(function () {
|
322
|
|
|
Always.always(this, selector, onInserted, onRemoved);
|
323
|
|
|
});
|
324
|
|
|
},
|
325
|
|
|
never: function (selector, onInserted, onRemoved) {
|
326
|
|
|
return $(this).each(function () {
|
327
|
|
|
Always.never(this, selector, onInserted, onRemoved);
|
328
|
|
|
});
|
329
|
|
|
}
|
330
|
|
|
});
|
331
|
|
|
})(jQuery);
|
332
|
|
|
} |
333
|
|
|
|
334
|
|
|
var Bindings = /** @class */ (function () {
|
335
|
|
|
function Bindings() {
|
336
|
|
|
}
|
337
|
|
|
Bindings.native = bindingNative;
|
338
|
|
|
Bindings.jQuery = bindingJQuery;
|
339
|
|
|
return Bindings;
|
340
|
|
|
}()); |
341
|
|
|
|
342
|
|
|
function polyfillMutationObserver() {
|
343
|
|
|
if (window.MutationObserver) {
|
344
|
|
|
return;
|
345
|
|
|
}
|
346
|
|
|
window.MutationObserver = window.WebKitMutationObserver;
|
347
|
|
|
} |
348
|
|
|
|
349
|
|
|
function polyfillElementMatches() {
|
350
|
|
|
var _this = this;
|
351
|
|
|
var prototype = Element.prototype;
|
352
|
|
|
if (prototype.matches) {
|
353
|
|
|
return;
|
354
|
|
|
}
|
355
|
|
|
prototype.matches =
|
356
|
|
|
prototype.matchesSelector ||
|
357
|
|
|
prototype.mozMatchesSelector ||
|
358
|
|
|
prototype.msMatchesSelector ||
|
359
|
|
|
prototype.oMatchesSelector ||
|
360
|
|
|
prototype.webkitMatchesSelector ||
|
361
|
|
|
(function (s) {
|
362
|
|
|
var matches = (_this.document || _this.ownerDocument).querySelectorAll(s);
|
363
|
|
|
for (var i = 0; i < matches.length; i++) {
|
364
|
|
|
if (matches.item(i) === _this) {
|
365
|
|
|
return true;
|
366
|
|
|
}
|
367
|
|
|
}
|
368
|
|
|
return false;
|
369
|
|
|
});
|
370
|
|
|
} |
371
|
|
|
|
372
|
|
|
var Polyfills = /** @class */ (function () {
|
373
|
|
|
function Polyfills() {
|
374
|
|
|
}
|
375
|
|
|
Polyfills.elementMatches = polyfillElementMatches;
|
376
|
|
|
Polyfills.mutationObserver = polyfillMutationObserver;
|
377
|
|
|
return Polyfills;
|
378
|
|
|
}()); |
379
|
|
|
|
380
|
|
|
/** global: Element */
|
381
|
|
|
/** global: HTMLElement */
|
382
|
|
|
/** global: MutationObserver */
|
383
|
|
|
/** global: WebKitMutationObserver */
|
384
|
|
|
// polyfills
|
385
|
|
|
Polyfills.elementMatches();
|
386
|
|
|
Polyfills.mutationObserver();
|
387
|
|
|
// bindings
|
388
|
|
|
Bindings.native();
|
389
|
|
|
Bindings.jQuery(); |
390
|
|
|
|
391
|
|
|
}(jQuery)); |
392
|
|
|
|