forked from meteor/meteor
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdombackend.js
179 lines (149 loc) · 5.29 KB
/
dombackend.js
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
var DOMBackend = {};
Blaze._DOMBackend = DOMBackend;
var $jq = (typeof jQuery !== 'undefined' ? jQuery :
(typeof Package !== 'undefined' ?
Package.jquery && Package.jquery.jQuery : null));
if (! $jq)
throw new Error("jQuery not found");
DOMBackend._$jq = $jq;
DOMBackend.parseHTML = function (html) {
// Return an array of nodes.
//
// jQuery does fancy stuff like creating an appropriate
// container element and setting innerHTML on it, as well
// as working around various IE quirks.
return $jq.parseHTML(html) || [];
};
DOMBackend.Events = {
// `selector` is non-null. `type` is one type (but
// may be in backend-specific form, e.g. have namespaces).
// Order fired must be order bound.
delegateEvents: function (elem, type, selector, handler) {
$jq(elem).on(type, selector, handler);
},
undelegateEvents: function (elem, type, handler) {
$jq(elem).off(type, '**', handler);
},
bindEventCapturer: function (elem, type, selector, handler) {
var $elem = $jq(elem);
var wrapper = function (event) {
event = $jq.event.fix(event);
event.currentTarget = event.target;
// Note: It might improve jQuery interop if we called into jQuery
// here somehow. Since we don't use jQuery to dispatch the event,
// we don't fire any of jQuery's event hooks or anything. However,
// since jQuery can't bind capturing handlers, it's not clear
// where we would hook in. Internal jQuery functions like `dispatch`
// are too high-level.
var $target = $jq(event.currentTarget);
if ($target.is($elem.find(selector)))
handler.call(elem, event);
};
handler._meteorui_wrapper = wrapper;
type = DOMBackend.Events.parseEventType(type);
// add *capturing* event listener
elem.addEventListener(type, wrapper, true);
},
unbindEventCapturer: function (elem, type, handler) {
type = DOMBackend.Events.parseEventType(type);
elem.removeEventListener(type, handler._meteorui_wrapper, true);
},
parseEventType: function (type) {
// strip off namespaces
var dotLoc = type.indexOf('.');
if (dotLoc >= 0)
return type.slice(0, dotLoc);
return type;
}
};
///// Removal detection and interoperability.
// For an explanation of this technique, see:
// http://bugs.jquery.com/ticket/12213#comment:23 .
//
// In short, an element is considered "removed" when jQuery
// cleans up its *private* userdata on the element,
// which we can detect using a custom event with a teardown
// hook.
var NOOP = function () {};
// Circular doubly-linked list
var TeardownCallback = function (func) {
this.next = this;
this.prev = this;
this.func = func;
};
// Insert newElt before oldElt in the circular list
TeardownCallback.prototype.linkBefore = function(oldElt) {
this.prev = oldElt.prev;
this.next = oldElt;
oldElt.prev.next = this;
oldElt.prev = this;
};
TeardownCallback.prototype.unlink = function () {
this.prev.next = this.next;
this.next.prev = this.prev;
};
TeardownCallback.prototype.go = function () {
var func = this.func;
func && func();
};
TeardownCallback.prototype.stop = TeardownCallback.prototype.unlink;
DOMBackend.Teardown = {
_JQUERY_EVENT_NAME: 'blaze_teardown_watcher',
_CB_PROP: '$blaze_teardown_callbacks',
// Registers a callback function to be called when the given element or
// one of its ancestors is removed from the DOM via the backend library.
// The callback function is called at most once, and it receives the element
// in question as an argument.
onElementTeardown: function (elem, func) {
var elt = new TeardownCallback(func);
var propName = DOMBackend.Teardown._CB_PROP;
if (! elem[propName]) {
// create an empty node that is never unlinked
elem[propName] = new TeardownCallback;
// Set up the event, only the first time.
$jq(elem).on(DOMBackend.Teardown._JQUERY_EVENT_NAME, NOOP);
}
elt.linkBefore(elem[propName]);
return elt; // so caller can call stop()
},
// Recursively call all teardown hooks, in the backend and registered
// through DOMBackend.onElementTeardown.
tearDownElement: function (elem) {
var elems = [];
// Array.prototype.slice.call doesn't work when given a NodeList in
// IE8 ("JScript object expected").
var nodeList = elem.getElementsByTagName('*');
for (var i = 0; i < nodeList.length; i++) {
elems.push(nodeList[i]);
}
elems.push(elem);
$jq.cleanData(elems);
}
};
$jq.event.special[DOMBackend.Teardown._JQUERY_EVENT_NAME] = {
setup: function () {
// This "setup" callback is important even though it is empty!
// Without it, jQuery will call addEventListener, which is a
// performance hit, especially with Chrome's async stack trace
// feature enabled.
},
teardown: function() {
var elem = this;
var callbacks = elem[DOMBackend.Teardown._CB_PROP];
if (callbacks) {
var elt = callbacks.next;
while (elt !== callbacks) {
elt.go();
elt = elt.next;
}
callbacks.go();
elem[DOMBackend.Teardown._CB_PROP] = null;
}
}
};
// Must use jQuery semantics for `context`, not
// querySelectorAll's. In other words, all the parts
// of `selector` must be found under `context`.
DOMBackend.findBySelector = function (selector, context) {
return $jq(selector, context);
};