-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.js
231 lines (187 loc) · 5.37 KB
/
index.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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
/* global define, Ractive */
void (function (root, factory) {
if (typeof define === 'function' && define.amd) {
define(factory);
} else if (typeof exports === 'object') {
module.exports = factory();
} else {
Ractive.adaptors.Ractive = factory(root.Ractive);
}
}(this, function () {
var Adaptor = {
filter: filter,
wrap: wrap
};
/*
* Advanced options:
* You can adjust these settings via `Ractive.adaptors.Ractive.maxKeyLength`
* and so on. There's usually no need to do that, but it may be good for
* optimizing tests.
*/
Adaptor.fireWrapEvents = true;
Adaptor.maxKeyLength = 2048;
/*
* Check if the child is an Ractive instance.
*
* Also, if this key has been wrapped before, don't rewrap it. (Happens on
* deeply-nested values, and .reset() for some reason.)
*/
function filter (child, keypath, parent) {
if (!isRactiveInstance(child)) return false;
if (parent &&
parent._ractiveWraps &&
parent._ractiveWraps[keypath]) {
return false;
}
return true;
}
/*
* Global write lock.
* This prevents infinite loops from happening where a parent will set a
* value on the child, and the child will attempt to write back to the
* parent, and so on.
*/
var locked = Adaptor.locked = {};
function lock (key, fn) {
if (locked[key]) return;
try {
locked[key] = true;
return fn();
} finally {
delete locked[key];
}
}
/*
* Returns a wrapped Adaptor for Ractive.
* See: http://docs.ractivejs.org/latest/writing-adaptor-plugins
*/
function wrap (parent, child, keypath, prefixer) {
setup();
return {
get: get,
set: set,
reset: reset,
teardown: teardown
};
/*
* Initializes the adaptor.
*/
function setup () {
checkForRecursion();
markAsWrapped();
propagateSubinstances();
child.on('change', onChange);
if (Adaptor.fireWrapEvents) {
child.fire('wrap', parent, keypath);
parent.fire('wrapchild', child, keypath);
}
}
/*
* If the child has its own Ractive instances, recurse upwards. This
* will do `parent.set('child.grandchild', instance)` so that the
* `parent` can listen to the grandchild.
*/
function propagateSubinstances () {
var re = {};
each(child.get(), function (val, key) {
if (isRactiveInstance(val)) re[key] = val;
});
parent.set(prefixer(re));
}
function teardown () {
delete parent._ractiveWraps[keypath];
child.off('change', onChange);
if (Adaptor.fireWrapEvents) {
child.fire('unwrap', parent, keypath);
parent.fire('unwrapchild', child, keypath);
}
}
/*
* Propagate changes from child to parent.
* We well break it apart into key/vals and set those individually because
* some values may be locked.
*/
function onChange (updates) {
each(updates, function (value, key) {
lock(child._guid + key, function () {
parent.set(keypath + '.' + key, value);
});
});
}
/*
* Returns all attributes of the child, including computed properties.
* See: https://github.com/ractivejs/ractive/issues/1250
*/
function get () {
// Optimization: if there are no computed properties, returning all
// non-computed data should suffice.
if (!child.computed) return child.get();
var re = {};
each(child.get(), function (val, key) {
re[key] = val;
});
each(child.computed, function (_, key) {
if (typeof re[key] === 'undefined') {
re[key] = child.get(key);
}
});
return re;
}
function set (key, value) {
lock(child._guid + key, function () {
child.set(key, value);
});
}
/*
* Allow setting values by passing a POJO to .set(), for instance,
* `.set('child', { ... })`. If anything else is passed onto .set()
* (like another Ractive instance, or another adaptor'able), destroy
* this wrapper.
*/
function reset (object) {
if (object && object.constructor === Object) {
child.set(object);
} else {
return false;
}
}
/*
* Die on recursion.
* Keypath will look like 'child.sub.parent.child.sub.parent' ad nauseum.
*/
function checkForRecursion () {
if (keypath && keypath.length > Adaptor.maxKeyLength) {
throw new Error('Keypath too long (possible circular dependency)');
}
}
/*
* Let future wrappers know what we have wrapped Ractive instances.
* This value is used on `filter()`.
*/
function markAsWrapped () {
if (!parent._ractiveWraps) parent._ractiveWraps = {};
parent._ractiveWraps[keypath] = child;
}
}
/*
* Cross-browser forEach helper.
*/
function each (obj, fn) {
for (var key in obj) {
if (obj.hasOwnProperty(key)) fn(obj[key], key);
}
}
/*
* Check if an `obj instanceof Ractive`. This check will not require a
* reference to the root Ractive instance.
*/
function isRactiveInstance (obj) {
return obj && obj.constructor &&
typeof obj._guid === 'string' &&
typeof obj.set === 'function' &&
typeof obj.off === 'function' &&
typeof obj.on === 'function' &&
typeof obj.constructor.defaults === 'object';
}
return Adaptor;
}));