Clone 3.5.1 from github

This commit is contained in:
djmaze 2020-09-17 16:27:00 +02:00
parent 5d281486d0
commit 091c4ec811
65 changed files with 6957 additions and 7 deletions

View file

@ -0,0 +1,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
}
})();

View file

@ -0,0 +1,115 @@
(function (undefined) {
// Overridable API for determining which component name applies to a given node. By overriding this,
// you can for example map specific tagNames to components that are not preregistered.
ko.components['getComponentNameForNode'] = function(node) {
var tagNameLower = ko.utils.tagNameLower(node);
if (ko.components.isRegistered(tagNameLower)) {
// Try to determine that this node can be considered a *custom* element; see https://github.com/knockout/knockout/issues/1603
if (tagNameLower.indexOf('-') != -1 || ('' + node) == "[object HTMLUnknownElement]" || (ko.utils.ieVersion <= 8 && node.tagName === tagNameLower)) {
return tagNameLower;
}
}
};
ko.components.addBindingsForCustomElement = function(allBindings, node, bindingContext, valueAccessors) {
// Determine if it's really a custom element matching a component
if (node.nodeType === 1) {
var componentName = ko.components['getComponentNameForNode'](node);
if (componentName) {
// It does represent a component, so add a component binding for it
allBindings = allBindings || {};
if (allBindings['component']) {
// Avoid silently overwriting some other 'component' binding that may already be on the element
throw new Error('Cannot use the "component" binding on a custom element matching a component');
}
var componentBindingValue = { 'name': componentName, 'params': getComponentParamsFromCustomElement(node, bindingContext) };
allBindings['component'] = valueAccessors
? function() { return componentBindingValue; }
: componentBindingValue;
}
}
return allBindings;
}
var nativeBindingProviderInstance = new ko.bindingProvider();
function getComponentParamsFromCustomElement(elem, bindingContext) {
var paramsAttribute = elem.getAttribute('params');
if (paramsAttribute) {
var params = nativeBindingProviderInstance['parseBindingsString'](paramsAttribute, bindingContext, elem, { 'valueAccessors': true, 'bindingParams': true }),
rawParamComputedValues = ko.utils.objectMap(params, function(paramValue, paramName) {
return ko.computed(paramValue, null, { disposeWhenNodeIsRemoved: elem });
}),
result = ko.utils.objectMap(rawParamComputedValues, function(paramValueComputed, paramName) {
var paramValue = paramValueComputed.peek();
// Does the evaluation of the parameter value unwrap any observables?
if (!paramValueComputed.isActive()) {
// No it doesn't, so there's no need for any computed wrapper. Just pass through the supplied value directly.
// Example: "someVal: firstName, age: 123" (whether or not firstName is an observable/computed)
return paramValue;
} else {
// Yes it does. Supply a computed property that unwraps both the outer (binding expression)
// level of observability, and any inner (resulting model value) level of observability.
// This means the component doesn't have to worry about multiple unwrapping. If the value is a
// writable observable, the computed will also be writable and pass the value on to the observable.
return ko.computed({
'read': function() {
return ko.utils.unwrapObservable(paramValueComputed());
},
'write': ko.isWriteableObservable(paramValue) && function(value) {
paramValueComputed()(value);
},
disposeWhenNodeIsRemoved: elem
});
}
});
// Give access to the raw computeds, as long as that wouldn't overwrite any custom param also called '$raw'
// This is in case the developer wants to react to outer (binding) observability separately from inner
// (model value) observability, or in case the model value observable has subobservables.
if (!Object.prototype.hasOwnProperty.call(result, '$raw')) {
result['$raw'] = rawParamComputedValues;
}
return result;
} else {
// For consistency, absence of a "params" attribute is treated the same as the presence of
// any empty one. Otherwise component viewmodels need special code to check whether or not
// 'params' or 'params.$raw' is null/undefined before reading subproperties, which is annoying.
return { '$raw': {} };
}
}
// --------------------------------------------------------------------------------
// Compatibility code for older (pre-HTML5) IE browsers
if (ko.utils.ieVersion < 9) {
// Whenever you preregister a component, enable it as a custom element in the current document
ko.components['register'] = (function(originalFunction) {
return function(componentName) {
document.createElement(componentName); // Allows IE<9 to parse markup containing the custom element
return originalFunction.apply(this, arguments);
}
})(ko.components['register']);
// Whenever you create a document fragment, enable all preregistered component names as custom elements
// This is needed to make innerShiv/jQuery HTML parsing correctly handle the custom elements
document.createDocumentFragment = (function(originalFunction) {
return function() {
var newDocFrag = originalFunction(),
allComponents = ko.components._allRegisteredComponents;
for (var componentName in allComponents) {
if (Object.prototype.hasOwnProperty.call(allComponents, componentName)) {
newDocFrag.createElement(componentName);
}
}
return newDocFrag;
};
})(document.createDocumentFragment);
}
})();

View file

@ -0,0 +1,231 @@
(function(undefined) {
// The default loader is responsible for two things:
// 1. Maintaining the default in-memory registry of component configuration objects
// (i.e., the thing you're writing to when you call ko.components.register(someName, ...))
// 2. Answering requests for components by fetching configuration objects
// from that default in-memory registry and resolving them into standard
// component definition objects (of the form { createViewModel: ..., template: ... })
// Custom loaders may override either of these facilities, i.e.,
// 1. To supply configuration objects from some other source (e.g., conventions)
// 2. Or, to resolve configuration objects by loading viewmodels/templates via arbitrary logic.
var defaultConfigRegistry = {};
ko.components.register = function(componentName, config) {
if (!config) {
throw new Error('Invalid configuration for ' + componentName);
}
if (ko.components.isRegistered(componentName)) {
throw new Error('Component ' + componentName + ' is already registered');
}
defaultConfigRegistry[componentName] = config;
};
ko.components.isRegistered = function(componentName) {
return Object.prototype.hasOwnProperty.call(defaultConfigRegistry, componentName);
};
ko.components.unregister = function(componentName) {
delete defaultConfigRegistry[componentName];
ko.components.clearCachedDefinition(componentName);
};
ko.components.defaultLoader = {
'getConfig': function(componentName, callback) {
var result = ko.components.isRegistered(componentName)
? defaultConfigRegistry[componentName]
: null;
callback(result);
},
'loadComponent': function(componentName, config, callback) {
var errorCallback = makeErrorCallback(componentName);
possiblyGetConfigFromAmd(errorCallback, config, function(loadedConfig) {
resolveConfig(componentName, errorCallback, loadedConfig, callback);
});
},
'loadTemplate': function(componentName, templateConfig, callback) {
resolveTemplate(makeErrorCallback(componentName), templateConfig, callback);
},
'loadViewModel': function(componentName, viewModelConfig, callback) {
resolveViewModel(makeErrorCallback(componentName), viewModelConfig, callback);
}
};
var createViewModelKey = 'createViewModel';
// Takes a config object of the form { template: ..., viewModel: ... }, and asynchronously convert it
// into the standard component definition format:
// { template: <ArrayOfDomNodes>, createViewModel: function(params, componentInfo) { ... } }.
// Since both template and viewModel may need to be resolved asynchronously, both tasks are performed
// in parallel, and the results joined when both are ready. We don't depend on any promises infrastructure,
// so this is implemented manually below.
function resolveConfig(componentName, errorCallback, config, callback) {
var result = {},
makeCallBackWhenZero = 2,
tryIssueCallback = function() {
if (--makeCallBackWhenZero === 0) {
callback(result);
}
},
templateConfig = config['template'],
viewModelConfig = config['viewModel'];
if (templateConfig) {
possiblyGetConfigFromAmd(errorCallback, templateConfig, function(loadedConfig) {
ko.components._getFirstResultFromLoaders('loadTemplate', [componentName, loadedConfig], function(resolvedTemplate) {
result['template'] = resolvedTemplate;
tryIssueCallback();
});
});
} else {
tryIssueCallback();
}
if (viewModelConfig) {
possiblyGetConfigFromAmd(errorCallback, viewModelConfig, function(loadedConfig) {
ko.components._getFirstResultFromLoaders('loadViewModel', [componentName, loadedConfig], function(resolvedViewModel) {
result[createViewModelKey] = resolvedViewModel;
tryIssueCallback();
});
});
} else {
tryIssueCallback();
}
}
function resolveTemplate(errorCallback, templateConfig, callback) {
if (typeof templateConfig === 'string') {
// Markup - parse it
callback(ko.utils.parseHtmlFragment(templateConfig));
} else if (templateConfig instanceof Array) {
// Assume already an array of DOM nodes - pass through unchanged
callback(templateConfig);
} else if (isDocumentFragment(templateConfig)) {
// Document fragment - use its child nodes
callback(ko.utils.makeArray(templateConfig.childNodes));
} else if (templateConfig['element']) {
var element = templateConfig['element'];
if (isDomElement(element)) {
// Element instance - copy its child nodes
callback(cloneNodesFromTemplateSourceElement(element));
} else if (typeof element === 'string') {
// Element ID - find it, then copy its child nodes
var elemInstance = document.getElementById(element);
if (elemInstance) {
callback(cloneNodesFromTemplateSourceElement(elemInstance));
} else {
errorCallback('Cannot find element with ID ' + element);
}
} else {
errorCallback('Unknown element type: ' + element);
}
} else {
errorCallback('Unknown template value: ' + templateConfig);
}
}
function resolveViewModel(errorCallback, viewModelConfig, callback) {
if (typeof viewModelConfig === 'function') {
// Constructor - convert to standard factory function format
// By design, this does *not* supply componentInfo to the constructor, as the intent is that
// componentInfo contains non-viewmodel data (e.g., the component's element) that should only
// be used in factory functions, not viewmodel constructors.
callback(function (params /*, componentInfo */) {
return new viewModelConfig(params);
});
} else if (typeof viewModelConfig[createViewModelKey] === 'function') {
// Already a factory function - use it as-is
callback(viewModelConfig[createViewModelKey]);
} else if ('instance' in viewModelConfig) {
// Fixed object instance - promote to createViewModel format for API consistency
var fixedInstance = viewModelConfig['instance'];
callback(function (params, componentInfo) {
return fixedInstance;
});
} else if ('viewModel' in viewModelConfig) {
// Resolved AMD module whose value is of the form { viewModel: ... }
resolveViewModel(errorCallback, viewModelConfig['viewModel'], callback);
} else {
errorCallback('Unknown viewModel value: ' + viewModelConfig);
}
}
function cloneNodesFromTemplateSourceElement(elemInstance) {
switch (ko.utils.tagNameLower(elemInstance)) {
case 'script':
return ko.utils.parseHtmlFragment(elemInstance.text);
case 'textarea':
return ko.utils.parseHtmlFragment(elemInstance.value);
case 'template':
// For browsers with proper <template> element support (i.e., where the .content property
// gives a document fragment), use that document fragment.
if (isDocumentFragment(elemInstance.content)) {
return ko.utils.cloneNodes(elemInstance.content.childNodes);
}
}
// Regular elements such as <div>, and <template> elements on old browsers that don't really
// understand <template> and just treat it as a regular container
return ko.utils.cloneNodes(elemInstance.childNodes);
}
function isDomElement(obj) {
if (window['HTMLElement']) {
return obj instanceof HTMLElement;
} else {
return obj && obj.tagName && obj.nodeType === 1;
}
}
function isDocumentFragment(obj) {
if (window['DocumentFragment']) {
return obj instanceof DocumentFragment;
} else {
return obj && obj.nodeType === 11;
}
}
function possiblyGetConfigFromAmd(errorCallback, config, callback) {
if (typeof config['require'] === 'string') {
// The config is the value of an AMD module
if (amdRequire || window['require']) {
(amdRequire || window['require'])([config['require']], function (module) {
if (module && typeof module === 'object' && module.__esModule && module['default']) {
module = module['default'];
}
callback(module);
});
} else {
errorCallback('Uses require, but no AMD loader is present');
}
} else {
callback(config);
}
}
function makeErrorCallback(componentName) {
return function (message) {
throw new Error('Component \'' + componentName + '\': ' + message);
};
}
ko.exportSymbol('components.register', ko.components.register);
ko.exportSymbol('components.isRegistered', ko.components.isRegistered);
ko.exportSymbol('components.unregister', ko.components.unregister);
// Expose the default loader so that developers can directly ask it for configuration
// or to resolve configuration
ko.exportSymbol('components.defaultLoader', ko.components.defaultLoader);
// By default, the default loader is the only registered component loader
ko.components['loaders'].push(ko.components.defaultLoader);
// Privately expose the underlying config registry for use in old-IE shim
ko.components._allRegisteredComponents = defaultConfigRegistry;
})();

View file

@ -0,0 +1,142 @@
(function(undefined) {
var loadingSubscribablesCache = {}, // Tracks component loads that are currently in flight
loadedDefinitionsCache = {}; // Tracks component loads that have already completed
ko.components = {
get: function(componentName, callback) {
var cachedDefinition = getObjectOwnProperty(loadedDefinitionsCache, componentName);
if (cachedDefinition) {
// It's already loaded and cached. Reuse the same definition object.
// Note that for API consistency, even cache hits complete asynchronously by default.
// You can bypass this by putting synchronous:true on your component config.
if (cachedDefinition.isSynchronousComponent) {
ko.dependencyDetection.ignore(function() { // See comment in loaderRegistryBehaviors.js for reasoning
callback(cachedDefinition.definition);
});
} else {
ko.tasks.schedule(function() { callback(cachedDefinition.definition); });
}
} else {
// Join the loading process that is already underway, or start a new one.
loadComponentAndNotify(componentName, callback);
}
},
clearCachedDefinition: function(componentName) {
delete loadedDefinitionsCache[componentName];
},
_getFirstResultFromLoaders: getFirstResultFromLoaders
};
function getObjectOwnProperty(obj, propName) {
return Object.prototype.hasOwnProperty.call(obj, propName) ? obj[propName] : undefined;
}
function loadComponentAndNotify(componentName, callback) {
var subscribable = getObjectOwnProperty(loadingSubscribablesCache, componentName),
completedAsync;
if (!subscribable) {
// It's not started loading yet. Start loading, and when it's done, move it to loadedDefinitionsCache.
subscribable = loadingSubscribablesCache[componentName] = new ko.subscribable();
subscribable.subscribe(callback);
beginLoadingComponent(componentName, function(definition, config) {
var isSynchronousComponent = !!(config && config['synchronous']);
loadedDefinitionsCache[componentName] = { definition: definition, isSynchronousComponent: isSynchronousComponent };
delete loadingSubscribablesCache[componentName];
// For API consistency, all loads complete asynchronously. However we want to avoid
// adding an extra task schedule if it's unnecessary (i.e., the completion is already
// async).
//
// You can bypass the 'always asynchronous' feature by putting the synchronous:true
// flag on your component configuration when you register it.
if (completedAsync || isSynchronousComponent) {
// Note that notifySubscribers ignores any dependencies read within the callback.
// See comment in loaderRegistryBehaviors.js for reasoning
subscribable['notifySubscribers'](definition);
} else {
ko.tasks.schedule(function() {
subscribable['notifySubscribers'](definition);
});
}
});
completedAsync = true;
} else {
subscribable.subscribe(callback);
}
}
function beginLoadingComponent(componentName, callback) {
getFirstResultFromLoaders('getConfig', [componentName], function(config) {
if (config) {
// We have a config, so now load its definition
getFirstResultFromLoaders('loadComponent', [componentName, config], function(definition) {
callback(definition, config);
});
} else {
// The component has no config - it's unknown to all the loaders.
// Note that this is not an error (e.g., a module loading error) - that would abort the
// process and this callback would not run. For this callback to run, all loaders must
// have confirmed they don't know about this component.
callback(null, null);
}
});
}
function getFirstResultFromLoaders(methodName, argsExceptCallback, callback, candidateLoaders) {
// On the first call in the stack, start with the full set of loaders
if (!candidateLoaders) {
candidateLoaders = ko.components['loaders'].slice(0); // Use a copy, because we'll be mutating this array
}
// Try the next candidate
var currentCandidateLoader = candidateLoaders.shift();
if (currentCandidateLoader) {
var methodInstance = currentCandidateLoader[methodName];
if (methodInstance) {
var wasAborted = false,
synchronousReturnValue = methodInstance.apply(currentCandidateLoader, argsExceptCallback.concat(function(result) {
if (wasAborted) {
callback(null);
} else if (result !== null) {
// This candidate returned a value. Use it.
callback(result);
} else {
// Try the next candidate
getFirstResultFromLoaders(methodName, argsExceptCallback, callback, candidateLoaders);
}
}));
// Currently, loaders may not return anything synchronously. This leaves open the possibility
// that we'll extend the API to support synchronous return values in the future. It won't be
// a breaking change, because currently no loader is allowed to return anything except undefined.
if (synchronousReturnValue !== undefined) {
wasAborted = true;
// Method to suppress exceptions will remain undocumented. This is only to keep
// KO's specs running tidily, since we can observe the loading got aborted without
// having exceptions cluttering up the console too.
if (!currentCandidateLoader['suppressLoaderExceptions']) {
throw new Error('Component loaders must supply values by invoking the callback, not by returning values synchronously.');
}
}
} else {
// This candidate doesn't have the relevant handler. Synchronously move on to the next one.
getFirstResultFromLoaders(methodName, argsExceptCallback, callback, candidateLoaders);
}
} else {
// No candidates returned a value
callback(null);
}
}
// Reference the loaders via string name so it's possible for developers
// to replace the whole array by assigning to ko.components.loaders
ko.components['loaders'] = [];
ko.exportSymbol('components', ko.components);
ko.exportSymbol('components.get', ko.components.get);
ko.exportSymbol('components.clearCachedDefinition', ko.components.clearCachedDefinition);
})();