mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-09-01 13:39:22 +03:00
Cleanup: Inputosaurus and Knockout Change: Knockout domData now uses WeakMap Replaced: Knockout domManipulation with a documentFragment
165 lines
7.3 KiB
JavaScript
165 lines
7.3 KiB
JavaScript
(() => {
|
|
|
|
// 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 = (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 = componentName =>
|
|
Object.prototype.hasOwnProperty.call(defaultConfigRegistry, componentName);
|
|
|
|
ko.components.unregister = componentName => {
|
|
delete defaultConfigRegistry[componentName];
|
|
ko.components.clearCachedDefinition(componentName);
|
|
};
|
|
|
|
ko.components.defaultLoader = {
|
|
'getConfig': (componentName, callback) => {
|
|
var result = ko.components.isRegistered(componentName)
|
|
? defaultConfigRegistry[componentName]
|
|
: null;
|
|
callback(result);
|
|
},
|
|
|
|
'loadComponent': (componentName, config, callback) => {
|
|
var errorCallback = makeErrorCallback(componentName);
|
|
resolveConfig(componentName, errorCallback, config, callback);
|
|
},
|
|
|
|
'loadTemplate': (componentName, templateConfig, callback) =>
|
|
resolveTemplate(makeErrorCallback(componentName), templateConfig, callback)
|
|
,
|
|
|
|
'loadViewModel': (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 = () => {
|
|
if (--makeCallBackWhenZero === 0) {
|
|
callback(result);
|
|
}
|
|
},
|
|
templateConfig = config['template'],
|
|
viewModelConfig = config['viewModel'];
|
|
|
|
if (templateConfig) {
|
|
ko.components._getFirstResultFromLoaders('loadTemplate', [componentName, templateConfig], resolvedTemplate => {
|
|
result['template'] = resolvedTemplate;
|
|
tryIssueCallback();
|
|
});
|
|
} else {
|
|
tryIssueCallback();
|
|
}
|
|
|
|
if (viewModelConfig) {
|
|
ko.components._getFirstResultFromLoaders('loadViewModel', [componentName, viewModelConfig], resolvedViewModel => {
|
|
result[createViewModelKey] = resolvedViewModel;
|
|
tryIssueCallback();
|
|
});
|
|
} else {
|
|
tryIssueCallback();
|
|
}
|
|
}
|
|
|
|
function resolveTemplate(errorCallback, templateConfig, callback) {
|
|
if (templateConfig instanceof Array) {
|
|
// Assume already an array of DOM nodes - pass through unchanged
|
|
callback(templateConfig);
|
|
} else if (templateConfig instanceof DocumentFragment) {
|
|
// Document fragment - use its child nodes
|
|
callback([...templateConfig.childNodes]);
|
|
} else if (templateConfig['element']) {
|
|
var element = templateConfig['element'];
|
|
if (element instanceof HTMLElement) {
|
|
// 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(params => 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(() => 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) {
|
|
if (elemInstance.matches('TEMPLATE')) {
|
|
// For browsers with proper <template> element support (i.e., where the .content property
|
|
// gives a document fragment), use that document fragment.
|
|
if (elemInstance.content instanceof DocumentFragment) {
|
|
return ko.utils.cloneNodes(elemInstance.content.childNodes);
|
|
}
|
|
}
|
|
throw 'Template Source Element not a <template>';
|
|
}
|
|
|
|
function makeErrorCallback(componentName) {
|
|
return message => {
|
|
throw new Error('Component \'' + componentName + '\': ' + message)
|
|
};
|
|
}
|
|
|
|
ko.exportSymbol('components.register', ko.components.register);
|
|
|
|
// By default, the default loader is the only registered component loader
|
|
ko.components['loaders'].push(ko.components.defaultLoader);
|
|
})();
|