|
5 | 5 | * Copyright (c) 2015-2016 Adam Pietrasiak |
6 | 6 | * Released under the MIT license |
7 | 7 | * https://github.com/timpler/jquery.initialize/blob/master/LICENSE |
| 8 | + * |
| 9 | + * This is based on MutationObserver |
| 10 | + * https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver |
8 | 11 | */ |
9 | 12 | ;(function ($) { |
10 | 13 |
|
11 | 14 | "use strict"; |
12 | 15 |
|
| 16 | + var combinators = [' ', '>', '+', '~']; // https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors#Combinators |
| 17 | + var fraternisers = ['+', '~']; // These combinators involve siblings. |
| 18 | + var complexTypes = ['ATTR', 'PSEUDO', 'ID', 'CLASS']; // These selectors are based upon attributes. |
| 19 | + |
| 20 | + // Understand what kind of selector the initializer is based upon. |
| 21 | + function grok(msobserver) { |
| 22 | + if (!$.find.tokenize) { |
| 23 | + // This is an old version of jQuery, so cannot parse the selector. |
| 24 | + // Therefore we must assume the worst case scenario. That is, that |
| 25 | + // this is a complicated selector. This feature was available in: |
| 26 | + // https://github.com/jquery/sizzle/issues/242 |
| 27 | + msobserver.isCombinatorial = true; |
| 28 | + msobserver.isFraternal = true; |
| 29 | + msobserver.isComplex = true; |
| 30 | + return; |
| 31 | + } |
| 32 | + |
| 33 | + // Parse the selector. |
| 34 | + msobserver.isCombinatorial = false; |
| 35 | + msobserver.isFraternal = false; |
| 36 | + msobserver.isComplex = false; |
| 37 | + var token = $.find.tokenize(msobserver.selector); |
| 38 | + for (var i = 0; i < token.length; i++) { |
| 39 | + for (var j = 0; j < token[i].length; j++) { |
| 40 | + if (combinators.indexOf(token[i][j].type) != -1) |
| 41 | + msobserver.isCombinatorial = true; // This selector uses combinators. |
| 42 | + |
| 43 | + if (fraternisers.indexOf(token[i][j].type) != -1) |
| 44 | + msobserver.isFraternal = true; // This selector uses sibling combinators. |
| 45 | + |
| 46 | + if (complexTypes.indexOf(token[i][j].type) != -1) |
| 47 | + msobserver.isComplex = true; // This selector is based on attributes. |
| 48 | + } |
| 49 | + } |
| 50 | + } |
| 51 | + |
13 | 52 | // MutationSelectorObserver represents a selector and it's associated initialization callback. |
14 | 53 | var MutationSelectorObserver = function (selector, callback, options) { |
15 | | - this.selector = selector; |
| 54 | + this.selector = selector.trim(); |
16 | 55 | this.callback = callback; |
17 | 56 | this.options = options; |
18 | | - }; |
19 | 57 |
|
20 | | - // List of mutation types that are observable. |
21 | | - var mtypes = ['childList', 'attributes']; |
| 58 | + grok(this); |
| 59 | + }; |
22 | 60 |
|
23 | 61 | // List of MutationSelectorObservers. |
24 | 62 | var msobservers = []; |
|
43 | 81 |
|
44 | 82 | // The MutationObserver watches for when new elements are added to the DOM. |
45 | 83 | var observer = new MutationObserver(function (mutations) { |
46 | | - |
47 | 84 | var matches = []; |
48 | | - function add(match) { |
| 85 | + function push(match) { |
49 | 86 | matches.push(match); |
50 | 87 | } |
51 | 88 |
|
52 | 89 | // For each mutation. |
53 | 90 | for (var m = 0; m < mutations.length; m++) { |
54 | 91 |
|
55 | | - // Do we observe this mutation type? |
56 | | - if ($.inArray(mutations[m].type, mtypes) == -1) continue; |
57 | | - |
58 | | - if (msobserver.options.scanMode == 'target') { |
59 | | - |
60 | | - // Search within the observed node for elements matching the selector. |
61 | | - // This can take longer, but we are more likely to find a match with |
62 | | - // complex selectors. |
63 | | - msobserver.options.target.querySelectorAll(msobserver.selector).forEach(add); |
64 | | - } else if (msobserver.options.scanMode == 'descendants') { |
65 | | - |
66 | | - // If this is an attributes mutation, then the target is the node upon which the mutation occurred. |
67 | | - if (mutations[m].type == 'attributes') { |
68 | | - mutations[m].target.querySelectorAll(msobserver.selector).forEach(add); |
69 | | - if (mutations[m].target.matches(msobserver.selector)) { |
70 | | - matches.push(mutations[m].target); |
71 | | - } |
72 | | - } else if (mutations[m].type == 'childList') { |
73 | | - |
74 | | - // Otherwise, search for added nodes. |
75 | | - // Search added nodes only for matching selectors. |
76 | | - for (var n = 0; n < mutations[m].addedNodes.length; n++) { |
77 | | - if (!(mutations[m].addedNodes[n] instanceof Element)) continue; |
78 | | - |
79 | | - mutations[m].addedNodes[n].querySelectorAll(msobserver.selector).forEach(add); |
80 | | - if (mutations[m].addedNodes[n].matches(msobserver.selector)) { |
81 | | - matches.push(mutations[m].addedNodes[n]); |
82 | | - } |
83 | | - } |
84 | | - } |
85 | | - } else if (msobserver.options.scanMode == 'exact') { |
86 | | - |
87 | | - // Similar to descendant scan mode, except it will not search within child nodes. |
88 | | - // This offers the most performance. |
89 | | - if (mutations[m].type == 'attributes') { |
90 | | - if (mutations[m].target.matches(msobserver.selector)) |
91 | | - matches.push(mutations[m].target); |
92 | | - } else if (mutations[m].type == 'childList') { |
93 | | - for (var n = 0; n < mutations[m].addedNodes.length; n++) { |
94 | | - if (!(mutations[m].addedNodes[n] instanceof Element)) continue; |
95 | | - if (mutations[m].addedNodes[n].matches(msobserver.selector)) |
96 | | - matches.push(mutations[m].addedNodes[n]); |
97 | | - } |
| 92 | + // If this is an attributes mutation, then the target is the node upon which the mutation occurred. |
| 93 | + if (mutations[m].type == 'attributes') { |
| 94 | + // Check if the mutated node matchs. |
| 95 | + if (mutations[m].target.matches(msobserver.selector)) |
| 96 | + matches.push(mutations[m].target); |
| 97 | + |
| 98 | + // If the selector is fraternal, query siblings of the mutated node for matches. |
| 99 | + if (msobserver.isFraternal) |
| 100 | + mutations[m].target.parentElement.querySelectorAll(msobserver.selector).forEach(push); |
| 101 | + else |
| 102 | + mutations[m].target.querySelectorAll(msobserver.selector).forEach(push); |
| 103 | + } |
| 104 | + |
| 105 | + // If this is an childList mutation, then inspect added nodes. |
| 106 | + if (mutations[m].type == 'childList') { |
| 107 | + |
| 108 | + // Search added nodes for matching selectors. |
| 109 | + for (var n = 0; n < mutations[m].addedNodes.length; n++) { |
| 110 | + if (!(mutations[m].addedNodes[n] instanceof Element)) continue; |
| 111 | + |
| 112 | + // Check if the added node matches the selector |
| 113 | + if (mutations[m].addedNodes[n].matches(msobserver.selector)) |
| 114 | + matches.push(mutations[m].addedNodes[n]); |
| 115 | + |
| 116 | + // If the selector is fraternal, query siblings for matches. |
| 117 | + if (msobserver.isFraternal) |
| 118 | + mutations[m].addedNodes[n].parentElement.querySelectorAll(msobserver.selector).forEach(push); |
| 119 | + else |
| 120 | + mutations[m].addedNodes[n].querySelectorAll(msobserver.selector).forEach(push); |
98 | 121 | } |
99 | | - |
100 | 122 | } |
101 | 123 | } |
102 | 124 |
|
| 125 | + // For each match, call the callback using jQuery.each() to initialize the element (once only.) |
103 | 126 | matches.forEach(function(match) { |
104 | 127 | $(match).each(msobserver.callback); |
105 | 128 | }); |
106 | 129 | }); |
107 | 130 |
|
108 | 131 | // Observe the target element. |
109 | | - observer.observe(options.target, {childList: true, subtree: true, attributes: true}); |
| 132 | + var defaultObeserverOpts = { childList: true, subtree: true, attributes: msobserver.isComplex }; |
| 133 | + observer.observe(options.target, options.observer || defaultObeserverOpts ); |
110 | 134 | }; |
111 | 135 |
|
112 | 136 | // Deprecated API (does not work with jQuery >= 3.1.1): |
|
119 | 143 | msobservers.initialize(selector, callback, $.extend({}, $.initialize.defaults, options)); |
120 | 144 | }; |
121 | 145 |
|
| 146 | + // Options |
122 | 147 | $.initialize.defaults = { |
123 | | - scanMode: 'target', // Can be either: 'target', 'descendants', or 'exact' |
124 | | - target: document.documentElement // Defaults observe the entire document. |
| 148 | + target: document.documentElement, // Defaults to observe the entire document. |
| 149 | + observer: null // MutationObserverInit: Defaults to internal configuration if not provided. |
125 | 150 | } |
126 | 151 |
|
127 | 152 | })(jQuery); |
0 commit comments