@tko/bind 4.0.0-alpha8.4 → 4.0.0-beta1.3

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/dist/bind.es6.js DELETED
@@ -1,996 +0,0 @@
1
- /*!
2
- * TKO DOM-Observable Binding 🥊 @tko/bind@4.0.0-alpha8.4
3
- * (c) The Knockout.js Team - https://tko.io
4
- * License: MIT (http://www.opensource.org/licenses/mit-license.php)
5
- */
6
-
7
- import { domData, extend, options, anyDomNodeIsAttachedToDocument, virtualElements, objectMap, tagNameLower, objectForEach, arrayIndexOf, arrayForEach, fixUpContinuousNodeArray, replaceDomNodes, arrayPushAll, arrayMap, cleanNode, removeNode, compareArrays } from '@tko/utils';
8
- import { subscribable, unwrap, isObservable, isWriteableObservable, dependencyDetection, observable } from '@tko/observable';
9
- import { computed, pureComputed } from '@tko/computed';
10
- import { LifeCycle } from '@tko/lifecycle';
11
-
12
- const contextAncestorBindingInfo = Symbol('_ancestorBindingInfo');
13
- const boundElementDomDataKey = domData.nextKey();
14
-
15
- const bindingEvent = {
16
- childrenComplete: 'childrenComplete',
17
- descendantsComplete: 'descendantsComplete',
18
-
19
- subscribe (node, event, callback, context) {
20
- const bindingInfo = domData.getOrSet(node, boundElementDomDataKey, {});
21
- if (!bindingInfo.eventSubscribable) {
22
- bindingInfo.eventSubscribable = new subscribable();
23
- }
24
- return bindingInfo.eventSubscribable.subscribe(callback, context, event)
25
- },
26
-
27
- notify (node, event) {
28
- const bindingInfo = domData.get(node, boundElementDomDataKey);
29
- if (bindingInfo) {
30
- if (bindingInfo.eventSubscribable) {
31
- bindingInfo.eventSubscribable.notifySubscribers(node, event);
32
- }
33
- }
34
- }
35
- };
36
-
37
- const boundElementDomDataKey$1 = domData.nextKey();
38
-
39
- const contextSubscribeSymbol = Symbol('Knockout Context Subscription');
40
-
41
- // Unique stub to indicate inheritance.
42
- const inheritParentIndicator = Symbol('Knockout Parent Indicator');
43
-
44
- // The bindingContext constructor is only called directly to create the root context. For child
45
- // contexts, use bindingContext.createChildContext or bindingContext.extend.
46
- function bindingContext (dataItemOrAccessor, parentContext, dataItemAlias, extendCallback, settings) {
47
- const self = this;
48
- const shouldInheritData = dataItemOrAccessor === inheritParentIndicator;
49
- const realDataItemOrAccessor = shouldInheritData ? undefined : dataItemOrAccessor;
50
- const isFunc = typeof realDataItemOrAccessor === 'function' && !isObservable(realDataItemOrAccessor);
51
-
52
- // Export 'ko' in the binding context so it will be available in bindings and templates
53
- // even if 'ko' isn't exported as a global, such as when using an AMD loader.
54
- // See https://github.com/SteveSanderson/knockout/issues/490
55
- self.ko = options.knockoutInstance;
56
- let subscribable$$1;
57
-
58
- // The binding context object includes static properties for the current, parent, and root view models.
59
- // If a view model is actually stored in an observable, the corresponding binding context object, and
60
- // any child contexts, must be updated when the view model is changed.
61
- function updateContext () {
62
- // Most of the time, the context will directly get a view model object, but if a function is given,
63
- // we call the function to retrieve the view model. If the function accesses any observables or returns
64
- // an observable, the dependency is tracked, and those observables can later cause the binding
65
- // context to be updated.
66
- const dataItemOrObservable = isFunc ? realDataItemOrAccessor() : realDataItemOrAccessor;
67
- let dataItem = unwrap(dataItemOrObservable);
68
-
69
- if (parentContext) {
70
- // When a "parent" context is given, register a dependency on the parent context. Thus whenever the
71
- // parent context is updated, this context will also be updated.
72
- if (parentContext[contextSubscribeSymbol]) {
73
- parentContext[contextSubscribeSymbol]();
74
- }
75
-
76
- // Copy $root and any custom properties from the parent context
77
- extend(self, parentContext);
78
-
79
- // Copy Symbol properties
80
- if (contextAncestorBindingInfo in parentContext) {
81
- self[contextAncestorBindingInfo] = parentContext[contextAncestorBindingInfo];
82
- }
83
- } else {
84
- self.$parents = [];
85
- self.$root = dataItem;
86
- }
87
-
88
- self[contextSubscribeSymbol] = subscribable$$1;
89
-
90
- if (shouldInheritData) {
91
- dataItem = self.$data;
92
- } else {
93
- self.$rawData = dataItemOrObservable;
94
- self.$data = dataItem;
95
- }
96
-
97
- if (dataItemAlias) { self[dataItemAlias] = dataItem; }
98
-
99
- // The extendCallback function is provided when creating a child context or extending a context.
100
- // It handles the specific actions needed to finish setting up the binding context. Actions in this
101
- // function could also add dependencies to this binding context.
102
- if (extendCallback) { extendCallback(self, parentContext, dataItem); }
103
-
104
- return self.$data
105
- }
106
-
107
- if (settings && settings.exportDependencies) {
108
- // The "exportDependencies" option means that the calling code will track any dependencies and re-create
109
- // the binding context when they change.
110
- updateContext();
111
- } else {
112
- subscribable$$1 = pureComputed(updateContext);
113
- subscribable$$1.peek();
114
-
115
- // At this point, the binding context has been initialized, and the "subscribable" computed observable is
116
- // subscribed to any observables that were accessed in the process. If there is nothing to track, the
117
- // computed will be inactive, and we can safely throw it away. If it's active, the computed is stored in
118
- // the context object.
119
- if (subscribable$$1.isActive()) {
120
- self[contextSubscribeSymbol] = subscribable$$1;
121
-
122
- // Always notify because even if the model ($data) hasn't changed, other context properties might have changed
123
- subscribable$$1['equalityComparer'] = null;
124
- } else {
125
- self[contextSubscribeSymbol] = undefined;
126
- }
127
- }
128
- }
129
-
130
- Object.assign(bindingContext.prototype, {
131
-
132
- lookup (token, globals, node) {
133
- // short circuits
134
- switch (token) {
135
- case '$element': return node
136
- case '$context': return this
137
- case 'this': case '$data': return this.$data
138
- }
139
- const $data = this.$data;
140
- // instanceof Object covers 1. {}, 2. [], 3. function() {}, 4. new *; it excludes undefined, null, primitives.
141
- if ($data instanceof Object && token in $data) { return $data[token] }
142
- if (token in this) { return this[token] }
143
- if (token in globals) { return globals[token] }
144
-
145
- throw new Error(`The variable "${token}" was not found on $data, $context, or globals.`)
146
- },
147
-
148
- // Extend the binding context hierarchy with a new view model object. If the parent context is watching
149
- // any observables, the new child context will automatically get a dependency on the parent context.
150
- // But this does not mean that the $data value of the child context will also get updated. If the child
151
- // view model also depends on the parent view model, you must provide a function that returns the correct
152
- // view model on each update.
153
- createChildContext (dataItemOrAccessor, dataItemAlias, extendCallback, settings) {
154
- return new bindingContext(dataItemOrAccessor, this, dataItemAlias, function (self, parentContext) {
155
- // Extend the context hierarchy by setting the appropriate pointers
156
- self.$parentContext = parentContext;
157
- self.$parent = parentContext.$data;
158
- self.$parents = (parentContext.$parents || []).slice(0);
159
- self.$parents.unshift(self.$parent);
160
- if (extendCallback) { extendCallback(self); }
161
- }, settings)
162
- },
163
-
164
- // Extend the binding context with new custom properties. This doesn't change the context hierarchy.
165
- // Similarly to "child" contexts, provide a function here to make sure that the correct values are set
166
- // when an observable view model is updated.
167
- extend (properties) {
168
- // If the parent context references an observable view model, "_subscribable" will always be the
169
- // latest view model object. If not, "_subscribable" isn't set, and we can use the static "$data" value.
170
- return new bindingContext(inheritParentIndicator, this, null, function (self, parentContext) {
171
- extend(self, typeof properties === 'function' ? properties.call(self) : properties);
172
- })
173
- },
174
-
175
- createStaticChildContext (dataItemOrAccessor, dataItemAlias) {
176
- return this.createChildContext(dataItemOrAccessor, dataItemAlias, null, { 'exportDependencies': true })
177
- }
178
- });
179
-
180
- function storedBindingContextForNode (node) {
181
- const bindingInfo = domData.get(node, boundElementDomDataKey$1);
182
- return bindingInfo && bindingInfo.context
183
- }
184
-
185
- // Retrieving binding context from arbitrary nodes
186
- function contextFor (node) {
187
- // We can only do something meaningful for elements and comment nodes (in particular, not text nodes, as IE can't store domdata for them)
188
- if (node && (node.nodeType === 1 || node.nodeType === 8)) {
189
- return storedBindingContextForNode(node)
190
- }
191
- }
192
-
193
- function dataFor (node) {
194
- var context = contextFor(node);
195
- return context ? context.$data : undefined
196
- }
197
-
198
- class BindingResult {
199
- constructor ({asyncBindingsApplied, rootNode, bindingContext}) {
200
- Object.assign(this, {
201
- rootNode,
202
- bindingContext,
203
- isSync: asyncBindingsApplied.size === 0,
204
- isComplete: this.isSync
205
- });
206
-
207
- if (!this.isSync) {
208
- this.completionPromise = this.completeWhenBindingsFinish(asyncBindingsApplied);
209
- }
210
- }
211
-
212
- async completeWhenBindingsFinish (asyncBindingsApplied) {
213
- await Promise.all(asyncBindingsApplied);
214
- this.isComplete = true;
215
- return this
216
- }
217
- }
218
-
219
- class BindingHandler extends LifeCycle {
220
- constructor (params) {
221
- super();
222
- const {$element, valueAccessor, allBindings, $context} = params;
223
- Object.assign(this, {
224
- valueAccessor,
225
- allBindings,
226
- $element,
227
- $context,
228
- $data: $context.$data
229
- });
230
-
231
- this.anchorTo($element);
232
- }
233
-
234
- get value () { return this.valueAccessor() }
235
- set value (v) {
236
- const va = this.valueAccessor();
237
- if (isWriteableObservable(va)) {
238
- va(v);
239
- } else {
240
- this.valueAccessor(v);
241
- }
242
- }
243
-
244
- get controlsDescendants () { return false }
245
-
246
- static get allowVirtualElements () { return false }
247
- static get isBindingHandlerClass () { return true }
248
-
249
- /* Overload this for asynchronous bindings or bindings that recursively
250
- apply bindings (e.g. components, foreach, template).
251
-
252
- A binding should be complete when it has run through once, notably
253
- in server-side bindings for pre-rendering.
254
- */
255
- get bindingCompleted () { return true }
256
-
257
- static registerAs (name, provider = options.bindingProviderInstance) {
258
- provider.bindingHandlers.set(name, this);
259
- }
260
- }
261
-
262
- /**
263
- * An AsyncBindingHandler shall call `completeBinding` when the binding
264
- * is to be considered complete.
265
- */
266
- const ResolveSymbol = Symbol('Async Binding Resolved');
267
-
268
- class AsyncBindingHandler extends BindingHandler {
269
- constructor (params) {
270
- super(params);
271
- this.bindingCompletion = new Promise((resolve) => {
272
- this[ResolveSymbol] = resolve;
273
- });
274
- this.completeBinding = bindingResult => this[ResolveSymbol](bindingResult);
275
- }
276
-
277
- get bindingCompleted () { return this.bindingCompletion }
278
- }
279
-
280
- /**
281
- * We have no guarantees, for users employing legacy bindings,
282
- * that it has not been changed with a modification like
283
- *
284
- * ko.bindingHandlers[name] = { init: ...}
285
- *
286
- * ... so we have to keep track by way of a map.
287
- */
288
- const PossibleWeakMap = options.global.WeakMap || Map;
289
- const legacyBindingMap = new PossibleWeakMap();
290
-
291
- class LegacyBindingHandler extends BindingHandler {
292
- constructor (params) {
293
- super(params);
294
- const handler = this.handler;
295
- this.onError = params.onError;
296
-
297
- if (typeof handler.dispose === 'function') {
298
- this.addDisposable(handler);
299
- }
300
-
301
- try {
302
- this.initReturn = handler.init && handler.init(...this.legacyArgs);
303
- } catch (e) {
304
- params.onError('init', e);
305
- }
306
- }
307
-
308
- onValueChange () {
309
- const handler = this.handler;
310
- if (typeof handler.update !== 'function') { return }
311
- try {
312
- handler.update(...this.legacyArgs);
313
- } catch (e) {
314
- this.onError('update', e);
315
- }
316
- }
317
-
318
- get legacyArgs () {
319
- return [
320
- this.$element, this.valueAccessor, this.allBindings,
321
- this.$data, this.$context
322
- ]
323
- }
324
-
325
- get controlsDescendants () {
326
- const objectToTest = this.initReturn || this.handler || {};
327
- return objectToTest.controlsDescendantBindings
328
- }
329
-
330
- /**
331
- * Create a handler instance from the `origin`, which may be:
332
- *
333
- * 1. an object (becomes LegacyBindingHandler)
334
- * 2. a function (becomes LegacyBindingHandler with `init: function`)
335
- *
336
- * If given an object (the only kind supported in knockout 3.x and before), it
337
- * shall draw the `init`, `update`, and `allowVirtualElements` properties
338
- */
339
- static getOrCreateFor (key, handler) {
340
- if (legacyBindingMap.has(handler)) {
341
- return legacyBindingMap.get(handler)
342
- }
343
- const newLegacyHandler = this.createFor(key, handler);
344
- legacyBindingMap.set(handler, newLegacyHandler);
345
- return newLegacyHandler
346
- }
347
-
348
- static createFor (key, handler) {
349
- if (typeof handler === 'function') {
350
- const [initFn, disposeFn] = [handler, handler.dispose];
351
- return class extends LegacyBindingHandler {
352
- get handler () {
353
- const init = initFn.bind(this);
354
- const dispose = disposeFn ? disposeFn.bind(this) : null;
355
- return { init, dispose }
356
- }
357
- static get after () { return handler.after }
358
- static get allowVirtualElements () {
359
- return handler.allowVirtualElements || virtualElements.allowedBindings[key]
360
- }
361
- }
362
- }
363
-
364
- if (typeof handler === 'object') {
365
- return class extends LegacyBindingHandler {
366
- get handler () { return handler }
367
- static get after () { return handler.after }
368
- static get allowVirtualElements () {
369
- return handler.allowVirtualElements || virtualElements.allowedBindings[key]
370
- }
371
- }
372
- }
373
-
374
- throw new Error('The given handler is not an appropriate type.')
375
- }
376
- }
377
-
378
- // The following element types will not be recursed into during binding.
379
- const bindingDoesNotRecurseIntoElementTypes = {
380
- // Don't want bindings that operate on text nodes to mutate <script> and <textarea> contents,
381
- // because it's unexpected and a potential XSS issue.
382
- // Also bindings should not operate on <template> elements since this breaks in Internet Explorer
383
- // and because such elements' contents are always intended to be bound in a different context
384
- // from where they appear in the document.
385
- 'script': true,
386
- 'textarea': true,
387
- 'template': true
388
- };
389
-
390
- function getBindingProvider () {
391
- return options.bindingProviderInstance.instance || options.bindingProviderInstance
392
- }
393
-
394
- function isProviderForNode (provider, node) {
395
- const nodeTypes = provider.FOR_NODE_TYPES || [1, 3, 8];
396
- return nodeTypes.includes(node.nodeType)
397
- }
398
-
399
- function asProperHandlerClass (handler, bindingKey) {
400
- if (!handler) { return }
401
- return handler.isBindingHandlerClass ? handler
402
- : LegacyBindingHandler.getOrCreateFor(bindingKey, handler)
403
- }
404
-
405
- function getBindingHandlerFromComponent (bindingKey, $component) {
406
- if (!$component || typeof $component.getBindingHandler !== 'function') { return }
407
- return asProperHandlerClass($component.getBindingHandler(bindingKey))
408
- }
409
-
410
- function getBindingHandler (bindingKey) {
411
- const bindingDefinition = options.getBindingHandler(bindingKey) || getBindingProvider().bindingHandlers.get(bindingKey);
412
- return asProperHandlerClass(bindingDefinition, bindingKey)
413
- }
414
-
415
- // Returns the value of a valueAccessor function
416
- function evaluateValueAccessor (valueAccessor) {
417
- return valueAccessor()
418
- }
419
-
420
- function applyBindingsToDescendantsInternal (bindingContext$$1, elementOrVirtualElement, asyncBindingsApplied) {
421
- let nextInQueue = virtualElements.firstChild(elementOrVirtualElement);
422
-
423
- if (!nextInQueue) { return }
424
-
425
- let currentChild;
426
- const provider = getBindingProvider();
427
- const preprocessNode = provider.preprocessNode;
428
-
429
- // Preprocessing allows a binding provider to mutate a node before bindings are applied to it. For example it's
430
- // possible to insert new siblings after it, and/or replace the node with a different one. This can be used to
431
- // implement custom binding syntaxes, such as {{ value }} for string interpolation, or custom element types that
432
- // trigger insertion of <template> contents at that point in the document.
433
- if (preprocessNode) {
434
- while (currentChild = nextInQueue) {
435
- nextInQueue = virtualElements.nextSibling(currentChild);
436
- preprocessNode.call(provider, currentChild);
437
- }
438
-
439
- // Reset nextInQueue for the next loop
440
- nextInQueue = virtualElements.firstChild(elementOrVirtualElement);
441
- }
442
-
443
- while (currentChild = nextInQueue) {
444
- // Keep a record of the next child *before* applying bindings, in case the binding removes the current child from its position
445
- nextInQueue = virtualElements.nextSibling(currentChild);
446
- applyBindingsToNodeAndDescendantsInternal(bindingContext$$1, currentChild, asyncBindingsApplied);
447
- }
448
-
449
- bindingEvent.notify(elementOrVirtualElement, bindingEvent.childrenComplete);
450
- }
451
-
452
- function hasBindings (node) {
453
- const provider = getBindingProvider();
454
- return isProviderForNode(provider, node) && provider.nodeHasBindings(node)
455
- }
456
-
457
- function nodeOrChildHasBindings (node) {
458
- return hasBindings(node) || [...node.childNodes].some(c => nodeOrChildHasBindings(c))
459
- }
460
-
461
- function applyBindingsToNodeAndDescendantsInternal (bindingContext$$1, nodeVerified, asyncBindingsApplied) {
462
- var isElement = nodeVerified.nodeType === 1;
463
- if (isElement) { // Workaround IE <= 8 HTML parsing weirdness
464
- virtualElements.normaliseVirtualElementDomStructure(nodeVerified);
465
- }
466
-
467
- // Perf optimisation: Apply bindings only if...
468
- // (1) We need to store the binding info for the node (all element nodes)
469
- // (2) It might have bindings (e.g., it has a data-bind attribute, or it's a marker for a containerless template)
470
-
471
- let shouldApplyBindings = isElement || // Case (1)
472
- hasBindings(nodeVerified); // Case (2)
473
-
474
- const { shouldBindDescendants } = shouldApplyBindings
475
- ? applyBindingsToNodeInternal(nodeVerified, null, bindingContext$$1, asyncBindingsApplied)
476
- : { shouldBindDescendants: true };
477
-
478
- if (shouldBindDescendants && !bindingDoesNotRecurseIntoElementTypes[tagNameLower(nodeVerified)]) {
479
- // We're recursing automatically into (real or virtual) child nodes without changing binding contexts. So,
480
- // * For children of a *real* element, the binding context is certainly the same as on their DOM .parentNode,
481
- // hence bindingContextsMayDifferFromDomParentElement is false
482
- // * For children of a *virtual* element, we can't be sure. Evaluating .parentNode on those children may
483
- // skip over any number of intermediate virtual elements, any of which might define a custom binding context,
484
- // hence bindingContextsMayDifferFromDomParentElement is true
485
- applyBindingsToDescendantsInternal(bindingContext$$1, nodeVerified, asyncBindingsApplied);
486
- }
487
- }
488
-
489
-
490
- function * topologicalSortBindings (bindings, $component) {
491
- const results = [];
492
- // Depth-first sort
493
- const bindingsConsidered = {}; // A temporary record of which bindings are already in 'result'
494
- const cyclicDependencyStack = []; // Keeps track of a depth-search so that, if there's a cycle, we know which bindings caused it
495
-
496
- objectForEach(bindings, function pushBinding (bindingKey) {
497
- if (!bindingsConsidered[bindingKey]) {
498
- const binding = getBindingHandlerFromComponent(bindingKey, $component) || getBindingHandler(bindingKey);
499
- if (!binding) { return }
500
- // First add dependencies (if any) of the current binding
501
- if (binding.after) {
502
- cyclicDependencyStack.push(bindingKey);
503
- arrayForEach(binding.after, function (bindingDependencyKey) {
504
- if (!bindings[bindingDependencyKey]) { return }
505
- if (arrayIndexOf(cyclicDependencyStack, bindingDependencyKey) !== -1) {
506
- throw Error('Cannot combine the following bindings, because they have a cyclic dependency: ' + cyclicDependencyStack.join(', '))
507
- } else {
508
- pushBinding(bindingDependencyKey);
509
- }
510
- });
511
- cyclicDependencyStack.length--;
512
- }
513
- // Next add the current binding
514
- results.push([ bindingKey, binding ]);
515
- }
516
- bindingsConsidered[bindingKey] = true;
517
- });
518
-
519
- for (const result of results) { yield result; }
520
- }
521
-
522
- function applyBindingsToNodeInternal (node, sourceBindings, bindingContext$$1, asyncBindingsApplied) {
523
- const bindingInfo = domData.getOrSet(node, boundElementDomDataKey$1, {});
524
- // Prevent multiple applyBindings calls for the same node, except when a binding value is specified
525
- const alreadyBound = bindingInfo.alreadyBound;
526
- if (!sourceBindings) {
527
- if (alreadyBound) {
528
- if (!nodeOrChildHasBindings(node)) { return false }
529
- onBindingError({
530
- during: 'apply',
531
- errorCaptured: new Error('You cannot apply bindings multiple times to the same element.'),
532
- element: node,
533
- bindingContext: bindingContext$$1
534
- });
535
- return false
536
- }
537
- bindingInfo.alreadyBound = true;
538
- }
539
-
540
- if (!alreadyBound) {
541
- bindingInfo.context = bindingContext$$1;
542
- }
543
-
544
- // Use bindings if given, otherwise fall back on asking the bindings provider to give us some bindings
545
- var bindings;
546
- if (sourceBindings && typeof sourceBindings !== 'function') {
547
- bindings = sourceBindings;
548
- } else {
549
- const provider = getBindingProvider();
550
- const getBindings = provider.getBindingAccessors;
551
-
552
- if (isProviderForNode(provider, node)) {
553
- // Get the binding from the provider within a computed observable so that we can update the bindings whenever
554
- // the binding context is updated or if the binding provider accesses observables.
555
- var bindingsUpdater = computed(
556
- function () {
557
- bindings = sourceBindings ? sourceBindings(bindingContext$$1, node) : getBindings.call(provider, node, bindingContext$$1);
558
- // Register a dependency on the binding context to support observable view models.
559
- if (bindings && bindingContext$$1[contextSubscribeSymbol]) { bindingContext$$1[contextSubscribeSymbol](); }
560
- return bindings
561
- },
562
- null, { disposeWhenNodeIsRemoved: node }
563
- );
564
-
565
- if (!bindings || !bindingsUpdater.isActive()) { bindingsUpdater = null; }
566
- }
567
- }
568
-
569
- var bindingHandlerThatControlsDescendantBindings;
570
- if (bindings) {
571
- const $component = bindingContext$$1.$component || {};
572
-
573
- const allBindingHandlers = {};
574
- domData.set(node, 'bindingHandlers', allBindingHandlers);
575
-
576
- // Return the value accessor for a given binding. When bindings are static (won't be updated because of a binding
577
- // context update), just return the value accessor from the binding. Otherwise, return a function that always gets
578
- // the latest binding value and registers a dependency on the binding updater.
579
- const getValueAccessor = bindingsUpdater
580
- ? (bindingKey) => function (optionalValue) {
581
- const valueAccessor = bindingsUpdater()[bindingKey];
582
- if (arguments.length === 0) {
583
- return evaluateValueAccessor(valueAccessor)
584
- } else {
585
- return valueAccessor(optionalValue)
586
- }
587
- } : (bindingKey) => bindings[bindingKey];
588
-
589
- // Use of allBindings as a function is maintained for backwards compatibility, but its use is deprecated
590
- function allBindings () {
591
- return objectMap(bindingsUpdater ? bindingsUpdater() : bindings, evaluateValueAccessor)
592
- }
593
-
594
- // The following is the 3.x allBindings API
595
- allBindings.has = (key) => key in bindings;
596
- allBindings.get = (key) => bindings[key] && evaluateValueAccessor(getValueAccessor(key));
597
-
598
- if (bindingEvent.childrenComplete in bindings) {
599
- bindingEvent.subscribe(node, bindingEvent.childrenComplete, () => {
600
- const callback = evaluateValueAccessor(bindings[bindingEvent.childrenComplete]);
601
- if (!callback) { return }
602
- const nodes = virtualElements.childNodes(node);
603
- if (nodes.length) { callback(nodes, dataFor(nodes[0])); }
604
- });
605
- }
606
-
607
- const bindingsGenerated = topologicalSortBindings(bindings, $component);
608
- const nodeAsyncBindingPromises = new Set();
609
- for (const [key, BindingHandlerClass] of bindingsGenerated) {
610
- // Go through the sorted bindings, calling init and update for each
611
- function reportBindingError (during, errorCaptured) {
612
- onBindingError({
613
- during,
614
- errorCaptured,
615
- bindings,
616
- allBindings,
617
- bindingKey: key,
618
- bindingContext: bindingContext$$1,
619
- element: node,
620
- valueAccessor: getValueAccessor(key)
621
- });
622
- }
623
-
624
- if (node.nodeType === 8 && !BindingHandlerClass.allowVirtualElements) {
625
- throw new Error(`The binding '${key}' cannot be used with virtual elements`)
626
- }
627
-
628
- try {
629
- const bindingHandler = dependencyDetection.ignore(() =>
630
- new BindingHandlerClass({
631
- allBindings,
632
- $element: node,
633
- $context: bindingContext$$1,
634
- onError: reportBindingError,
635
- valueAccessor (...v) { return getValueAccessor(key)(...v) }
636
- })
637
- );
638
-
639
- if (bindingHandler.onValueChange) {
640
- dependencyDetection.ignore(() =>
641
- bindingHandler.computed('onValueChange')
642
- );
643
- }
644
-
645
- // Expose the bindings via domData.
646
- allBindingHandlers[key] = bindingHandler;
647
-
648
- if (bindingHandler.controlsDescendants) {
649
- if (bindingHandlerThatControlsDescendantBindings !== undefined) { throw new Error('Multiple bindings (' + bindingHandlerThatControlsDescendantBindings + ' and ' + key + ') are trying to control descendant bindings of the same element. You cannot use these bindings together on the same element.') }
650
- bindingHandlerThatControlsDescendantBindings = key;
651
- }
652
-
653
- if (bindingHandler.bindingCompleted instanceof Promise) {
654
- asyncBindingsApplied.add(bindingHandler.bindingCompleted);
655
- nodeAsyncBindingPromises.add(bindingHandler.bindingCompleted);
656
- }
657
- } catch (err) {
658
- reportBindingError('creation', err);
659
- }
660
- }
661
-
662
- triggerDescendantsComplete(node, bindings, nodeAsyncBindingPromises);
663
- }
664
-
665
- const shouldBindDescendants = bindingHandlerThatControlsDescendantBindings === undefined;
666
- return { shouldBindDescendants }
667
- }
668
-
669
- /**
670
- *
671
- * @param {HTMLElement} node
672
- * @param {Object} bindings
673
- * @param {[Promise]} nodeAsyncBindingPromises
674
- */
675
- function triggerDescendantsComplete (node, bindings, nodeAsyncBindingPromises) {
676
- /** descendantsComplete ought to be an instance of the descendantsComplete
677
- * binding handler. */
678
- const hasBindingHandler = bindingEvent.descendantsComplete in bindings;
679
- const hasFirstChild = virtualElements.firstChild(node);
680
- const accessor = hasBindingHandler && evaluateValueAccessor(bindings[bindingEvent.descendantsComplete]);
681
- const callback = () => {
682
- bindingEvent.notify(node, bindingEvent.descendantsComplete);
683
- if (accessor && hasFirstChild) { accessor(node); }
684
- };
685
- if (nodeAsyncBindingPromises.size) {
686
- Promise.all(nodeAsyncBindingPromises).then(callback);
687
- } else {
688
- callback();
689
- }
690
- }
691
-
692
-
693
- function getBindingContext (viewModelOrBindingContext, extendContextCallback) {
694
- return viewModelOrBindingContext && (viewModelOrBindingContext instanceof bindingContext)
695
- ? viewModelOrBindingContext
696
- : new bindingContext(viewModelOrBindingContext, undefined, undefined, extendContextCallback)
697
- }
698
-
699
- function applyBindingAccessorsToNode (node, bindings, viewModelOrBindingContext, asyncBindingsApplied) {
700
- if (node.nodeType === 1) { // If it's an element, workaround IE <= 8 HTML parsing weirdness
701
- virtualElements.normaliseVirtualElementDomStructure(node);
702
- }
703
- return applyBindingsToNodeInternal(node, bindings, getBindingContext(viewModelOrBindingContext), asyncBindingsApplied)
704
- }
705
-
706
- function applyBindingsToNode (node, bindings, viewModelOrBindingContext) {
707
- const asyncBindingsApplied = new Set();
708
- const bindingContext$$1 = getBindingContext(viewModelOrBindingContext);
709
- const bindingAccessors = getBindingProvider().makeBindingAccessors(bindings, bindingContext$$1, node);
710
- applyBindingAccessorsToNode(node, bindingAccessors, bindingContext$$1, asyncBindingsApplied);
711
- return new BindingResult({asyncBindingsApplied, rootNode: node, bindingContext: bindingContext$$1})
712
- }
713
-
714
- function applyBindingsToDescendants (viewModelOrBindingContext, rootNode) {
715
- const asyncBindingsApplied = new Set();
716
- if (rootNode.nodeType === 1 || rootNode.nodeType === 8) {
717
- const bindingContext$$1 = getBindingContext(viewModelOrBindingContext);
718
- applyBindingsToDescendantsInternal(bindingContext$$1, rootNode, asyncBindingsApplied);
719
- return new BindingResult({asyncBindingsApplied, rootNode, bindingContext: bindingContext$$1})
720
- }
721
- return new BindingResult({asyncBindingsApplied, rootNode})
722
- }
723
-
724
- function applyBindings (viewModelOrBindingContext, rootNode, extendContextCallback) {
725
- const asyncBindingsApplied = new Set();
726
- // If jQuery is loaded after Knockout, we won't initially have access to it. So save it here.
727
- if (!options.jQuery === undefined && options.jQuery) {
728
- options.jQuery = options.jQuery;
729
- }
730
-
731
- // rootNode is optional
732
- if (!rootNode) {
733
- rootNode = window.document.body;
734
- if (!rootNode) {
735
- throw Error('ko.applyBindings: could not find window.document.body; has the document been loaded?')
736
- }
737
- } else if (rootNode.nodeType !== 1 && rootNode.nodeType !== 8) {
738
- throw Error('ko.applyBindings: first parameter should be your view model; second parameter should be a DOM node')
739
- }
740
- const rootContext = getBindingContext(viewModelOrBindingContext, extendContextCallback);
741
- applyBindingsToNodeAndDescendantsInternal(rootContext, rootNode, asyncBindingsApplied);
742
- return Promise.all(asyncBindingsApplied)
743
- }
744
-
745
- function onBindingError (spec) {
746
- var error;
747
- if (spec.bindingKey) {
748
- // During: 'init' or initial 'update'
749
- error = spec.errorCaptured;
750
- spec.message = 'Unable to process binding "' + spec.bindingKey +
751
- '" in binding "' + spec.bindingKey +
752
- '"\nMessage: ' + (error.message ? error.message : error);
753
- } else {
754
- // During: 'apply'
755
- error = spec.errorCaptured;
756
- }
757
- try {
758
- extend(error, spec);
759
- } catch (e) {
760
- // Read-only error e.g. a DOMEXception.
761
- spec.stack = error.stack;
762
- error = new Error(error.message ? error.message : error);
763
- extend(error, spec);
764
- }
765
- options.onError(error);
766
- }
767
-
768
- /* eslint no-cond-assign: 0 */
769
-
770
- // Objective:
771
- // * Given an input array, a container DOM node, and a function from array elements to arrays of DOM nodes,
772
- // map the array elements to arrays of DOM nodes, concatenate together all these arrays, and use them to populate the container DOM node
773
- // * Next time we're given the same combination of things (with the array possibly having mutated), update the container DOM node
774
- // so that its children is again the concatenation of the mappings of the array elements, but don't re-map any array elements that we
775
- // previously mapped - retain those nodes, and just insert/delete other ones
776
-
777
- // "callbackAfterAddingNodes" will be invoked after any "mapping"-generated nodes are inserted into the container node
778
- // You can use this, for example, to activate bindings on those nodes.
779
-
780
- function mapNodeAndRefreshWhenChanged (containerNode, mapping, valueToMap, callbackAfterAddingNodes, index) {
781
- // Map this array value inside a dependentObservable so we re-map when any dependency changes
782
- var mappedNodes = [];
783
- var dependentObservable = computed(function () {
784
- var newMappedNodes = mapping(valueToMap, index, fixUpContinuousNodeArray(mappedNodes, containerNode)) || [];
785
-
786
- // On subsequent evaluations, just replace the previously-inserted DOM nodes
787
- if (mappedNodes.length > 0) {
788
- replaceDomNodes(mappedNodes, newMappedNodes);
789
- if (callbackAfterAddingNodes) { dependencyDetection.ignore(callbackAfterAddingNodes, null, [valueToMap, newMappedNodes, index]); }
790
- }
791
-
792
- // Replace the contents of the mappedNodes array, thereby updating the record
793
- // of which nodes would be deleted if valueToMap was itself later removed
794
- mappedNodes.length = 0;
795
- arrayPushAll(mappedNodes, newMappedNodes);
796
- }, null, { disposeWhenNodeIsRemoved: containerNode, disposeWhen: function () { return !anyDomNodeIsAttachedToDocument(mappedNodes) } });
797
- return { mappedNodes: mappedNodes, dependentObservable: (dependentObservable.isActive() ? dependentObservable : undefined) }
798
- }
799
-
800
- var lastMappingResultDomDataKey = domData.nextKey();
801
- let deletedItemDummyValue = domData.nextKey();
802
-
803
- function setDomNodeChildrenFromArrayMapping (domNode, array, mapping, options$$1, callbackAfterAddingNodes, editScript) {
804
- // Compare the provided array against the previous one
805
- array = array || [];
806
- if (typeof array.length === 'undefined') {
807
- array = [array];
808
- }
809
- options$$1 = options$$1 || {};
810
- let lastMappingResult = domData.get(domNode, lastMappingResultDomDataKey);
811
- let isFirstExecution = !lastMappingResult;
812
-
813
- // Build the new mapping result
814
- var newMappingResult = [];
815
- var lastMappingResultIndex = 0;
816
- var newMappingResultIndex = 0;
817
-
818
- var nodesToDelete = [];
819
- var itemsToProcess = [];
820
- var itemsForBeforeRemoveCallbacks = [];
821
- var itemsForMoveCallbacks = [];
822
- var itemsForAfterAddCallbacks = [];
823
- var mapData;
824
- let countWaitingForRemove = 0;
825
-
826
- function itemAdded (value) {
827
- mapData = { arrayEntry: value, indexObservable: observable(newMappingResultIndex++) };
828
- newMappingResult.push(mapData);
829
- itemsToProcess.push(mapData);
830
- if (!isFirstExecution) {
831
- itemsForAfterAddCallbacks.push(mapData);
832
- }
833
- }
834
-
835
- function itemMovedOrRetained (oldPosition) {
836
- mapData = lastMappingResult[oldPosition];
837
- if (newMappingResultIndex !== oldPosition) {
838
- itemsForMoveCallbacks.push(mapData);
839
- }
840
- // Since updating the index might change the nodes, do so before calling fixUpContinuousNodeArray
841
- mapData.indexObservable(newMappingResultIndex++);
842
- fixUpContinuousNodeArray(mapData.mappedNodes, domNode);
843
- newMappingResult.push(mapData);
844
- itemsToProcess.push(mapData);
845
- }
846
-
847
- function callCallback (callback, items) {
848
- if (callback) {
849
- for (var i = 0, n = items.length; i < n; i++) {
850
- arrayForEach(items[i].mappedNodes, function (node) {
851
- callback(node, i, items[i].arrayEntry);
852
- });
853
- }
854
- }
855
- }
856
-
857
- if (isFirstExecution) {
858
- arrayForEach(array, itemAdded);
859
- } else {
860
- if (!editScript || (lastMappingResult && lastMappingResult['_countWaitingForRemove'])) {
861
- // Compare the provided array against the previous one
862
- var lastArray = isFirstExecution ? [] : arrayMap(lastMappingResult, function (x) { return x.arrayEntry });
863
- var compareOptions = {
864
- 'dontLimitMoves': options$$1['dontLimitMoves'],
865
- 'sparse': true
866
- };
867
- editScript = compareArrays(lastArray, array, compareOptions);
868
- }
869
-
870
- for (var i = 0, editScriptItem, movedIndex, itemIndex; editScriptItem = editScript[i]; i++) {
871
- movedIndex = editScriptItem['moved'];
872
- itemIndex = editScriptItem['index'];
873
- switch (editScriptItem['status']) {
874
- case 'deleted':
875
- while (lastMappingResultIndex < itemIndex) {
876
- itemMovedOrRetained(lastMappingResultIndex++);
877
- }
878
- if (movedIndex === undefined) {
879
- mapData = lastMappingResult[lastMappingResultIndex];
880
-
881
- // Stop tracking changes to the mapping for these nodes
882
- if (mapData.dependentObservable) {
883
- mapData.dependentObservable.dispose();
884
- mapData.dependentObservable = undefined;
885
- }
886
-
887
- // Queue these nodes for later removal
888
- if (fixUpContinuousNodeArray(mapData.mappedNodes, domNode).length) {
889
- if (options$$1['beforeRemove']) {
890
- newMappingResult.push(mapData);
891
- itemsToProcess.push(mapData);
892
- countWaitingForRemove++;
893
- if (mapData.arrayEntry === deletedItemDummyValue) {
894
- mapData = null;
895
- } else {
896
- itemsForBeforeRemoveCallbacks.push(mapData);
897
- }
898
- }
899
- if (mapData) {
900
- nodesToDelete.push.apply(nodesToDelete, mapData.mappedNodes);
901
- }
902
- }
903
- }
904
- lastMappingResultIndex++;
905
- break
906
-
907
- case 'added':
908
- while (newMappingResultIndex < itemIndex) {
909
- itemMovedOrRetained(lastMappingResultIndex++);
910
- }
911
- if (movedIndex !== undefined) {
912
- itemMovedOrRetained(movedIndex);
913
- } else {
914
- itemAdded(editScriptItem['value']);
915
- }
916
- break
917
- }
918
- }
919
-
920
- while (newMappingResultIndex < array.length) {
921
- itemMovedOrRetained(lastMappingResultIndex++);
922
- }
923
-
924
- // Record that the current view may still contain deleted items
925
- // because it means we won't be able to use a provided editScript.
926
- newMappingResult['_countWaitingForRemove'] = countWaitingForRemove;
927
- }
928
-
929
- // Store a copy of the array items we just considered so we can difference it next time
930
- domData.set(domNode, lastMappingResultDomDataKey, newMappingResult);
931
-
932
- // Call beforeMove first before any changes have been made to the DOM
933
- callCallback(options$$1['beforeMove'], itemsForMoveCallbacks);
934
-
935
- // Next remove nodes for deleted items (or just clean if there's a beforeRemove callback)
936
- arrayForEach(nodesToDelete, options$$1['beforeRemove'] ? cleanNode : removeNode);
937
-
938
- // Next add/reorder the remaining items (will include deleted items if there's a beforeRemove callback)
939
- i = 0;
940
- for (var nextNode = virtualElements.firstChild(domNode), lastNode, node; mapData = itemsToProcess[i]; i++) {
941
- // Get nodes for newly added items
942
- if (!mapData.mappedNodes) { extend(mapData, mapNodeAndRefreshWhenChanged(domNode, mapping, mapData.arrayEntry, callbackAfterAddingNodes, mapData.indexObservable)); }
943
-
944
- // Put nodes in the right place if they aren't there already
945
- for (var j = 0; node = mapData.mappedNodes[j]; nextNode = node.nextSibling, lastNode = node, j++) {
946
- if (node !== nextNode) { virtualElements.insertAfter(domNode, node, lastNode); }
947
- }
948
-
949
- // Run the callbacks for newly added nodes (for example, to apply bindings, etc.)
950
- if (!mapData.initialized && callbackAfterAddingNodes) {
951
- callbackAfterAddingNodes(mapData.arrayEntry, mapData.mappedNodes, mapData.indexObservable);
952
- mapData.initialized = true;
953
- }
954
- }
955
-
956
- // If there's a beforeRemove callback, call it after reordering.
957
- // Note that we assume that the beforeRemove callback will usually be used to remove the nodes using
958
- // some sort of animation, which is why we first reorder the nodes that will be removed. If the
959
- // callback instead removes the nodes right away, it would be more efficient to skip reordering them.
960
- // Perhaps we'll make that change in the future if this scenario becomes more common.
961
- callCallback(options$$1['beforeRemove'], itemsForBeforeRemoveCallbacks);
962
-
963
- // Replace the stored values of deleted items with a dummy value. This provides two benefits: it marks this item
964
- // as already "removed" so we won't call beforeRemove for it again, and it ensures that the item won't match up
965
- // with an actual item in the array and appear as "retained" or "moved".
966
- for (i = 0; i < itemsForBeforeRemoveCallbacks.length; ++i) {
967
- itemsForBeforeRemoveCallbacks[i].arrayEntry = deletedItemDummyValue;
968
- }
969
-
970
- // Finally call afterMove and afterAdd callbacks
971
- callCallback(options$$1['afterMove'], itemsForMoveCallbacks);
972
- callCallback(options$$1['afterAdd'], itemsForAfterAddCallbacks);
973
- }
974
-
975
- /**
976
- * This DescendantBindingHandler is a base class for bindings that control
977
- * descendants, such as the `if`, `with`, `component`, `foreach` and `template`
978
- * bindings.
979
- */
980
- class DescendantBindingHandler extends AsyncBindingHandler {
981
- get controlsDescendants () { return true }
982
-
983
- async applyBindingsToDescendants (childContext, callback) {
984
- const bindingResult = applyBindingsToDescendants(childContext, this.$element);
985
- if (bindingResult.isSync) {
986
- this.bindingCompletion = bindingResult;
987
- } else {
988
- await bindingResult.completionPromise;
989
- }
990
- if (callback) { callback(bindingResult); }
991
- this.completeBinding(bindingResult);
992
- }
993
- }
994
-
995
- export { BindingHandler, AsyncBindingHandler, DescendantBindingHandler, bindingEvent, boundElementDomDataKey$1 as boundElementDomDataKey, contextSubscribeSymbol, bindingContext, storedBindingContextForNode, contextFor, dataFor, getBindingHandler, applyBindingAccessorsToNode, applyBindingsToNode, applyBindingsToDescendants, applyBindings, setDomNodeChildrenFromArrayMapping };
996
- //# sourceMappingURL=bind.es6.js.map