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/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "wallace",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "The framework that brings you freedom.",
|
|
5
|
+
"babel": {
|
|
6
|
+
"presets": [
|
|
7
|
+
"@babel/preset-env"
|
|
8
|
+
],
|
|
9
|
+
"plugins": [
|
|
10
|
+
"babel-plugin-wallace",
|
|
11
|
+
"@babel/plugin-syntax-jsx",
|
|
12
|
+
"@babel/plugin-proposal-class-properties"
|
|
13
|
+
]
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"src"
|
|
18
|
+
],
|
|
19
|
+
"main": "dist/wallace.js",
|
|
20
|
+
"scripts": {
|
|
21
|
+
"prepublish": "npm run build",
|
|
22
|
+
"build": "NODE_ENV=production rollup -c",
|
|
23
|
+
"test": "jest --clearCache && jest",
|
|
24
|
+
"clear-jest": "jest --clearCache"
|
|
25
|
+
},
|
|
26
|
+
"author": "Andrew Buchan",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
|
30
|
+
"babel-plugin-wallace": "^0.0.1"
|
|
31
|
+
},
|
|
32
|
+
"gitHead": "eab798895e65906cfab812010aafd131a74cb72d",
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"rollup": "^3.27.0"
|
|
35
|
+
}
|
|
36
|
+
}
|
package/src/component.js
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import {KeyedPool, InstancePool, SequentialPool} from './pool'
|
|
2
|
+
import {buildComponent, createComponent} from './utils'
|
|
3
|
+
import {und, makeEl} from './helpers'
|
|
4
|
+
import mountie from './mountie'
|
|
5
|
+
import {Wrapper} from './wrapper'
|
|
6
|
+
import {Lookup} from './lookup'
|
|
7
|
+
|
|
8
|
+
const noop = function() {}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Represents a component.
|
|
12
|
+
*/
|
|
13
|
+
export function Component(parent) {
|
|
14
|
+
const s = this
|
|
15
|
+
s.parent = parent // The parent component
|
|
16
|
+
s.props = undefined // The props passed to the component. May be changed.
|
|
17
|
+
|
|
18
|
+
// These will be set during build
|
|
19
|
+
s.e = null // the element
|
|
20
|
+
s.el = null // the named wrappers
|
|
21
|
+
|
|
22
|
+
// Internal state objects
|
|
23
|
+
s.__nv = [] // Nested components
|
|
24
|
+
s.__ov = {} // The old values for watches to compare against
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
var proto = Component.prototype
|
|
29
|
+
|
|
30
|
+
proto.onUpdate = noop
|
|
31
|
+
proto.afterUpdate = noop
|
|
32
|
+
proto.onInit = noop
|
|
33
|
+
proto.afterInit = noop
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Gets called once immediately after building.
|
|
37
|
+
* Sets initial props extracted from __html__.
|
|
38
|
+
* Note there is an issue here, in that we rely on there being initial props to call init
|
|
39
|
+
* on nested components.
|
|
40
|
+
*/
|
|
41
|
+
proto.init = function() {
|
|
42
|
+
this.onInit()
|
|
43
|
+
for (let key in this.__ip) {
|
|
44
|
+
let nestedComponent = this.el[key]
|
|
45
|
+
let callback = this.__ip[key]
|
|
46
|
+
if (callback) {
|
|
47
|
+
nestedComponent.props = callback(this, this.props)
|
|
48
|
+
}
|
|
49
|
+
nestedComponent.init()
|
|
50
|
+
}
|
|
51
|
+
this.afterInit()
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Calls a function somewhere up the parent tree.
|
|
55
|
+
*/
|
|
56
|
+
proto.bubble = function(name) {
|
|
57
|
+
let target = this.parent
|
|
58
|
+
while (!und(target)) {
|
|
59
|
+
if (target[name]) {
|
|
60
|
+
// We don't really care about performance here, so accessing arguments is fine.
|
|
61
|
+
return target[name].apply(target, Array.prototype.slice.call(arguments, 1))
|
|
62
|
+
}
|
|
63
|
+
target = target.parent
|
|
64
|
+
}
|
|
65
|
+
throw 'Bubble popped.'
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Move the component to new parent. Necessary if sharing a pool.
|
|
69
|
+
*/
|
|
70
|
+
proto.move = function(newParent) {
|
|
71
|
+
if (this.parent && this.parent.__nv) {
|
|
72
|
+
const nv = this.parent.__nv
|
|
73
|
+
nv.splice(nv.indexOf(this), 1)
|
|
74
|
+
}
|
|
75
|
+
this.parent = newParent
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Builds a nested component of the specified class. Its up to you how you use it.
|
|
79
|
+
*/
|
|
80
|
+
proto.nest = function(cls, props) {
|
|
81
|
+
const child = createComponent(cls, this, props || this.props)
|
|
82
|
+
this.__nv.push(child)
|
|
83
|
+
return child
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Lookup a watched value during update. Returns an object with {o, n, c}
|
|
87
|
+
* (oldValue, newValue, changed).
|
|
88
|
+
* You must call this.resetLookups before calling this during an update.
|
|
89
|
+
* The point is to pool the result so it doesn't have to be repeated.
|
|
90
|
+
*/
|
|
91
|
+
proto.lookup = function(query) {
|
|
92
|
+
return this.__qc.get(this, query)
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Resets the lookups, must be called before calling this.lookup() during an update.
|
|
96
|
+
*/
|
|
97
|
+
proto.resetLookups = function() {
|
|
98
|
+
this.__qc.reset()
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Sets the props and updates the component.
|
|
102
|
+
*/
|
|
103
|
+
proto.setProps = function(props) {
|
|
104
|
+
this.props = props
|
|
105
|
+
this.update()
|
|
106
|
+
return this
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Call this if you want to get mount() and unmount() callbacks.
|
|
110
|
+
*/
|
|
111
|
+
proto.trackMounting = function() {
|
|
112
|
+
this.__mt.track(this)
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Updates the component.
|
|
116
|
+
*/
|
|
117
|
+
proto.update = function() {
|
|
118
|
+
this.onUpdate()
|
|
119
|
+
this.resetLookups()
|
|
120
|
+
this.updateSelf()
|
|
121
|
+
this.updateNested()
|
|
122
|
+
this.afterUpdate()
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Loops over watches skipping shielded watches if elements are hidden.
|
|
126
|
+
*/
|
|
127
|
+
proto.updateSelf = function() {
|
|
128
|
+
let i = 0, watch, wrapper, shieldCount, shieldQuery, shieldQueryResult, shouldBeVisible
|
|
129
|
+
const watches = this.__wc
|
|
130
|
+
const il = watches.length
|
|
131
|
+
while (i < il) {
|
|
132
|
+
watch = watches[i]
|
|
133
|
+
wrapper = this.el[watch.wk]
|
|
134
|
+
shieldQuery = watch.sq
|
|
135
|
+
i ++
|
|
136
|
+
shouldBeVisible = true
|
|
137
|
+
if (shieldQuery) {
|
|
138
|
+
// Get the newValue for shieldQuery using lookup
|
|
139
|
+
shieldQueryResult = this.lookup(shieldQuery).n
|
|
140
|
+
|
|
141
|
+
// Determine if shouldBeVisible based on reverseShield
|
|
142
|
+
// i.e. whether "shieldQuery===true" means show or hide.
|
|
143
|
+
shouldBeVisible = watch.rv ? shieldQueryResult : !shieldQueryResult
|
|
144
|
+
|
|
145
|
+
// The number of watches to skip if this element is not visible
|
|
146
|
+
shieldCount = shouldBeVisible ? 0 : watch.sc
|
|
147
|
+
|
|
148
|
+
// Set the element visibility
|
|
149
|
+
wrapper.visible(shouldBeVisible)
|
|
150
|
+
i += shieldCount
|
|
151
|
+
}
|
|
152
|
+
if (shouldBeVisible) {
|
|
153
|
+
applyWatchCallbacks(this, wrapper, watch.cb)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Update nested components (but not repeat elements).
|
|
159
|
+
*/
|
|
160
|
+
proto.updateNested = function() {
|
|
161
|
+
// These are user created by calling nest()
|
|
162
|
+
const items = this.__nv
|
|
163
|
+
for (let i=0, il=items.length; i<il; i++) {
|
|
164
|
+
let child = items[i]
|
|
165
|
+
if (child.__ia()) {
|
|
166
|
+
child.update()
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// These are created with directives, and whose props arguments may need reprocessed.
|
|
170
|
+
for (let key in this.__ip) {
|
|
171
|
+
let callback = this.__ip[key]
|
|
172
|
+
let nestedComponent = this.el[key]
|
|
173
|
+
if (callback) {
|
|
174
|
+
nestedComponent.setProps(callback(this, this.props))
|
|
175
|
+
} else {
|
|
176
|
+
nestedComponent.update()
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Calls the callback if the value has changed (
|
|
182
|
+
*/
|
|
183
|
+
// changed(name, callback) {
|
|
184
|
+
// const n = this.__ov[name]
|
|
185
|
+
// const o = this.props[name]
|
|
186
|
+
// if (n !== o) {
|
|
187
|
+
// callback(n, o)
|
|
188
|
+
// }
|
|
189
|
+
// }
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Creates a watch.
|
|
194
|
+
*/
|
|
195
|
+
proto.__wa = function(wrapperKey, shieldQuery, reverseShield, shieldCount, callbacks) {
|
|
196
|
+
return {
|
|
197
|
+
wk: wrapperKey, // The key of the corresponding wrapper.
|
|
198
|
+
sq: shieldQuery, // The shield query key
|
|
199
|
+
rv: reverseShield, // whether shieldQuery should be flipped
|
|
200
|
+
sc: shieldCount, // The number of items to shield
|
|
201
|
+
cb: callbacks // The callbacks - object
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
const applyWatchCallbacks = (component, wrapper, callbacks) => {
|
|
207
|
+
|
|
208
|
+
for (let key in callbacks) {
|
|
209
|
+
let callback = callbacks[key]
|
|
210
|
+
if (key === '*') {
|
|
211
|
+
callback.call(component, wrapper, component.props, component)
|
|
212
|
+
} else {
|
|
213
|
+
// means: {new, old, changed}
|
|
214
|
+
const {n, o, c} = component.lookup(key)
|
|
215
|
+
if (c) {
|
|
216
|
+
callback.call(component, n, o, wrapper, component.props, component)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* The global mount tracker.
|
|
225
|
+
*/
|
|
226
|
+
proto.__mt = mountie
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Nest Internal. For building a nested component declared in the html.
|
|
230
|
+
*/
|
|
231
|
+
proto.__ni = function(path, cls) {
|
|
232
|
+
const child = buildComponent(cls, this)
|
|
233
|
+
this.__gw(path).replace(child.e)
|
|
234
|
+
return child
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
*
|
|
239
|
+
* @param {function} baseClass - the base class to extend from
|
|
240
|
+
* @param {object} [prototypeExtras] - an object with extra things to be added to the prototype
|
|
241
|
+
* @param {function} [prototypeExtras] - the function to be used as constructor
|
|
242
|
+
*/
|
|
243
|
+
Component.prototype.__ex = function(baseClass, prototypeExtras, constructorFunction) {
|
|
244
|
+
var subClass = constructorFunction || function(parent) {
|
|
245
|
+
baseClass.call(this, parent)
|
|
246
|
+
}
|
|
247
|
+
subClass.prototype = Object.create(baseClass && baseClass.prototype, {
|
|
248
|
+
constructor: { value: subClass, writable: true, configurable: true }
|
|
249
|
+
});
|
|
250
|
+
if (prototypeExtras) {
|
|
251
|
+
Object.assign(subClass.prototype, prototypeExtras);
|
|
252
|
+
}
|
|
253
|
+
return subClass
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Create a component pool.
|
|
259
|
+
*/
|
|
260
|
+
proto.pool = function(cls, keyFn) {
|
|
261
|
+
return keyFn ? new KeyedPool(cls, keyFn) : new SequentialPool(cls)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Create an instance pool, for switches.
|
|
266
|
+
*/
|
|
267
|
+
proto.__ic = function(mappings, fallback) {
|
|
268
|
+
return new InstancePool(mappings, fallback)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Build the DOM. We pass prototype as local var for compactness.
|
|
273
|
+
*/
|
|
274
|
+
proto.__bd = function(prototype) {
|
|
275
|
+
if (prototype.__cn === undefined) {
|
|
276
|
+
prototype.__cn = makeEl(prototype.__ht)
|
|
277
|
+
}
|
|
278
|
+
this.e = prototype.__cn.cloneNode(true)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// proto.__bd = function(prototype, clone) {
|
|
282
|
+
// if (clone && !prototype.__cn) {
|
|
283
|
+
// prototype.__cn = makeEl(prototype.__ht)
|
|
284
|
+
// }
|
|
285
|
+
// this.e = clone ? prototype.__cn.cloneNode(true) : makeEl(prototype.__ht)
|
|
286
|
+
// }
|
|
287
|
+
|
|
288
|
+
// proto.__bd = function(prototype) {
|
|
289
|
+
// this.e = makeEl(prototype.__ht)
|
|
290
|
+
// }
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Returns a regular wrapper around element at path, where path is an array of indices.
|
|
294
|
+
* This is used by the babel plugin.
|
|
295
|
+
*/
|
|
296
|
+
proto.__gw = function(path) {
|
|
297
|
+
return new Wrapper(this.__fe(path))
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Finds an element at specified path, where path is an array of indices.
|
|
302
|
+
* This is used by the babel plugin.
|
|
303
|
+
*/
|
|
304
|
+
proto.__fe = function(path) {
|
|
305
|
+
return path.reduce((acc, index) => acc.childNodes[index], this.e)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Is Attached.
|
|
310
|
+
* Determines whether this component is attached to the DOM.
|
|
311
|
+
*/
|
|
312
|
+
proto.__ia = function() {
|
|
313
|
+
let e = this.e
|
|
314
|
+
while (e) {
|
|
315
|
+
if (e === document) {
|
|
316
|
+
return true
|
|
317
|
+
}
|
|
318
|
+
e = e.parentNode
|
|
319
|
+
}
|
|
320
|
+
return false
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Creates a lookup.
|
|
325
|
+
*/
|
|
326
|
+
proto.__lu = function(callbacks) {
|
|
327
|
+
return new Lookup(callbacks)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Creates an anonymous stub component class
|
|
332
|
+
*/
|
|
333
|
+
proto.__sv = function() {
|
|
334
|
+
const cls = function(parent) {
|
|
335
|
+
Component.call(this, parent)
|
|
336
|
+
}
|
|
337
|
+
cls.prototype = new Component()
|
|
338
|
+
return cls
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Toggles visibility, like wrapper.
|
|
343
|
+
*/
|
|
344
|
+
proto.visible = function(visible) {
|
|
345
|
+
this.e.classList.toggle('hidden', !visible)
|
|
346
|
+
}
|
package/src/helpers.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const doc = document
|
|
2
|
+
const throwAway = doc.createElement('template')
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Create an element from html string
|
|
6
|
+
*/
|
|
7
|
+
export function makeEl(html) {
|
|
8
|
+
throwAway.innerHTML = html
|
|
9
|
+
return throwAway.content.firstChild
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Some utility functions
|
|
14
|
+
*/
|
|
15
|
+
export const und = x => x === undefined
|
|
16
|
+
export const isStr = x => typeof x === 'string'
|
package/src/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {createComponent, h, mount, wrap} from './utils'
|
|
2
|
+
import {isStr} from './helpers'
|
|
3
|
+
import {Component} from './component'
|
|
4
|
+
import {KeyedPool, InstancePool, SequentialPool} from './pool'
|
|
5
|
+
import {Wrapper} from './wrapper'
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
createComponent,
|
|
9
|
+
h,
|
|
10
|
+
mount,
|
|
11
|
+
KeyedPool,
|
|
12
|
+
InstancePool,
|
|
13
|
+
isStr,
|
|
14
|
+
SequentialPool,
|
|
15
|
+
Component,
|
|
16
|
+
Wrapper,
|
|
17
|
+
wrap
|
|
18
|
+
}
|
package/src/lookup.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {und} from './helpers'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Used internally.
|
|
5
|
+
* An object which pools the results of lookup queries so we don't have to
|
|
6
|
+
* repeat them in the same component.
|
|
7
|
+
* The Lookup instance will be shared between instances of a component.
|
|
8
|
+
* Must call reset() on every update.
|
|
9
|
+
*/
|
|
10
|
+
export function Lookup(callbacks) {
|
|
11
|
+
this.callbacks = callbacks
|
|
12
|
+
this.run = {}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
Lookup.prototype = {
|
|
16
|
+
get: function(component, key) {
|
|
17
|
+
const run = this.run
|
|
18
|
+
if (run[key] === undefined) {
|
|
19
|
+
// Verbose but efficient way as it avoids lookups?
|
|
20
|
+
// Or is this harmful to performance because we're just reading values more than calling functions?
|
|
21
|
+
let o = component.__ov[key]
|
|
22
|
+
o = und(o) ? '' : o
|
|
23
|
+
const n = this.callbacks[key](component, component.props)
|
|
24
|
+
const c = n !== o
|
|
25
|
+
component.__ov[key] = n
|
|
26
|
+
const rtn = {n, o, c}
|
|
27
|
+
run[key] = rtn
|
|
28
|
+
return rtn
|
|
29
|
+
}
|
|
30
|
+
return run[key]
|
|
31
|
+
},
|
|
32
|
+
reset: function() {
|
|
33
|
+
this.run = {}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
package/src/mountie.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wallace's crude way of tracking mounting and unmounting.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const trackedComponents = []
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
track: function (component) {
|
|
9
|
+
trackedComponents.push({component: component, isAttached: component.__ia()})
|
|
10
|
+
},
|
|
11
|
+
flush: function () {
|
|
12
|
+
for (let i=0, il=trackedComponents.length; i<il; i++) {
|
|
13
|
+
let trackedComponent = trackedComponents[i]
|
|
14
|
+
let component = trackedComponent.component
|
|
15
|
+
let attachedNow = component.__ia()
|
|
16
|
+
if (attachedNow !== trackedComponent.isAttached) {
|
|
17
|
+
let fn = attachedNow ? component.mount : component.unmount
|
|
18
|
+
fn.apply(component)
|
|
19
|
+
trackedComponent.isAttached = attachedNow
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
package/src/pool.js
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import {createComponent} from './utils'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pools same type components, retrieving by sequence.
|
|
5
|
+
* Must not be shared.
|
|
6
|
+
*
|
|
7
|
+
* @param {class} componentClass - The class of Component to create.
|
|
8
|
+
* @param {function} keyFn - A function which obtains the key to pool by
|
|
9
|
+
*/
|
|
10
|
+
export function KeyedPool(componentClass, keyFn) {
|
|
11
|
+
this._v = componentClass
|
|
12
|
+
this._f = keyFn
|
|
13
|
+
this._k = [] // keys
|
|
14
|
+
this._p = {} // pool of component instances
|
|
15
|
+
}
|
|
16
|
+
const proto = KeyedPool.prototype
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Retrieves a single component. Though not used in Wallace itself, it may
|
|
20
|
+
* be used elsewhere, such as in the router.
|
|
21
|
+
*
|
|
22
|
+
* @param {Object} item - The item which will be passed as props.
|
|
23
|
+
* @param {Component} parent - The parent component.
|
|
24
|
+
*/
|
|
25
|
+
proto.getOne = function(item, parent) {
|
|
26
|
+
return this._get(this._p, this._v, this._f(item), item, parent)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Updates the element's childNodes to match the items.
|
|
31
|
+
* Performance is important.
|
|
32
|
+
*
|
|
33
|
+
* @param {DOMElement} e - The DOM element to patch.
|
|
34
|
+
* @param {Array} items - Array of items which will be passed as props.
|
|
35
|
+
* @param {Component} parent - The parent component.
|
|
36
|
+
*/
|
|
37
|
+
proto.patch = function(e, items, parent) {
|
|
38
|
+
const pool = this._p
|
|
39
|
+
const componentClass = this._v
|
|
40
|
+
const keyFn = this._f
|
|
41
|
+
const childNodes = e.childNodes
|
|
42
|
+
const itemsLength = items.length
|
|
43
|
+
const oldKeySequence = this._k
|
|
44
|
+
const newKeys = []
|
|
45
|
+
let item, key, component, childElementCount = oldKeySequence.length + 1
|
|
46
|
+
for (let i=0; i<itemsLength; i++) {
|
|
47
|
+
item = items[i]
|
|
48
|
+
key = keyFn(item)
|
|
49
|
+
component = this._get(pool, componentClass, key, item, parent)
|
|
50
|
+
newKeys.push(key)
|
|
51
|
+
if (i > childElementCount) {
|
|
52
|
+
e.appendChild(component.e)
|
|
53
|
+
} else if (key !== oldKeySequence[i]) {
|
|
54
|
+
e.insertBefore(component.e, childNodes[i])
|
|
55
|
+
pull(oldKeySequence, key, i)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
this._k = newKeys
|
|
59
|
+
trimChildren(e, childNodes, itemsLength)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Internal
|
|
63
|
+
proto._get = function(pool, componentClass, key, item, parent) {
|
|
64
|
+
let component
|
|
65
|
+
if (pool.hasOwnProperty(key)) {
|
|
66
|
+
component = pool[key]
|
|
67
|
+
component.setProps(item)
|
|
68
|
+
} else {
|
|
69
|
+
component = createComponent(componentClass, parent, item)
|
|
70
|
+
pool[key] = component;
|
|
71
|
+
}
|
|
72
|
+
return component
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Pools same type components, retrieving by sequence.
|
|
77
|
+
* Must not be shared.
|
|
78
|
+
*
|
|
79
|
+
* @param {class} componentClass - The class of Component to create.
|
|
80
|
+
*/
|
|
81
|
+
export function SequentialPool(componentClass) {
|
|
82
|
+
this._v = componentClass
|
|
83
|
+
this._p = [] // pool of component instances
|
|
84
|
+
this._c = 0 // Child element count
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Updates the element's childNodes to match the items.
|
|
89
|
+
* Performance is important.
|
|
90
|
+
*
|
|
91
|
+
* @param {DOMElement} e - The DOM element to patch.
|
|
92
|
+
* @param {Array} items - Array of items which will be passed as props.
|
|
93
|
+
* @param {Component} parent - The parent component.
|
|
94
|
+
*/
|
|
95
|
+
SequentialPool.prototype.patch = function(e, items, parent) {
|
|
96
|
+
const pool = this._p
|
|
97
|
+
const componentClass = this._v
|
|
98
|
+
const childNodes = e.childNodes
|
|
99
|
+
const itemsLength = items.length
|
|
100
|
+
let item, component, poolCount = pool.length, childElementCount = this._c
|
|
101
|
+
|
|
102
|
+
for (let i=0; i<itemsLength; i++) {
|
|
103
|
+
item = items[i]
|
|
104
|
+
if (i < poolCount) {
|
|
105
|
+
component = pool[i]
|
|
106
|
+
component.setProps(item)
|
|
107
|
+
} else {
|
|
108
|
+
component = createComponent(componentClass, parent, item)
|
|
109
|
+
pool.push(component)
|
|
110
|
+
poolCount ++
|
|
111
|
+
}
|
|
112
|
+
if (i >= childElementCount) {
|
|
113
|
+
e.appendChild(component.e)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
this._c = itemsLength
|
|
117
|
+
trimChildren(e, childNodes, itemsLength)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* An object which creates and pools components according to the mappings provided.
|
|
122
|
+
* If there is no match in the mappings, the fallback function is called.
|
|
123
|
+
*
|
|
124
|
+
* Note that the fallback must return an instance (of Component or Wrapper) whereas
|
|
125
|
+
* mappings must specify component classes.
|
|
126
|
+
*
|
|
127
|
+
* You can rely solely on the fallback if you like.
|
|
128
|
+
*
|
|
129
|
+
* @param {Object} mappings - a mapping of format key->componentClass
|
|
130
|
+
* @param {function} fallback - a function to call when no key is provided.
|
|
131
|
+
*
|
|
132
|
+
*/
|
|
133
|
+
export function InstancePool(mappings, fallback) {
|
|
134
|
+
this._m = mappings
|
|
135
|
+
this._f = fallback
|
|
136
|
+
this._i = {} // Instances
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
InstancePool.prototype.getOne = function(key, parentComponent) {
|
|
140
|
+
if (!this._i.hasOwnProperty(key)) {
|
|
141
|
+
this._i[key] = this._m.hasOwnProperty(key) ?
|
|
142
|
+
parentComponent.nest(this._m[key]) : this._f(key, parentComponent)
|
|
143
|
+
}
|
|
144
|
+
return this._i[key]
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Trims the unwanted child elements from the end.
|
|
149
|
+
*
|
|
150
|
+
* @param {Node} e
|
|
151
|
+
* @param {Array} childNodes
|
|
152
|
+
* @param {Int} itemsLength
|
|
153
|
+
*/
|
|
154
|
+
function trimChildren(e, childNodes, itemsLength) {
|
|
155
|
+
let lastIndex = childNodes.length - 1
|
|
156
|
+
let keepIndex = itemsLength - 1
|
|
157
|
+
for (let i=lastIndex; i>keepIndex; i--) {
|
|
158
|
+
e.removeChild(childNodes[i])
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Pulls an item forward in an array, to replicate insertBefore.
|
|
164
|
+
* @param {Array} arr
|
|
165
|
+
* @param {any} item
|
|
166
|
+
* @param {Int} to
|
|
167
|
+
*/
|
|
168
|
+
function pull(arr, item, to) {
|
|
169
|
+
const position = arr.indexOf(item)
|
|
170
|
+
if (position != to) {
|
|
171
|
+
arr.splice(to, 0, arr.splice(position, 1)[0])
|
|
172
|
+
}
|
|
173
|
+
}
|