wallace 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/dist/wallace.js +811 -0
- package/package.json +36 -0
- package/src/component.js +346 -0
- package/src/helpers.js +16 -0
- package/src/index.js +18 -0
- package/src/lookup.js +36 -0
- package/src/mountie.js +23 -0
- package/src/pool.js +173 -0
- package/src/utils.js +64 -0
- package/src/wrapper.js +152 -0
package/dist/wallace.js
ADDED
|
@@ -0,0 +1,811 @@
|
|
|
1
|
+
const doc = document;
|
|
2
|
+
const throwAway = doc.createElement('template');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Create an element from html string
|
|
6
|
+
*/
|
|
7
|
+
function makeEl(html) {
|
|
8
|
+
throwAway.innerHTML = html;
|
|
9
|
+
return throwAway.content.firstChild
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Some utility functions
|
|
14
|
+
*/
|
|
15
|
+
const und = x => x === undefined;
|
|
16
|
+
const isStr = x => typeof x === 'string';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A wrapper around a DOM element.
|
|
20
|
+
* All transformative methods return this (except transitions as they return promises)
|
|
21
|
+
* This means those methods can be chained.
|
|
22
|
+
*/
|
|
23
|
+
function Wrapper(element) {
|
|
24
|
+
this.e = element;
|
|
25
|
+
this._pool = undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
Wrapper.prototype = {
|
|
29
|
+
/**
|
|
30
|
+
* Get element as 'e' from item, else return text node.
|
|
31
|
+
*/
|
|
32
|
+
__ge: function(item) {
|
|
33
|
+
return item.e || doc.createTextNode(item)
|
|
34
|
+
},
|
|
35
|
+
/**
|
|
36
|
+
* Gets an attribute from the element. Cannot be chained.
|
|
37
|
+
*/
|
|
38
|
+
getAtt: function(name) {
|
|
39
|
+
return this.e[name]
|
|
40
|
+
},
|
|
41
|
+
/**
|
|
42
|
+
* Gets the element's value. Cannot be chained.
|
|
43
|
+
*/
|
|
44
|
+
getValue: function() {
|
|
45
|
+
return this.e.value
|
|
46
|
+
},
|
|
47
|
+
isChecked: function() {
|
|
48
|
+
return this.e.checked
|
|
49
|
+
},
|
|
50
|
+
/* Every method below must return 'this' so it can be chained */
|
|
51
|
+
append: function(item) {
|
|
52
|
+
this.e.appendChild(this.__ge(item));
|
|
53
|
+
return this
|
|
54
|
+
},
|
|
55
|
+
att: function(name, value) {
|
|
56
|
+
this.e.setAttribute(name, value);
|
|
57
|
+
return this
|
|
58
|
+
},
|
|
59
|
+
atts: function(atts) {
|
|
60
|
+
for (let name in atts) {
|
|
61
|
+
this.att(name, atts[name]);
|
|
62
|
+
}
|
|
63
|
+
return this
|
|
64
|
+
},
|
|
65
|
+
pool: function(pool) {
|
|
66
|
+
this._pool = pool;
|
|
67
|
+
return this
|
|
68
|
+
},
|
|
69
|
+
clear: function() {
|
|
70
|
+
this.e.innerHTML = '';
|
|
71
|
+
this.e.textContent = '';
|
|
72
|
+
this.e.value = '';
|
|
73
|
+
return this
|
|
74
|
+
},
|
|
75
|
+
checked: function(value) {
|
|
76
|
+
this.e.checked = !!value;
|
|
77
|
+
return this
|
|
78
|
+
},
|
|
79
|
+
child: function(wrapper) {
|
|
80
|
+
this.e.innerHTML = '';
|
|
81
|
+
this.e.appendChild(wrapper.e);
|
|
82
|
+
return this
|
|
83
|
+
},
|
|
84
|
+
css: function(style) {
|
|
85
|
+
this.e.className = style;
|
|
86
|
+
return this
|
|
87
|
+
},
|
|
88
|
+
cssAdd: function(style) {
|
|
89
|
+
this.e.classList.add(style);
|
|
90
|
+
return this
|
|
91
|
+
},
|
|
92
|
+
cssRemove: function(style) {
|
|
93
|
+
this.e.classList.remove(style);
|
|
94
|
+
return this
|
|
95
|
+
},
|
|
96
|
+
cssToggle: function(style) {
|
|
97
|
+
this.e.classList.toggle(style);
|
|
98
|
+
return this
|
|
99
|
+
},
|
|
100
|
+
disabled: function(disabled) {
|
|
101
|
+
this.e.disabled = disabled;
|
|
102
|
+
return this
|
|
103
|
+
},
|
|
104
|
+
href: function(value) {
|
|
105
|
+
return this.att('href', value)
|
|
106
|
+
},
|
|
107
|
+
html: function(html) {
|
|
108
|
+
this.e.innerHTML = html;
|
|
109
|
+
return this
|
|
110
|
+
},
|
|
111
|
+
id: function(value) {
|
|
112
|
+
return this.att('id', value)
|
|
113
|
+
},
|
|
114
|
+
/*
|
|
115
|
+
* Set inner as individual item or array. Not optimised.
|
|
116
|
+
*/
|
|
117
|
+
inner: function(items) {
|
|
118
|
+
if (!Array.isArray(items)) {
|
|
119
|
+
items = [items];
|
|
120
|
+
}
|
|
121
|
+
const e = this.e;
|
|
122
|
+
e.innerHTML = '';
|
|
123
|
+
for (var i=0, il=items.length; i<il; i++) {
|
|
124
|
+
e.appendChild(this.__ge(items[i]));
|
|
125
|
+
}
|
|
126
|
+
return this
|
|
127
|
+
},
|
|
128
|
+
/*
|
|
129
|
+
* Set items from pool.
|
|
130
|
+
*/
|
|
131
|
+
items: function(items, parent) {
|
|
132
|
+
this._pool.patch(this.e, items, parent);
|
|
133
|
+
return this
|
|
134
|
+
},
|
|
135
|
+
on: function(event, callback) {
|
|
136
|
+
this.e.addEventListener(event, e => callback(this, e));
|
|
137
|
+
return this
|
|
138
|
+
},
|
|
139
|
+
replace: function(el) {
|
|
140
|
+
this.e.parentNode.replaceChild(el, this.e);
|
|
141
|
+
return this
|
|
142
|
+
},
|
|
143
|
+
src: function(value) {
|
|
144
|
+
return this.att('src', value)
|
|
145
|
+
},
|
|
146
|
+
style: function(name, value) {
|
|
147
|
+
this.e.style[name] = value;
|
|
148
|
+
return this
|
|
149
|
+
},
|
|
150
|
+
swap: function(key, parent) {
|
|
151
|
+
this.child(this._pool.getOne(key, parent));
|
|
152
|
+
return this
|
|
153
|
+
},
|
|
154
|
+
text: function(value) {
|
|
155
|
+
this.e.textContent = value;
|
|
156
|
+
return this
|
|
157
|
+
},
|
|
158
|
+
visible: function(visible) {
|
|
159
|
+
this.e.classList.toggle('hidden', !visible);
|
|
160
|
+
return this
|
|
161
|
+
},
|
|
162
|
+
value: function(value) {
|
|
163
|
+
this.e.value = value;
|
|
164
|
+
return this
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Creates and mounts a component onto an element.
|
|
170
|
+
*
|
|
171
|
+
* @param {unsure} elementOrId Either a string representing an id, or an element.
|
|
172
|
+
* @param {class} cls The class of Component to create
|
|
173
|
+
* @param {object} props The props to pass to the component (optional)
|
|
174
|
+
* @param {object} parent The parent component (optional)
|
|
175
|
+
*/
|
|
176
|
+
function mount(elementOrId, cls, props, parent) {
|
|
177
|
+
const component = createComponent(cls, parent, props);
|
|
178
|
+
const nodeToReplace = getElement(elementOrId);
|
|
179
|
+
nodeToReplace.parentNode.replaceChild(component.e, nodeToReplace);
|
|
180
|
+
return component
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* returns a Wrapper around an element.
|
|
185
|
+
*
|
|
186
|
+
* @param {unsure} elementOrId Either a string representing an id, or an element.
|
|
187
|
+
*/
|
|
188
|
+
function wrap(elementOrId) {
|
|
189
|
+
return new Wrapper(getElement(elementOrId))
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
function getElement(elementOrId) {
|
|
194
|
+
return isStr(elementOrId) ? document.getElementById(elementOrId) : elementOrId
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Creates a component and initialises it.
|
|
200
|
+
*
|
|
201
|
+
* @param {class} cls The class of Component to create
|
|
202
|
+
* @param {object} parent The parent component (optional)
|
|
203
|
+
* @param {object} props The props to pass to the component (optional)
|
|
204
|
+
*/
|
|
205
|
+
function createComponent(cls, parent, props) {
|
|
206
|
+
const component = buildComponent(cls, parent);
|
|
207
|
+
component.props = props;
|
|
208
|
+
component.init();
|
|
209
|
+
component.update();
|
|
210
|
+
return component
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Builds a component.
|
|
215
|
+
*/
|
|
216
|
+
function buildComponent(cls, parent) {
|
|
217
|
+
const component = new cls(parent);
|
|
218
|
+
component.__bv(component, cls.prototype);
|
|
219
|
+
return component
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Creates a wrapper of type tag e.g. h('div')
|
|
225
|
+
*/
|
|
226
|
+
function h(tag) {
|
|
227
|
+
return new Wrapper(doc.createElement(tag))
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Pools same type components, retrieving by sequence.
|
|
232
|
+
* Must not be shared.
|
|
233
|
+
*
|
|
234
|
+
* @param {class} componentClass - The class of Component to create.
|
|
235
|
+
* @param {function} keyFn - A function which obtains the key to pool by
|
|
236
|
+
*/
|
|
237
|
+
function KeyedPool(componentClass, keyFn) {
|
|
238
|
+
this._v = componentClass;
|
|
239
|
+
this._f = keyFn;
|
|
240
|
+
this._k = []; // keys
|
|
241
|
+
this._p = {}; // pool of component instances
|
|
242
|
+
}
|
|
243
|
+
const proto$1 = KeyedPool.prototype;
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Retrieves a single component. Though not used in Wallace itself, it may
|
|
247
|
+
* be used elsewhere, such as in the router.
|
|
248
|
+
*
|
|
249
|
+
* @param {Object} item - The item which will be passed as props.
|
|
250
|
+
* @param {Component} parent - The parent component.
|
|
251
|
+
*/
|
|
252
|
+
proto$1.getOne = function(item, parent) {
|
|
253
|
+
return this._get(this._p, this._v, this._f(item), item, parent)
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Updates the element's childNodes to match the items.
|
|
258
|
+
* Performance is important.
|
|
259
|
+
*
|
|
260
|
+
* @param {DOMElement} e - The DOM element to patch.
|
|
261
|
+
* @param {Array} items - Array of items which will be passed as props.
|
|
262
|
+
* @param {Component} parent - The parent component.
|
|
263
|
+
*/
|
|
264
|
+
proto$1.patch = function(e, items, parent) {
|
|
265
|
+
const pool = this._p;
|
|
266
|
+
const componentClass = this._v;
|
|
267
|
+
const keyFn = this._f;
|
|
268
|
+
const childNodes = e.childNodes;
|
|
269
|
+
const itemsLength = items.length;
|
|
270
|
+
const oldKeySequence = this._k;
|
|
271
|
+
const newKeys = [];
|
|
272
|
+
let item, key, component, childElementCount = oldKeySequence.length + 1;
|
|
273
|
+
for (let i=0; i<itemsLength; i++) {
|
|
274
|
+
item = items[i];
|
|
275
|
+
key = keyFn(item);
|
|
276
|
+
component = this._get(pool, componentClass, key, item, parent);
|
|
277
|
+
newKeys.push(key);
|
|
278
|
+
if (i > childElementCount) {
|
|
279
|
+
e.appendChild(component.e);
|
|
280
|
+
} else if (key !== oldKeySequence[i]) {
|
|
281
|
+
e.insertBefore(component.e, childNodes[i]);
|
|
282
|
+
pull(oldKeySequence, key, i);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
this._k = newKeys;
|
|
286
|
+
trimChildren(e, childNodes, itemsLength);
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
// Internal
|
|
290
|
+
proto$1._get = function(pool, componentClass, key, item, parent) {
|
|
291
|
+
let component;
|
|
292
|
+
if (pool.hasOwnProperty(key)) {
|
|
293
|
+
component = pool[key];
|
|
294
|
+
component.setProps(item);
|
|
295
|
+
} else {
|
|
296
|
+
component = createComponent(componentClass, parent, item);
|
|
297
|
+
pool[key] = component;
|
|
298
|
+
}
|
|
299
|
+
return component
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Pools same type components, retrieving by sequence.
|
|
304
|
+
* Must not be shared.
|
|
305
|
+
*
|
|
306
|
+
* @param {class} componentClass - The class of Component to create.
|
|
307
|
+
*/
|
|
308
|
+
function SequentialPool(componentClass) {
|
|
309
|
+
this._v = componentClass;
|
|
310
|
+
this._p = []; // pool of component instances
|
|
311
|
+
this._c = 0; // Child element count
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Updates the element's childNodes to match the items.
|
|
316
|
+
* Performance is important.
|
|
317
|
+
*
|
|
318
|
+
* @param {DOMElement} e - The DOM element to patch.
|
|
319
|
+
* @param {Array} items - Array of items which will be passed as props.
|
|
320
|
+
* @param {Component} parent - The parent component.
|
|
321
|
+
*/
|
|
322
|
+
SequentialPool.prototype.patch = function(e, items, parent) {
|
|
323
|
+
const pool = this._p;
|
|
324
|
+
const componentClass = this._v;
|
|
325
|
+
const childNodes = e.childNodes;
|
|
326
|
+
const itemsLength = items.length;
|
|
327
|
+
let item, component, poolCount = pool.length, childElementCount = this._c;
|
|
328
|
+
|
|
329
|
+
for (let i=0; i<itemsLength; i++) {
|
|
330
|
+
item = items[i];
|
|
331
|
+
if (i < poolCount) {
|
|
332
|
+
component = pool[i];
|
|
333
|
+
component.setProps(item);
|
|
334
|
+
} else {
|
|
335
|
+
component = createComponent(componentClass, parent, item);
|
|
336
|
+
pool.push(component);
|
|
337
|
+
poolCount ++;
|
|
338
|
+
}
|
|
339
|
+
if (i >= childElementCount) {
|
|
340
|
+
e.appendChild(component.e);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
this._c = itemsLength;
|
|
344
|
+
trimChildren(e, childNodes, itemsLength);
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* An object which creates and pools components according to the mappings provided.
|
|
349
|
+
* If there is no match in the mappings, the fallback function is called.
|
|
350
|
+
*
|
|
351
|
+
* Note that the fallback must return an instance (of Component or Wrapper) whereas
|
|
352
|
+
* mappings must specify component classes.
|
|
353
|
+
*
|
|
354
|
+
* You can rely solely on the fallback if you like.
|
|
355
|
+
*
|
|
356
|
+
* @param {Object} mappings - a mapping of format key->componentClass
|
|
357
|
+
* @param {function} fallback - a function to call when no key is provided.
|
|
358
|
+
*
|
|
359
|
+
*/
|
|
360
|
+
function InstancePool(mappings, fallback) {
|
|
361
|
+
this._m = mappings;
|
|
362
|
+
this._f = fallback;
|
|
363
|
+
this._i = {}; // Instances
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
InstancePool.prototype.getOne = function(key, parentComponent) {
|
|
367
|
+
if (!this._i.hasOwnProperty(key)) {
|
|
368
|
+
this._i[key] = this._m.hasOwnProperty(key) ?
|
|
369
|
+
parentComponent.nest(this._m[key]) : this._f(key, parentComponent);
|
|
370
|
+
}
|
|
371
|
+
return this._i[key]
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Trims the unwanted child elements from the end.
|
|
376
|
+
*
|
|
377
|
+
* @param {Node} e
|
|
378
|
+
* @param {Array} childNodes
|
|
379
|
+
* @param {Int} itemsLength
|
|
380
|
+
*/
|
|
381
|
+
function trimChildren(e, childNodes, itemsLength) {
|
|
382
|
+
let lastIndex = childNodes.length - 1;
|
|
383
|
+
let keepIndex = itemsLength - 1;
|
|
384
|
+
for (let i=lastIndex; i>keepIndex; i--) {
|
|
385
|
+
e.removeChild(childNodes[i]);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Pulls an item forward in an array, to replicate insertBefore.
|
|
391
|
+
* @param {Array} arr
|
|
392
|
+
* @param {any} item
|
|
393
|
+
* @param {Int} to
|
|
394
|
+
*/
|
|
395
|
+
function pull(arr, item, to) {
|
|
396
|
+
const position = arr.indexOf(item);
|
|
397
|
+
if (position != to) {
|
|
398
|
+
arr.splice(to, 0, arr.splice(position, 1)[0]);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Wallace's crude way of tracking mounting and unmounting.
|
|
404
|
+
*/
|
|
405
|
+
|
|
406
|
+
const trackedComponents = [];
|
|
407
|
+
|
|
408
|
+
var mountie = {
|
|
409
|
+
track: function (component) {
|
|
410
|
+
trackedComponents.push({component: component, isAttached: component.__ia()});
|
|
411
|
+
},
|
|
412
|
+
flush: function () {
|
|
413
|
+
for (let i=0, il=trackedComponents.length; i<il; i++) {
|
|
414
|
+
let trackedComponent = trackedComponents[i];
|
|
415
|
+
let component = trackedComponent.component;
|
|
416
|
+
let attachedNow = component.__ia();
|
|
417
|
+
if (attachedNow !== trackedComponent.isAttached) {
|
|
418
|
+
let fn = attachedNow ? component.mount : component.unmount;
|
|
419
|
+
fn.apply(component);
|
|
420
|
+
trackedComponent.isAttached = attachedNow;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Used internally.
|
|
428
|
+
* An object which pools the results of lookup queries so we don't have to
|
|
429
|
+
* repeat them in the same component.
|
|
430
|
+
* The Lookup instance will be shared between instances of a component.
|
|
431
|
+
* Must call reset() on every update.
|
|
432
|
+
*/
|
|
433
|
+
function Lookup(callbacks) {
|
|
434
|
+
this.callbacks = callbacks;
|
|
435
|
+
this.run = {};
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
Lookup.prototype = {
|
|
439
|
+
get: function(component, key) {
|
|
440
|
+
const run = this.run;
|
|
441
|
+
if (run[key] === undefined) {
|
|
442
|
+
// Verbose but efficient way as it avoids lookups?
|
|
443
|
+
// Or is this harmful to performance because we're just reading values more than calling functions?
|
|
444
|
+
let o = component.__ov[key];
|
|
445
|
+
o = und(o) ? '' : o;
|
|
446
|
+
const n = this.callbacks[key](component, component.props);
|
|
447
|
+
const c = n !== o;
|
|
448
|
+
component.__ov[key] = n;
|
|
449
|
+
const rtn = {n, o, c};
|
|
450
|
+
run[key] = rtn;
|
|
451
|
+
return rtn
|
|
452
|
+
}
|
|
453
|
+
return run[key]
|
|
454
|
+
},
|
|
455
|
+
reset: function() {
|
|
456
|
+
this.run = {};
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
const noop = function() {};
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Represents a component.
|
|
464
|
+
*/
|
|
465
|
+
function Component(parent) {
|
|
466
|
+
const s = this;
|
|
467
|
+
s.parent = parent; // The parent component
|
|
468
|
+
s.props = undefined; // The props passed to the component. May be changed.
|
|
469
|
+
|
|
470
|
+
// These will be set during build
|
|
471
|
+
s.e = null; // the element
|
|
472
|
+
s.el = null; // the named wrappers
|
|
473
|
+
|
|
474
|
+
// Internal state objects
|
|
475
|
+
s.__nv = []; // Nested components
|
|
476
|
+
s.__ov = {}; // The old values for watches to compare against
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
var proto = Component.prototype;
|
|
481
|
+
|
|
482
|
+
proto.onUpdate = noop;
|
|
483
|
+
proto.afterUpdate = noop;
|
|
484
|
+
proto.onInit = noop;
|
|
485
|
+
proto.afterInit = noop;
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Gets called once immediately after building.
|
|
489
|
+
* Sets initial props extracted from __html__.
|
|
490
|
+
* Note there is an issue here, in that we rely on there being initial props to call init
|
|
491
|
+
* on nested components.
|
|
492
|
+
*/
|
|
493
|
+
proto.init = function() {
|
|
494
|
+
this.onInit();
|
|
495
|
+
for (let key in this.__ip) {
|
|
496
|
+
let nestedComponent = this.el[key];
|
|
497
|
+
let callback = this.__ip[key];
|
|
498
|
+
if (callback) {
|
|
499
|
+
nestedComponent.props = callback(this, this.props);
|
|
500
|
+
}
|
|
501
|
+
nestedComponent.init();
|
|
502
|
+
}
|
|
503
|
+
this.afterInit();
|
|
504
|
+
};
|
|
505
|
+
/**
|
|
506
|
+
* Calls a function somewhere up the parent tree.
|
|
507
|
+
*/
|
|
508
|
+
proto.bubble = function(name) {
|
|
509
|
+
let target = this.parent;
|
|
510
|
+
while (!und(target)) {
|
|
511
|
+
if (target[name]) {
|
|
512
|
+
// We don't really care about performance here, so accessing arguments is fine.
|
|
513
|
+
return target[name].apply(target, Array.prototype.slice.call(arguments, 1))
|
|
514
|
+
}
|
|
515
|
+
target = target.parent;
|
|
516
|
+
}
|
|
517
|
+
throw 'Bubble popped.'
|
|
518
|
+
};
|
|
519
|
+
/**
|
|
520
|
+
* Move the component to new parent. Necessary if sharing a pool.
|
|
521
|
+
*/
|
|
522
|
+
proto.move = function(newParent) {
|
|
523
|
+
if (this.parent && this.parent.__nv) {
|
|
524
|
+
const nv = this.parent.__nv;
|
|
525
|
+
nv.splice(nv.indexOf(this), 1);
|
|
526
|
+
}
|
|
527
|
+
this.parent = newParent;
|
|
528
|
+
};
|
|
529
|
+
/**
|
|
530
|
+
* Builds a nested component of the specified class. Its up to you how you use it.
|
|
531
|
+
*/
|
|
532
|
+
proto.nest = function(cls, props) {
|
|
533
|
+
const child = createComponent(cls, this, props || this.props);
|
|
534
|
+
this.__nv.push(child);
|
|
535
|
+
return child
|
|
536
|
+
};
|
|
537
|
+
/**
|
|
538
|
+
* Lookup a watched value during update. Returns an object with {o, n, c}
|
|
539
|
+
* (oldValue, newValue, changed).
|
|
540
|
+
* You must call this.resetLookups before calling this during an update.
|
|
541
|
+
* The point is to pool the result so it doesn't have to be repeated.
|
|
542
|
+
*/
|
|
543
|
+
proto.lookup = function(query) {
|
|
544
|
+
return this.__qc.get(this, query)
|
|
545
|
+
};
|
|
546
|
+
/**
|
|
547
|
+
* Resets the lookups, must be called before calling this.lookup() during an update.
|
|
548
|
+
*/
|
|
549
|
+
proto.resetLookups = function() {
|
|
550
|
+
this.__qc.reset();
|
|
551
|
+
};
|
|
552
|
+
/**
|
|
553
|
+
* Sets the props and updates the component.
|
|
554
|
+
*/
|
|
555
|
+
proto.setProps = function(props) {
|
|
556
|
+
this.props = props;
|
|
557
|
+
this.update();
|
|
558
|
+
return this
|
|
559
|
+
};
|
|
560
|
+
/**
|
|
561
|
+
* Call this if you want to get mount() and unmount() callbacks.
|
|
562
|
+
*/
|
|
563
|
+
proto.trackMounting = function() {
|
|
564
|
+
this.__mt.track(this);
|
|
565
|
+
};
|
|
566
|
+
/**
|
|
567
|
+
* Updates the component.
|
|
568
|
+
*/
|
|
569
|
+
proto.update = function() {
|
|
570
|
+
this.onUpdate();
|
|
571
|
+
this.resetLookups();
|
|
572
|
+
this.updateSelf();
|
|
573
|
+
this.updateNested();
|
|
574
|
+
this.afterUpdate();
|
|
575
|
+
};
|
|
576
|
+
/**
|
|
577
|
+
* Loops over watches skipping shielded watches if elements are hidden.
|
|
578
|
+
*/
|
|
579
|
+
proto.updateSelf = function() {
|
|
580
|
+
let i = 0, watch, wrapper, shieldCount, shieldQuery, shieldQueryResult, shouldBeVisible;
|
|
581
|
+
const watches = this.__wc;
|
|
582
|
+
const il = watches.length;
|
|
583
|
+
while (i < il) {
|
|
584
|
+
watch = watches[i];
|
|
585
|
+
wrapper = this.el[watch.wk];
|
|
586
|
+
shieldQuery = watch.sq;
|
|
587
|
+
i ++;
|
|
588
|
+
shouldBeVisible = true;
|
|
589
|
+
if (shieldQuery) {
|
|
590
|
+
// Get the newValue for shieldQuery using lookup
|
|
591
|
+
shieldQueryResult = this.lookup(shieldQuery).n;
|
|
592
|
+
|
|
593
|
+
// Determine if shouldBeVisible based on reverseShield
|
|
594
|
+
// i.e. whether "shieldQuery===true" means show or hide.
|
|
595
|
+
shouldBeVisible = watch.rv ? shieldQueryResult : !shieldQueryResult;
|
|
596
|
+
|
|
597
|
+
// The number of watches to skip if this element is not visible
|
|
598
|
+
shieldCount = shouldBeVisible ? 0 : watch.sc;
|
|
599
|
+
|
|
600
|
+
// Set the element visibility
|
|
601
|
+
wrapper.visible(shouldBeVisible);
|
|
602
|
+
i += shieldCount;
|
|
603
|
+
}
|
|
604
|
+
if (shouldBeVisible) {
|
|
605
|
+
applyWatchCallbacks(this, wrapper, watch.cb);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
/**
|
|
610
|
+
* Update nested components (but not repeat elements).
|
|
611
|
+
*/
|
|
612
|
+
proto.updateNested = function() {
|
|
613
|
+
// These are user created by calling nest()
|
|
614
|
+
const items = this.__nv;
|
|
615
|
+
for (let i=0, il=items.length; i<il; i++) {
|
|
616
|
+
let child = items[i];
|
|
617
|
+
if (child.__ia()) {
|
|
618
|
+
child.update();
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
// These are created with directives, and whose props arguments may need reprocessed.
|
|
622
|
+
for (let key in this.__ip) {
|
|
623
|
+
let callback = this.__ip[key];
|
|
624
|
+
let nestedComponent = this.el[key];
|
|
625
|
+
if (callback) {
|
|
626
|
+
nestedComponent.setProps(callback(this, this.props));
|
|
627
|
+
} else {
|
|
628
|
+
nestedComponent.update();
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
/**
|
|
633
|
+
* Calls the callback if the value has changed (
|
|
634
|
+
*/
|
|
635
|
+
// changed(name, callback) {
|
|
636
|
+
// const n = this.__ov[name]
|
|
637
|
+
// const o = this.props[name]
|
|
638
|
+
// if (n !== o) {
|
|
639
|
+
// callback(n, o)
|
|
640
|
+
// }
|
|
641
|
+
// }
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Creates a watch.
|
|
646
|
+
*/
|
|
647
|
+
proto.__wa = function(wrapperKey, shieldQuery, reverseShield, shieldCount, callbacks) {
|
|
648
|
+
return {
|
|
649
|
+
wk: wrapperKey, // The key of the corresponding wrapper.
|
|
650
|
+
sq: shieldQuery, // The shield query key
|
|
651
|
+
rv: reverseShield, // whether shieldQuery should be flipped
|
|
652
|
+
sc: shieldCount, // The number of items to shield
|
|
653
|
+
cb: callbacks // The callbacks - object
|
|
654
|
+
}
|
|
655
|
+
};
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
const applyWatchCallbacks = (component, wrapper, callbacks) => {
|
|
659
|
+
|
|
660
|
+
for (let key in callbacks) {
|
|
661
|
+
let callback = callbacks[key];
|
|
662
|
+
if (key === '*') {
|
|
663
|
+
callback.call(component, wrapper, component.props, component);
|
|
664
|
+
} else {
|
|
665
|
+
// means: {new, old, changed}
|
|
666
|
+
const {n, o, c} = component.lookup(key);
|
|
667
|
+
if (c) {
|
|
668
|
+
callback.call(component, n, o, wrapper, component.props, component);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* The global mount tracker.
|
|
677
|
+
*/
|
|
678
|
+
proto.__mt = mountie;
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* Nest Internal. For building a nested component declared in the html.
|
|
682
|
+
*/
|
|
683
|
+
proto.__ni = function(path, cls) {
|
|
684
|
+
const child = buildComponent(cls, this);
|
|
685
|
+
this.__gw(path).replace(child.e);
|
|
686
|
+
return child
|
|
687
|
+
};
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
*
|
|
691
|
+
* @param {function} baseClass - the base class to extend from
|
|
692
|
+
* @param {object} [prototypeExtras] - an object with extra things to be added to the prototype
|
|
693
|
+
* @param {function} [prototypeExtras] - the function to be used as constructor
|
|
694
|
+
*/
|
|
695
|
+
Component.prototype.__ex = function(baseClass, prototypeExtras, constructorFunction) {
|
|
696
|
+
var subClass = constructorFunction || function(parent) {
|
|
697
|
+
baseClass.call(this, parent);
|
|
698
|
+
};
|
|
699
|
+
subClass.prototype = Object.create(baseClass && baseClass.prototype, {
|
|
700
|
+
constructor: { value: subClass, writable: true, configurable: true }
|
|
701
|
+
});
|
|
702
|
+
if (prototypeExtras) {
|
|
703
|
+
Object.assign(subClass.prototype, prototypeExtras);
|
|
704
|
+
}
|
|
705
|
+
return subClass
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* Create a component pool.
|
|
711
|
+
*/
|
|
712
|
+
proto.pool = function(cls, keyFn) {
|
|
713
|
+
return keyFn ? new KeyedPool(cls, keyFn) : new SequentialPool(cls)
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
/**
|
|
717
|
+
* Create an instance pool, for switches.
|
|
718
|
+
*/
|
|
719
|
+
proto.__ic = function(mappings, fallback) {
|
|
720
|
+
return new InstancePool(mappings, fallback)
|
|
721
|
+
};
|
|
722
|
+
|
|
723
|
+
/**
|
|
724
|
+
* Build the DOM. We pass prototype as local var for compactness.
|
|
725
|
+
*/
|
|
726
|
+
proto.__bd = function(prototype) {
|
|
727
|
+
if (prototype.__cn === undefined) {
|
|
728
|
+
prototype.__cn = makeEl(prototype.__ht);
|
|
729
|
+
}
|
|
730
|
+
this.e = prototype.__cn.cloneNode(true);
|
|
731
|
+
};
|
|
732
|
+
|
|
733
|
+
// proto.__bd = function(prototype, clone) {
|
|
734
|
+
// if (clone && !prototype.__cn) {
|
|
735
|
+
// prototype.__cn = makeEl(prototype.__ht)
|
|
736
|
+
// }
|
|
737
|
+
// this.e = clone ? prototype.__cn.cloneNode(true) : makeEl(prototype.__ht)
|
|
738
|
+
// }
|
|
739
|
+
|
|
740
|
+
// proto.__bd = function(prototype) {
|
|
741
|
+
// this.e = makeEl(prototype.__ht)
|
|
742
|
+
// }
|
|
743
|
+
|
|
744
|
+
/**
|
|
745
|
+
* Returns a regular wrapper around element at path, where path is an array of indices.
|
|
746
|
+
* This is used by the babel plugin.
|
|
747
|
+
*/
|
|
748
|
+
proto.__gw = function(path) {
|
|
749
|
+
return new Wrapper(this.__fe(path))
|
|
750
|
+
};
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* Finds an element at specified path, where path is an array of indices.
|
|
754
|
+
* This is used by the babel plugin.
|
|
755
|
+
*/
|
|
756
|
+
proto.__fe = function(path) {
|
|
757
|
+
return path.reduce((acc, index) => acc.childNodes[index], this.e)
|
|
758
|
+
};
|
|
759
|
+
|
|
760
|
+
/**
|
|
761
|
+
* Is Attached.
|
|
762
|
+
* Determines whether this component is attached to the DOM.
|
|
763
|
+
*/
|
|
764
|
+
proto.__ia = function() {
|
|
765
|
+
let e = this.e;
|
|
766
|
+
while (e) {
|
|
767
|
+
if (e === document) {
|
|
768
|
+
return true
|
|
769
|
+
}
|
|
770
|
+
e = e.parentNode;
|
|
771
|
+
}
|
|
772
|
+
return false
|
|
773
|
+
};
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* Creates a lookup.
|
|
777
|
+
*/
|
|
778
|
+
proto.__lu = function(callbacks) {
|
|
779
|
+
return new Lookup(callbacks)
|
|
780
|
+
};
|
|
781
|
+
|
|
782
|
+
/**
|
|
783
|
+
* Creates an anonymous stub component class
|
|
784
|
+
*/
|
|
785
|
+
proto.__sv = function() {
|
|
786
|
+
const cls = function(parent) {
|
|
787
|
+
Component.call(this, parent);
|
|
788
|
+
};
|
|
789
|
+
cls.prototype = new Component();
|
|
790
|
+
return cls
|
|
791
|
+
};
|
|
792
|
+
|
|
793
|
+
/**
|
|
794
|
+
* Toggles visibility, like wrapper.
|
|
795
|
+
*/
|
|
796
|
+
proto.visible = function(visible) {
|
|
797
|
+
this.e.classList.toggle('hidden', !visible);
|
|
798
|
+
};
|
|
799
|
+
|
|
800
|
+
module.exports = {
|
|
801
|
+
createComponent,
|
|
802
|
+
h,
|
|
803
|
+
mount,
|
|
804
|
+
KeyedPool,
|
|
805
|
+
InstancePool,
|
|
806
|
+
isStr,
|
|
807
|
+
SequentialPool,
|
|
808
|
+
Component,
|
|
809
|
+
Wrapper,
|
|
810
|
+
wrap
|
|
811
|
+
};
|