-
Notifications
You must be signed in to change notification settings - Fork 1.5k
/
componentBinding.js
105 lines (86 loc) · 4.83 KB
/
componentBinding.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
(function(undefined) {
var componentLoadingOperationUniqueId = 0;
ko.bindingHandlers['component'] = {
'init': function(element, valueAccessor, ignored1, ignored2, bindingContext) {
var currentViewModel,
currentLoadingOperationId,
afterRenderSub,
disposeAssociatedComponentViewModel = function () {
var currentViewModelDispose = currentViewModel && currentViewModel['dispose'];
if (typeof currentViewModelDispose === 'function') {
currentViewModelDispose.call(currentViewModel);
}
if (afterRenderSub) {
afterRenderSub.dispose();
}
afterRenderSub = null;
currentViewModel = null;
// Any in-flight loading operation is no longer relevant, so make sure we ignore its completion
currentLoadingOperationId = null;
},
originalChildNodes = ko.utils.makeArray(ko.virtualElements.childNodes(element));
ko.virtualElements.emptyNode(element);
ko.utils.domNodeDisposal.addDisposeCallback(element, disposeAssociatedComponentViewModel);
ko.computed(function () {
var value = ko.utils.unwrapObservable(valueAccessor()),
componentName, componentParams;
if (typeof value === 'string') {
componentName = value;
} else {
componentName = ko.utils.unwrapObservable(value['name']);
componentParams = ko.utils.unwrapObservable(value['params']);
}
if (!componentName) {
throw new Error('No component name specified');
}
var asyncContext = ko.bindingEvent.startPossiblyAsyncContentBinding(element, bindingContext);
var loadingOperationId = currentLoadingOperationId = ++componentLoadingOperationUniqueId;
ko.components.get(componentName, function(componentDefinition) {
// If this is not the current load operation for this element, ignore it.
if (currentLoadingOperationId !== loadingOperationId) {
return;
}
// Clean up previous state
disposeAssociatedComponentViewModel();
// Instantiate and bind new component. Implicitly this cleans any old DOM nodes.
if (!componentDefinition) {
throw new Error('Unknown component \'' + componentName + '\'');
}
cloneTemplateIntoElement(componentName, componentDefinition, element);
var componentInfo = {
'element': element,
'templateNodes': originalChildNodes
};
var componentViewModel = createViewModel(componentDefinition, componentParams, componentInfo),
childBindingContext = asyncContext['createChildContext'](componentViewModel, {
'extend': function(ctx) {
ctx['$component'] = componentViewModel;
ctx['$componentTemplateNodes'] = originalChildNodes;
}
});
if (componentViewModel && componentViewModel['koDescendantsComplete']) {
afterRenderSub = ko.bindingEvent.subscribe(element, ko.bindingEvent.descendantsComplete, componentViewModel['koDescendantsComplete'], componentViewModel);
}
currentViewModel = componentViewModel;
ko.applyBindingsToDescendants(childBindingContext, element);
});
}, null, { disposeWhenNodeIsRemoved: element });
return { 'controlsDescendantBindings': true };
}
};
ko.virtualElements.allowedBindings['component'] = true;
function cloneTemplateIntoElement(componentName, componentDefinition, element) {
var template = componentDefinition['template'];
if (!template) {
throw new Error('Component \'' + componentName + '\' has no template');
}
var clonedNodesArray = ko.utils.cloneNodes(template);
ko.virtualElements.setDomNodeChildren(element, clonedNodesArray);
}
function createViewModel(componentDefinition, componentParams, componentInfo) {
var componentViewModelFactory = componentDefinition['createViewModel'];
return componentViewModelFactory
? componentViewModelFactory.call(componentDefinition, componentParams, componentInfo)
: componentParams; // Template-only component
}
})();