alchemy-form 0.2.9 → 0.3.0-alpha.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/CHANGELOG.md +15 -0
- package/assets/stylesheets/form/elements/_code_input.scss +0 -1
- package/assets/stylesheets/form/elements/_field.scss +15 -0
- package/assets/stylesheets/form/elements/_settings_editor.scss +62 -0
- package/assets/stylesheets/form/elements/_table.scss +0 -1
- package/assets/stylesheets/form/elements/_virtual_scroll.scss +19 -0
- package/assets/stylesheets/form/elements/index.scss +3 -1
- package/config/routes.js +3 -3
- package/controller/form_api_controller.js +2 -4
- package/element/00_form_base.js +21 -0
- package/element/al_code_input.js +51 -13
- package/element/al_field.js +25 -24
- package/element/al_form.js +12 -4
- package/element/al_select.js +2 -1
- package/element/al_settings_editor.js +367 -0
- package/element/al_table.js +13 -7
- package/element/al_virtual_scroll.js +873 -0
- package/package.json +3 -3
- package/view/form/elements/al_settings_editor.hwk +36 -0
- package/view/form/inputs/edit/object.hwk +9 -0
- package/view/form/inputs/edit/settings.hwk +5 -0
- package/view/form/inputs/view/string.hwk +14 -1
- package/view/form/wrappers/default/default.hwk +18 -2
- /package/element/{al_string_input.js → 50-al_string_input.js} +0 -0
|
@@ -0,0 +1,873 @@
|
|
|
1
|
+
const DOM_ELEMENT = Symbol('dom_element'),
|
|
2
|
+
HAS_APPEARS_LISTENER = Symbol('has_appears_listener');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The al-virtual-scroll element:
|
|
6
|
+
* A scrollable container that only renders the visible elements.
|
|
7
|
+
*
|
|
8
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
9
|
+
* @since 0.2.10
|
|
10
|
+
* @version 0.2.10
|
|
11
|
+
*/
|
|
12
|
+
const VirtualScroll = Function.inherits('Alchemy.Element.Form.WithDataprovider', 'VirtualScroll');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The template to use to render an entry
|
|
16
|
+
*
|
|
17
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
18
|
+
* @since 0.2.10
|
|
19
|
+
* @version 0.2.10
|
|
20
|
+
*/
|
|
21
|
+
VirtualScroll.setAttribute('item-template');
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Which sides can be used for remote loading?
|
|
25
|
+
*
|
|
26
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
27
|
+
* @since 0.2.10
|
|
28
|
+
* @version 0.2.10
|
|
29
|
+
*/
|
|
30
|
+
VirtualScroll.setAttribute('remote-load-sides', function getRemoteLoadSides(val) {
|
|
31
|
+
|
|
32
|
+
if (val == null) {
|
|
33
|
+
val = ['top', 'bottom'];
|
|
34
|
+
} else {
|
|
35
|
+
val = val.split(',');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return val;
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* On what side are new items added?
|
|
43
|
+
*
|
|
44
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
45
|
+
* @since 0.2.10
|
|
46
|
+
* @version 0.2.10
|
|
47
|
+
*/
|
|
48
|
+
VirtualScroll.setAttribute('insert-side', function getInsertSide(val) {
|
|
49
|
+
|
|
50
|
+
if (!val) {
|
|
51
|
+
val = 'bottom';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return val;
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* How should the records be sorted?
|
|
59
|
+
* This is only used to inform the remote host.
|
|
60
|
+
*
|
|
61
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
62
|
+
* @since 0.2.10
|
|
63
|
+
* @version 0.2.10
|
|
64
|
+
*/
|
|
65
|
+
VirtualScroll.setAttribute('sort', function getSort(val) {
|
|
66
|
+
|
|
67
|
+
if (val == -1) {
|
|
68
|
+
val = 'desc';
|
|
69
|
+
} else if (val == 1) {
|
|
70
|
+
val = 'asc';
|
|
71
|
+
} else if (typeof val == 'string') {
|
|
72
|
+
val = val.toLowerCase();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!val) {
|
|
76
|
+
val = 'asc';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return val;
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The batch size
|
|
84
|
+
*
|
|
85
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
86
|
+
* @since 0.2.10
|
|
87
|
+
* @version 0.2.10
|
|
88
|
+
*/
|
|
89
|
+
VirtualScroll.setAttribute('batch-size', function getBatchSize(value) {
|
|
90
|
+
|
|
91
|
+
if (!value || !isFinite(value)) {
|
|
92
|
+
value = 10;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return value;
|
|
96
|
+
|
|
97
|
+
}, {type: 'number'});
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The amount of allowed-invisible-elements to remain in the dom
|
|
101
|
+
*
|
|
102
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
103
|
+
* @since 0.2.10
|
|
104
|
+
* @version 0.2.10
|
|
105
|
+
*/
|
|
106
|
+
VirtualScroll.setAttribute('allowed-invisible-elements', function getAllowedInvisibleElements(value) {
|
|
107
|
+
|
|
108
|
+
if (!value || !isFinite(value) || value < 0) {
|
|
109
|
+
value = this.batch_size;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!value || !isFinite(value)) {
|
|
113
|
+
value = 10;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Keep at least 5 elements at each side
|
|
117
|
+
if (value < 5) {
|
|
118
|
+
value = 5;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return value;
|
|
122
|
+
|
|
123
|
+
}, {type: 'number'});
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* The expected height of an entry (in pixels)
|
|
127
|
+
*
|
|
128
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
129
|
+
* @since 0.2.10
|
|
130
|
+
* @version 0.2.10
|
|
131
|
+
*/
|
|
132
|
+
VirtualScroll.setAttribute('expected-item-height', {type: 'number'});
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* The expected width of an entry (in pixels)
|
|
136
|
+
*
|
|
137
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
138
|
+
* @since 0.2.10
|
|
139
|
+
* @version 0.2.10
|
|
140
|
+
*/
|
|
141
|
+
VirtualScroll.setAttribute('expected-item-width', {type: 'number'});
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Getter for the top-trigger element
|
|
145
|
+
*
|
|
146
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
147
|
+
* @since 0.2.10
|
|
148
|
+
* @version 0.2.10
|
|
149
|
+
*/
|
|
150
|
+
VirtualScroll.addElementGetter('top_trigger', '.top-trigger');
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Getter for the bottom-trigger element
|
|
154
|
+
*
|
|
155
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
156
|
+
* @since 0.2.10
|
|
157
|
+
* @version 0.2.10
|
|
158
|
+
*/
|
|
159
|
+
VirtualScroll.addElementGetter('bottom_trigger', '.bottom-trigger');
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* The bidrectional array
|
|
163
|
+
*
|
|
164
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
165
|
+
* @since 0.2.10
|
|
166
|
+
* @version 0.2.10
|
|
167
|
+
*/
|
|
168
|
+
VirtualScroll.setAssignedProperty('loaded_entries', function getLoadedEntries(val) {
|
|
169
|
+
|
|
170
|
+
if (!val) {
|
|
171
|
+
val = new Classes.Develry.LinkedMap();
|
|
172
|
+
this.loaded_entries = val;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return val;
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Get the expected item height in pixels
|
|
180
|
+
*
|
|
181
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
182
|
+
* @since 0.2.10
|
|
183
|
+
* @version 0.2.10
|
|
184
|
+
*/
|
|
185
|
+
VirtualScroll.setMethod(function getExpectedItemHeight() {
|
|
186
|
+
return this.expected_item_height || 100;
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Construct the config object used to fetch data
|
|
191
|
+
*
|
|
192
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
193
|
+
* @since 0.2.10
|
|
194
|
+
* @version 0.2.10
|
|
195
|
+
*/
|
|
196
|
+
VirtualScroll.setMethod(function getRemoteFetchConfig(config) {
|
|
197
|
+
|
|
198
|
+
if (!config) {
|
|
199
|
+
config = {};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (!config.direction) {
|
|
203
|
+
config.initial_data = true;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
config.insert_side = this.insert_side;
|
|
207
|
+
config.sort = this.sort;
|
|
208
|
+
config.batch_size = this.batch_size;
|
|
209
|
+
|
|
210
|
+
this.ensureTriggerElements();
|
|
211
|
+
|
|
212
|
+
if (this.page_size) {
|
|
213
|
+
config.page_size = this.page_size;
|
|
214
|
+
config.page = this.getWantedPage();
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
let head = this.loaded_entries.head,
|
|
218
|
+
tail = this.loaded_entries.tail;
|
|
219
|
+
|
|
220
|
+
if (config.direction == 'top') {
|
|
221
|
+
if (head) {
|
|
222
|
+
config.before = head.value?.record;
|
|
223
|
+
}
|
|
224
|
+
} else if (config.direction == 'bottom') {
|
|
225
|
+
if (tail) {
|
|
226
|
+
config.after = tail.value?.record;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return config;
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Make sure the elements that should trigger new elements
|
|
235
|
+
* to render are added to the DOM
|
|
236
|
+
*
|
|
237
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
238
|
+
* @since 0.2.10
|
|
239
|
+
* @version 0.2.10
|
|
240
|
+
*/
|
|
241
|
+
VirtualScroll.setMethod(function ensureTriggerElements() {
|
|
242
|
+
|
|
243
|
+
let top_trigger = this.top_trigger,
|
|
244
|
+
bottom_trigger = this.bottom_trigger;
|
|
245
|
+
|
|
246
|
+
if (!top_trigger) {
|
|
247
|
+
top_trigger = this.createElement('div');
|
|
248
|
+
top_trigger.classList.add('top-trigger');
|
|
249
|
+
this.prepend(top_trigger);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (!bottom_trigger) {
|
|
253
|
+
bottom_trigger = this.createElement('div');
|
|
254
|
+
bottom_trigger.classList.add('bottom-trigger');
|
|
255
|
+
this.append(bottom_trigger);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (!Blast.isBrowser || this[HAS_APPEARS_LISTENER]) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
this[HAS_APPEARS_LISTENER] = true;
|
|
263
|
+
|
|
264
|
+
this.addEventListener('scroll', e => {
|
|
265
|
+
this.handleScrollEvent();
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
const onElementVisible = (element) => {
|
|
269
|
+
|
|
270
|
+
if (element == top_trigger) {
|
|
271
|
+
this.showEntries('top');
|
|
272
|
+
} else if (element == bottom_trigger) {
|
|
273
|
+
this.showEntries('bottom');
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const observer = new IntersectionObserver((entries, observer) => {
|
|
278
|
+
for (let entry of entries) {
|
|
279
|
+
if (entry.isIntersecting) {
|
|
280
|
+
onElementVisible(entry.target);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}, {
|
|
284
|
+
threshold: 0,
|
|
285
|
+
rootMargin: '50px 0px 50px 0px',
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
observer.observe(top_trigger);
|
|
289
|
+
observer.observe(bottom_trigger);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Apply the fetched data
|
|
294
|
+
*
|
|
295
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
296
|
+
* @since 0.2.10
|
|
297
|
+
* @version 0.2.10
|
|
298
|
+
*/
|
|
299
|
+
VirtualScroll.setMethod(function applyFetchedData(err, result, config) {
|
|
300
|
+
|
|
301
|
+
if (err) {
|
|
302
|
+
console.error(err);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (!result) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
let records;
|
|
311
|
+
|
|
312
|
+
if (Array.isArray(result)) {
|
|
313
|
+
records = result;
|
|
314
|
+
} else if (result.length) {
|
|
315
|
+
// This way we keep `DocumentList` instances as they are!
|
|
316
|
+
records = result;
|
|
317
|
+
} else {
|
|
318
|
+
records = result.records;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
let append;
|
|
322
|
+
|
|
323
|
+
if (config.initial_data || config.append) {
|
|
324
|
+
append = true;
|
|
325
|
+
} else if (config.append != null) {
|
|
326
|
+
append = config.append;
|
|
327
|
+
} else {
|
|
328
|
+
append = config.direction == 'bottom';
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (append) {
|
|
332
|
+
//records = Array.cast(records).reverse();
|
|
333
|
+
for (let record of records) {
|
|
334
|
+
let entry = this.createEntryFor(record);
|
|
335
|
+
this.loaded_entries.set(entry.key, entry);
|
|
336
|
+
}
|
|
337
|
+
} else {
|
|
338
|
+
for (let record of records) {
|
|
339
|
+
let entry = this.createEntryFor(record);
|
|
340
|
+
this.loaded_entries.unshift(entry.key, entry);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
this.addMissingElementsFromLoadedEntries();
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Get the highest index of a dom element that has been added
|
|
349
|
+
*
|
|
350
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
351
|
+
* @since 0.2.10
|
|
352
|
+
* @version 0.2.10
|
|
353
|
+
*/
|
|
354
|
+
VirtualScroll.setMethod(function getDomKeyRange() {
|
|
355
|
+
|
|
356
|
+
let elements = this.querySelectorAll('[data-loaded-entry-key]'),
|
|
357
|
+
highest = null,
|
|
358
|
+
lowest = null,
|
|
359
|
+
element;
|
|
360
|
+
|
|
361
|
+
element = elements[0];
|
|
362
|
+
|
|
363
|
+
if (element) {
|
|
364
|
+
lowest = element.dataset.loadedEntryKey;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
element = elements[elements.length - 1];
|
|
368
|
+
|
|
369
|
+
if (element) {
|
|
370
|
+
highest = element.dataset.loadedEntryKey;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return [lowest, highest];
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Handle the scroll event
|
|
378
|
+
*
|
|
379
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
380
|
+
* @since 0.2.10
|
|
381
|
+
* @version 0.2.10
|
|
382
|
+
*/
|
|
383
|
+
VirtualScroll.setMethod(function handleScrollEvent() {
|
|
384
|
+
// @TODO
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Add missing elements from loaded entries
|
|
389
|
+
*
|
|
390
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
391
|
+
* @since 0.2.10
|
|
392
|
+
* @version 0.2.10
|
|
393
|
+
*/
|
|
394
|
+
VirtualScroll.setMethod(function showEntries(side) {
|
|
395
|
+
|
|
396
|
+
let [lowest_key, highest_key] = this.getDomKeyRange();
|
|
397
|
+
let key_to_check,
|
|
398
|
+
direction;
|
|
399
|
+
|
|
400
|
+
if (side == 'bottom') {
|
|
401
|
+
direction = 'getNodeAfter';
|
|
402
|
+
key_to_check = highest_key;
|
|
403
|
+
} else {
|
|
404
|
+
direction = 'getNodeBefore';
|
|
405
|
+
key_to_check = lowest_key;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
this.addMissingElementsFromLoadedEntries();
|
|
409
|
+
|
|
410
|
+
// If the id to check is already loaded, don't do anything
|
|
411
|
+
if (this.loaded_entries[direction](key_to_check, this.batch_size)) {
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (this.remote_load_sides.includes(side)) {
|
|
416
|
+
this.loadRemoteData({direction: side});
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Get the key of something
|
|
422
|
+
*
|
|
423
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
424
|
+
* @since 0.2.10
|
|
425
|
+
* @version 0.2.10
|
|
426
|
+
*/
|
|
427
|
+
VirtualScroll.setMethod(function getKeyOf(record) {
|
|
428
|
+
|
|
429
|
+
if (record._id != null) {
|
|
430
|
+
return '' + record._id;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (record.id != null) {
|
|
434
|
+
return '' + record.id;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (record.key != null) {
|
|
438
|
+
return '' + record.key;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
throw new Error('Could not get key of record');
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Create an entry object for the given record
|
|
446
|
+
*
|
|
447
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
448
|
+
* @since 0.2.10
|
|
449
|
+
* @version 0.2.10
|
|
450
|
+
*/
|
|
451
|
+
VirtualScroll.setMethod(function createEntryFor(record) {
|
|
452
|
+
|
|
453
|
+
let entry = {
|
|
454
|
+
record,
|
|
455
|
+
key : this.getKeyOf(record),
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
return entry;
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Insert a new record from somewhere
|
|
463
|
+
*
|
|
464
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
465
|
+
* @since 0.2.10
|
|
466
|
+
* @version 0.2.10
|
|
467
|
+
*/
|
|
468
|
+
VirtualScroll.setMethod(function insertRecord(record) {
|
|
469
|
+
|
|
470
|
+
let entry = this.createEntryFor(record);
|
|
471
|
+
|
|
472
|
+
if (this.insert_side == 'bottom') {
|
|
473
|
+
this.loaded_entries.set(entry.key, entry);
|
|
474
|
+
} else {
|
|
475
|
+
this.loaded_entries.unshift(entry.key, entry);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
let is_at_bottom = this.scrollTop + this.clientHeight >= this.scrollHeight;
|
|
479
|
+
|
|
480
|
+
// Add possible new elements
|
|
481
|
+
let add_count = this.addMissingElementsFromLoadedEntries();
|
|
482
|
+
|
|
483
|
+
if (is_at_bottom && add_count > 0) {
|
|
484
|
+
this.scrollTop = this.scrollHeight;
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Add missing elements from loaded entries
|
|
490
|
+
*
|
|
491
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
492
|
+
* @since 0.2.10
|
|
493
|
+
* @version 0.2.10
|
|
494
|
+
*/
|
|
495
|
+
VirtualScroll.setMethod(function addMissingElementsFromLoadedEntries() {
|
|
496
|
+
|
|
497
|
+
let [lowest_key, highest_key] = this.getDomKeyRange();
|
|
498
|
+
|
|
499
|
+
let top_result,
|
|
500
|
+
bottom_result;
|
|
501
|
+
|
|
502
|
+
let add_to_bottom = 0,
|
|
503
|
+
add_to_top = 0;
|
|
504
|
+
|
|
505
|
+
// We add even more padding to this isVisible check, just to make sure
|
|
506
|
+
if (Blast.isNode) {
|
|
507
|
+
add_to_top = 10;
|
|
508
|
+
} else if (this.top_trigger?.isVisible?.(100)) {
|
|
509
|
+
add_to_top = 10;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
if (this.bottom_trigger?.isVisible?.(100)) {
|
|
513
|
+
add_to_bottom = 10;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
let added = [];
|
|
517
|
+
let add_count = 0;
|
|
518
|
+
|
|
519
|
+
let top_node = this.loaded_entries.getNode(lowest_key),
|
|
520
|
+
bottom_node = this.loaded_entries.getNode(highest_key);
|
|
521
|
+
|
|
522
|
+
if (!top_node && !bottom_node) {
|
|
523
|
+
top_node = this.loaded_entries.tail;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
while (true) {
|
|
527
|
+
|
|
528
|
+
if (add_to_top > 0 && top_node) {
|
|
529
|
+
add_to_top--;
|
|
530
|
+
top_node = top_node.prev;
|
|
531
|
+
|
|
532
|
+
top_result = this._addAdjacent(this.top_trigger, top_node);
|
|
533
|
+
|
|
534
|
+
if (top_result) {
|
|
535
|
+
added.push(top_node.key);
|
|
536
|
+
add_count++;
|
|
537
|
+
}
|
|
538
|
+
} else {
|
|
539
|
+
top_result = false;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (add_to_bottom > 0 && bottom_node) {
|
|
543
|
+
add_to_bottom--;
|
|
544
|
+
bottom_node = bottom_node.next;
|
|
545
|
+
bottom_result = this._addAdjacent(this.bottom_trigger, bottom_node);
|
|
546
|
+
|
|
547
|
+
if (bottom_result) {
|
|
548
|
+
added.push(bottom_node.key);
|
|
549
|
+
add_count++;
|
|
550
|
+
}
|
|
551
|
+
} else {
|
|
552
|
+
bottom_result = false;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
if (!top_result && !bottom_result) {
|
|
556
|
+
break;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
this.cullInvisibleElements(added);
|
|
561
|
+
|
|
562
|
+
return add_count;
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Cull invisible elements from the DOM.
|
|
567
|
+
*
|
|
568
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
569
|
+
* @since 0.2.10
|
|
570
|
+
* @version 0.2.10
|
|
571
|
+
*/
|
|
572
|
+
VirtualScroll.setMethod(function cullInvisibleElements(added) {
|
|
573
|
+
|
|
574
|
+
if (!Blast.isBrowser) {
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
if (!added) {
|
|
579
|
+
added = [];
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// Get the lowest and highest dom index
|
|
583
|
+
let [lowest_key, highest_key] = this.getDomKeyRange();
|
|
584
|
+
|
|
585
|
+
let lowest_element = this.querySelector('[data-loaded-entry-key="' + lowest_key + '"]'),
|
|
586
|
+
highest_element = this.querySelector('[data-loaded-entry-key="' + highest_key + '"]');
|
|
587
|
+
|
|
588
|
+
let lowest_to_remove = [],
|
|
589
|
+
highest_to_remove = [];
|
|
590
|
+
|
|
591
|
+
const isNear = (element) => {
|
|
592
|
+
|
|
593
|
+
if (added.includes(element)) {
|
|
594
|
+
return true;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
if (element == this.top_trigger) {
|
|
598
|
+
return true;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
if (element == this.bottom_trigger) {
|
|
602
|
+
return true;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
if (element.isVisible()) {
|
|
606
|
+
return true;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
return false;
|
|
610
|
+
};
|
|
611
|
+
|
|
612
|
+
while (highest_element) {
|
|
613
|
+
|
|
614
|
+
if (isNear(highest_element)) {
|
|
615
|
+
break;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
highest_to_remove.push(highest_element);
|
|
619
|
+
highest_element = highest_element.previousElementSibling;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
while (lowest_element) {
|
|
623
|
+
|
|
624
|
+
if (isNear(lowest_element)) {
|
|
625
|
+
break;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
lowest_to_remove.push(lowest_element);
|
|
629
|
+
lowest_element = lowest_element.nextElementSibling;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
let allowed_invisible_elements = this.allowed_invisible_elements;
|
|
633
|
+
|
|
634
|
+
if (highest_to_remove.length > allowed_invisible_elements) {
|
|
635
|
+
highest_to_remove = highest_to_remove.slice(0, highest_to_remove.length - allowed_invisible_elements);
|
|
636
|
+
|
|
637
|
+
for (let element of highest_to_remove) {
|
|
638
|
+
this._hideElement(element, 'bottom');
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
if (lowest_to_remove.length > allowed_invisible_elements) {
|
|
643
|
+
lowest_to_remove = lowest_to_remove.slice(0, lowest_to_remove.length - allowed_invisible_elements);
|
|
644
|
+
|
|
645
|
+
for (let element of lowest_to_remove) {
|
|
646
|
+
this._hideElement(element, 'top');
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Hide the given element
|
|
653
|
+
*
|
|
654
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
655
|
+
* @since 0.2.10
|
|
656
|
+
* @version 0.2.10
|
|
657
|
+
*/
|
|
658
|
+
VirtualScroll.setMethod(function _hideElement(element, removed_from_side) {
|
|
659
|
+
this.increaseScrollPaddingBecauseRemoved(removed_from_side, element);
|
|
660
|
+
element.remove();
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* Get the scrollpadding for the given side
|
|
665
|
+
*
|
|
666
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
667
|
+
* @since 0.2.10
|
|
668
|
+
* @version 0.2.10
|
|
669
|
+
*/
|
|
670
|
+
VirtualScroll.setMethod(function getScrollPadding(side) {
|
|
671
|
+
|
|
672
|
+
if (typeof side != 'string') {
|
|
673
|
+
side = this.bottom_trigger == side ? 'bottom' : 'top';
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
let result = this[side + '_scroll_padding'];
|
|
677
|
+
|
|
678
|
+
if (!result) {
|
|
679
|
+
result = 0;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
return result;
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* Set the scrollpadding for the given side
|
|
687
|
+
*
|
|
688
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
689
|
+
* @since 0.2.10
|
|
690
|
+
* @version 0.2.10
|
|
691
|
+
*/
|
|
692
|
+
VirtualScroll.setMethod(function setScrollPadding(side, amount) {
|
|
693
|
+
|
|
694
|
+
if (!amount || amount < 0) {
|
|
695
|
+
amount = 0;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
this[side + '_scroll_padding'] = amount;
|
|
699
|
+
|
|
700
|
+
let trigger = this[side + '_trigger'];
|
|
701
|
+
|
|
702
|
+
if (!trigger) {
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
let style = trigger.style;
|
|
707
|
+
|
|
708
|
+
if (amount > 0) {
|
|
709
|
+
style.height = amount + 'px';
|
|
710
|
+
} else {
|
|
711
|
+
style.height = '';
|
|
712
|
+
}
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Increase the scroll padding of the given side with the given element height
|
|
717
|
+
* because it has been removed from the given side
|
|
718
|
+
*
|
|
719
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
720
|
+
* @since 0.2.10
|
|
721
|
+
* @version 0.2.10
|
|
722
|
+
*
|
|
723
|
+
* @param {String} side
|
|
724
|
+
* @param {Element} element
|
|
725
|
+
*/
|
|
726
|
+
VirtualScroll.setMethod(function increaseScrollPaddingBecauseRemoved(side, element) {
|
|
727
|
+
element.dataset.removedFromSide = side;
|
|
728
|
+
this.increaseScrollPadding(side, element);
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* Increase the scroll padding of the given side with the given element height.
|
|
733
|
+
*
|
|
734
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
735
|
+
* @since 0.2.10
|
|
736
|
+
* @version 0.2.10
|
|
737
|
+
*/
|
|
738
|
+
VirtualScroll.setMethod(function increaseScrollPadding(side, element) {
|
|
739
|
+
|
|
740
|
+
let amount = element.offsetHeight;
|
|
741
|
+
|
|
742
|
+
let current = this.getScrollPadding(side);
|
|
743
|
+
this.setScrollPadding(side, current + amount);
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
/**
|
|
747
|
+
* Decrease the scroll padding of the given side
|
|
748
|
+
*
|
|
749
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
750
|
+
* @since 0.2.10
|
|
751
|
+
* @version 0.2.10
|
|
752
|
+
*
|
|
753
|
+
* @param {String} side
|
|
754
|
+
*/
|
|
755
|
+
VirtualScroll.setMethod(function decreaseScrollPadding(side, element) {
|
|
756
|
+
let amount = element.offsetHeight;
|
|
757
|
+
let current = this.getScrollPadding(side);
|
|
758
|
+
this.setScrollPadding(side, current - amount);
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
/**
|
|
762
|
+
* Decrease the scroll padding of the given side
|
|
763
|
+
*
|
|
764
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
765
|
+
* @since 0.2.10
|
|
766
|
+
* @version 0.2.10
|
|
767
|
+
*
|
|
768
|
+
* @param {Element} side_trigger
|
|
769
|
+
* @param {Element} element
|
|
770
|
+
*/
|
|
771
|
+
VirtualScroll.setMethod(function decreaseScrollPaddingBecauseInserted(side_trigger, element) {
|
|
772
|
+
|
|
773
|
+
let side = this.bottom_trigger == side_trigger ? 'bottom' : 'top';
|
|
774
|
+
let previous_removed_from_side = element.dataset.removedFromSide;
|
|
775
|
+
|
|
776
|
+
if (previous_removed_from_side) {
|
|
777
|
+
this.decreaseScrollPadding(previous_removed_from_side, element);
|
|
778
|
+
} else {
|
|
779
|
+
this.decreaseScrollPadding(side, element);
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
element.removeAttribute('data-removed-from-side');
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* Add an element adjacent to the given trigger
|
|
787
|
+
*
|
|
788
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
789
|
+
* @since 0.2.10
|
|
790
|
+
* @version 0.2.10
|
|
791
|
+
*
|
|
792
|
+
* @param {Element} trigger_element
|
|
793
|
+
* @param {KeyedNode} node
|
|
794
|
+
*
|
|
795
|
+
* @return {boolean}
|
|
796
|
+
*/
|
|
797
|
+
VirtualScroll.setMethod(function _addAdjacent(trigger_element, node) {
|
|
798
|
+
|
|
799
|
+
let position = trigger_element == this.bottom_trigger ? 'beforebegin' : 'afterend';
|
|
800
|
+
let is_top = trigger_element == this.top_trigger;
|
|
801
|
+
|
|
802
|
+
let entry = node?.value;
|
|
803
|
+
|
|
804
|
+
if (!entry) {
|
|
805
|
+
return false;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
let element = this.getEntryElement(entry);
|
|
809
|
+
|
|
810
|
+
if (!element) {
|
|
811
|
+
return false;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
let current_scroll = this.scrollTop,
|
|
815
|
+
first_element = this.top_trigger.nextElementSibling,
|
|
816
|
+
first_offset = first_element?.offsetTop;
|
|
817
|
+
|
|
818
|
+
trigger_element.insertAdjacentElement(position, element);
|
|
819
|
+
this.decreaseScrollPaddingBecauseInserted(trigger_element, element);
|
|
820
|
+
|
|
821
|
+
if (is_top && first_offset != null) {
|
|
822
|
+
let new_offset = first_element.offsetTop;
|
|
823
|
+
let difference = new_offset - first_offset;
|
|
824
|
+
let new_scroll = difference + current_scroll;
|
|
825
|
+
this.scrollTop = new_scroll;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
return true;
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
/**
|
|
832
|
+
* Get the entry element
|
|
833
|
+
*
|
|
834
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
835
|
+
* @since 0.2.10
|
|
836
|
+
* @version 0.2.10
|
|
837
|
+
*/
|
|
838
|
+
VirtualScroll.setMethod(function getEntryElement(entry) {
|
|
839
|
+
|
|
840
|
+
if (!entry) {
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
let result = entry[DOM_ELEMENT];
|
|
845
|
+
|
|
846
|
+
if (!result) {
|
|
847
|
+
result = this.createElement('div');
|
|
848
|
+
entry[DOM_ELEMENT] = result;
|
|
849
|
+
result.textContent = entry.record.name;
|
|
850
|
+
result.dataset.loadedEntryKey = entry.key;
|
|
851
|
+
|
|
852
|
+
if (typeof hawkejs !== 'undefined') {
|
|
853
|
+
let placeholder = hawkejs.scene.general_renderer.partial(this.item_template, {
|
|
854
|
+
record: entry.record
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
result.append(placeholder);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
return result;
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
/**
|
|
865
|
+
* Added to the DOM for the first time
|
|
866
|
+
*
|
|
867
|
+
* @author Jelle De Loecker <jelle@elevenways.be>
|
|
868
|
+
* @since 0.2.10
|
|
869
|
+
* @version 0.2.10
|
|
870
|
+
*/
|
|
871
|
+
VirtualScroll.setMethod(function introduced() {
|
|
872
|
+
this.ensureTriggerElements();
|
|
873
|
+
});
|