mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-09-03 22:47:03 +03:00
Clone 3.5.1 from github
This commit is contained in:
parent
5d281486d0
commit
091c4ec811
65 changed files with 6957 additions and 7 deletions
105
vendors/knockout/src/components/componentBinding.js
vendored
Normal file
105
vendors/knockout/src/components/componentBinding.js
vendored
Normal 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
|
||||
}
|
||||
|
||||
})();
|
||||
115
vendors/knockout/src/components/customElements.js
vendored
Normal file
115
vendors/knockout/src/components/customElements.js
vendored
Normal 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);
|
||||
}
|
||||
})();
|
||||
231
vendors/knockout/src/components/defaultLoader.js
vendored
Normal file
231
vendors/knockout/src/components/defaultLoader.js
vendored
Normal 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;
|
||||
})();
|
||||
142
vendors/knockout/src/components/loaderRegistry.js
vendored
Normal file
142
vendors/knockout/src/components/loaderRegistry.js
vendored
Normal 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);
|
||||
})();
|
||||
Loading…
Add table
Add a link
Reference in a new issue