More Knockout code to ES6

This commit is contained in:
djmaze 2021-07-29 11:43:47 +02:00
parent b7cb990b00
commit cbecdd93a0
22 changed files with 733 additions and 865 deletions

View file

@ -6,142 +6,141 @@
ko.bindingHandlers = {};
// Use an overridable method for retrieving binding handlers so that plugins may support dynamically created handlers
ko['getBindingHandler'] = bindingKey => ko.bindingHandlers[bindingKey];
var inheritParentVm = {};
// The ko.bindingContext constructor is only called directly to create the root context. For child
// contexts, use bindingContext.createChildContext or bindingContext.extend.
ko.bindingContext = function(dataItemOrAccessor, parentContext, dataItemAlias, extendCallback, options) {
ko.bindingContext = class {
constructor(dataItemOrAccessor, parentContext, dataItemAlias, extendCallback, options)
{
// The binding context object includes static properties for the current, parent, and root view models.
// If a view model is actually stored in an observable, the corresponding binding context object, and
// any child contexts, must be updated when the view model is changed.
function updateContext() {
// Most of the time, the context will directly get a view model object, but if a function is given,
// we call the function to retrieve the view model. If the function accesses any observables or returns
// an observable, the dependency is tracked, and those observables can later cause the binding
// context to be updated.
var dataItemOrObservable = isFunc ? realDataItemOrAccessor() : realDataItemOrAccessor,
dataItem = ko.utils.unwrapObservable(dataItemOrObservable);
// The binding context object includes static properties for the current, parent, and root view models.
// If a view model is actually stored in an observable, the corresponding binding context object, and
// any child contexts, must be updated when the view model is changed.
function updateContext() {
// Most of the time, the context will directly get a view model object, but if a function is given,
// we call the function to retrieve the view model. If the function accesses any observables or returns
// an observable, the dependency is tracked, and those observables can later cause the binding
// context to be updated.
var dataItemOrObservable = isFunc ? realDataItemOrAccessor() : realDataItemOrAccessor,
dataItem = ko.utils.unwrapObservable(dataItemOrObservable);
if (parentContext) {
// Copy $root and any custom properties from the parent context
ko.utils.extend(self, parentContext);
if (parentContext) {
// Copy $root and any custom properties from the parent context
ko.utils.extend(self, parentContext);
// Copy Symbol properties
if (contextAncestorBindingInfo in parentContext) {
self[contextAncestorBindingInfo] = parentContext[contextAncestorBindingInfo];
}
} else {
self['$parents'] = [];
self['$root'] = dataItem;
// Copy Symbol properties
if (contextAncestorBindingInfo in parentContext) {
self[contextAncestorBindingInfo] = parentContext[contextAncestorBindingInfo];
// Export 'ko' in the binding context so it will be available in bindings and templates
// even if 'ko' isn't exported as a global, such as when using an AMD loader.
// See https://github.com/SteveSanderson/knockout/issues/490
self['ko'] = ko;
}
self[contextSubscribable] = subscribable;
if (shouldInheritData) {
dataItem = self['$data'];
} else {
self['$rawData'] = dataItemOrObservable;
self['$data'] = dataItem;
}
if (dataItemAlias)
self[dataItemAlias] = dataItem;
// The extendCallback function is provided when creating a child context or extending a context.
// It handles the specific actions needed to finish setting up the binding context. Actions in this
// function could also add dependencies to this binding context.
if (extendCallback)
extendCallback(self, parentContext, dataItem);
// When a "parent" context is given and we don't already have a dependency on its context, register a dependency on it.
// Thus whenever the parent context is updated, this context will also be updated.
if (parentContext && parentContext[contextSubscribable] && !ko.dependencyDetection.computed().hasAncestorDependency(parentContext[contextSubscribable])) {
parentContext[contextSubscribable]();
}
if (dataDependency) {
self[contextDataDependency] = dataDependency;
}
return self['$data'];
}
var self = this,
shouldInheritData = dataItemOrAccessor === inheritParentVm,
realDataItemOrAccessor = shouldInheritData ? undefined : dataItemOrAccessor,
isFunc = typeof(realDataItemOrAccessor) == "function" && !ko.isObservable(realDataItemOrAccessor),
subscribable,
dataDependency = options && options['dataDependency'];
if (options && options['exportDependencies']) {
// The "exportDependencies" option means that the calling code will track any dependencies and re-create
// the binding context when they change.
updateContext();
} else {
self['$parents'] = [];
self['$root'] = dataItem;
subscribable = ko.pureComputed(updateContext);
subscribable.peek();
// Export 'ko' in the binding context so it will be available in bindings and templates
// even if 'ko' isn't exported as a global, such as when using an AMD loader.
// See https://github.com/SteveSanderson/knockout/issues/490
self['ko'] = ko;
// At this point, the binding context has been initialized, and the "subscribable" computed observable is
// subscribed to any observables that were accessed in the process. If there is nothing to track, the
// computed will be inactive, and we can safely throw it away. If it's active, the computed is stored in
// the context object.
if (subscribable.isActive()) {
// Always notify because even if the model ($data) hasn't changed, other context properties might have changed
subscribable['equalityComparer'] = null;
} else {
self[contextSubscribable] = undefined;
}
}
self[contextSubscribable] = subscribable;
if (shouldInheritData) {
dataItem = self['$data'];
} else {
self['$rawData'] = dataItemOrObservable;
self['$data'] = dataItem;
}
if (dataItemAlias)
self[dataItemAlias] = dataItem;
// The extendCallback function is provided when creating a child context or extending a context.
// It handles the specific actions needed to finish setting up the binding context. Actions in this
// function could also add dependencies to this binding context.
if (extendCallback)
extendCallback(self, parentContext, dataItem);
// When a "parent" context is given and we don't already have a dependency on its context, register a dependency on it.
// Thus whenever the parent context is updated, this context will also be updated.
if (parentContext && parentContext[contextSubscribable] && !ko.dependencyDetection.computed().hasAncestorDependency(parentContext[contextSubscribable])) {
parentContext[contextSubscribable]();
}
if (dataDependency) {
self[contextDataDependency] = dataDependency;
}
return self['$data'];
}
var self = this,
shouldInheritData = dataItemOrAccessor === inheritParentVm,
realDataItemOrAccessor = shouldInheritData ? undefined : dataItemOrAccessor,
isFunc = typeof(realDataItemOrAccessor) == "function" && !ko.isObservable(realDataItemOrAccessor),
subscribable,
dataDependency = options && options['dataDependency'];
if (options && options['exportDependencies']) {
// The "exportDependencies" option means that the calling code will track any dependencies and re-create
// the binding context when they change.
updateContext();
} else {
subscribable = ko.pureComputed(updateContext);
subscribable.peek();
// At this point, the binding context has been initialized, and the "subscribable" computed observable is
// subscribed to any observables that were accessed in the process. If there is nothing to track, the
// computed will be inactive, and we can safely throw it away. If it's active, the computed is stored in
// the context object.
if (subscribable.isActive()) {
// Always notify because even if the model ($data) hasn't changed, other context properties might have changed
subscribable['equalityComparer'] = null;
} else {
self[contextSubscribable] = undefined;
// Extend the binding context hierarchy with a new view model object. If the parent context is watching
// any observables, the new child context will automatically get a dependency on the parent context.
// But this does not mean that the $data value of the child context will also get updated. If the child
// view model also depends on the parent view model, you must provide a function that returns the correct
// view model on each update.
'createChildContext'(dataItemOrAccessor, dataItemAlias, extendCallback, options) {
if (!options && dataItemAlias && typeof dataItemAlias == "object") {
options = dataItemAlias;
dataItemAlias = options['as'];
extendCallback = options['extend'];
}
}
}
// Extend the binding context hierarchy with a new view model object. If the parent context is watching
// any observables, the new child context will automatically get a dependency on the parent context.
// But this does not mean that the $data value of the child context will also get updated. If the child
// view model also depends on the parent view model, you must provide a function that returns the correct
// view model on each update.
ko.bindingContext.prototype['createChildContext'] = function (dataItemOrAccessor, dataItemAlias, extendCallback, options) {
if (!options && dataItemAlias && typeof dataItemAlias == "object") {
options = dataItemAlias;
dataItemAlias = options['as'];
extendCallback = options['extend'];
}
if (dataItemAlias && options && options['noChildContext']) {
var isFunc = typeof(dataItemOrAccessor) == "function" && !ko.isObservable(dataItemOrAccessor);
return new ko.bindingContext(inheritParentVm, this, null, self => {
if (extendCallback)
extendCallback(self);
self[dataItemAlias] = isFunc ? dataItemOrAccessor() : dataItemOrAccessor;
}, options);
}
if (dataItemAlias && options && options['noChildContext']) {
var isFunc = typeof(dataItemOrAccessor) == "function" && !ko.isObservable(dataItemOrAccessor);
return new ko.bindingContext(inheritParentVm, this, null, self => {
return new ko.bindingContext(dataItemOrAccessor, this, dataItemAlias, (self, parentContext) => {
// Extend the context hierarchy by setting the appropriate pointers
self['$parentContext'] = parentContext;
self['$parent'] = parentContext['$data'];
self['$parents'] = (parentContext['$parents'] || []).slice(0);
self['$parents'].unshift(self['$parent']);
if (extendCallback)
extendCallback(self);
self[dataItemAlias] = isFunc ? dataItemOrAccessor() : dataItemOrAccessor;
}, options);
}
return new ko.bindingContext(dataItemOrAccessor, this, dataItemAlias, (self, parentContext) => {
// Extend the context hierarchy by setting the appropriate pointers
self['$parentContext'] = parentContext;
self['$parent'] = parentContext['$data'];
self['$parents'] = (parentContext['$parents'] || []).slice(0);
self['$parents'].unshift(self['$parent']);
if (extendCallback)
extendCallback(self);
}, options);
};
// Extend the binding context with new custom properties. This doesn't change the context hierarchy.
// Similarly to "child" contexts, provide a function here to make sure that the correct values are set
// when an observable view model is updated.
ko.bindingContext.prototype['extend'] = function(properties, options) {
return new ko.bindingContext(inheritParentVm, this, null, self =>
ko.utils.extend(self, typeof(properties) == "function" ? properties(self) : properties)
, options);
// Extend the binding context with new custom properties. This doesn't change the context hierarchy.
// Similarly to "child" contexts, provide a function here to make sure that the correct values are set
// when an observable view model is updated.
'extend'(properties, options) {
return new ko.bindingContext(inheritParentVm, this, null, self =>
ko.utils.extend(self, typeof(properties) == "function" ? properties(self) : properties)
, options);
}
};
var boundElementDomDataKey = ko.utils.domData.nextKey();
@ -154,41 +153,44 @@
asyncContext.notifyAncestor();
}
}
function AsyncCompleteContext(node, bindingInfo, ancestorBindingInfo) {
this.node = node;
this.bindingInfo = bindingInfo;
this.asyncDescendants = [];
this.childrenComplete = false;
if (!bindingInfo.asyncContext) {
ko.utils.domNodeDisposal.addDisposeCallback(node, asyncContextDispose);
class AsyncCompleteContext {
constructor(node, bindingInfo, ancestorBindingInfo) {
this.node = node;
this.bindingInfo = bindingInfo;
this.asyncDescendants = new Set;
this.childrenComplete = false;
if (!bindingInfo.asyncContext) {
ko.utils.domNodeDisposal.addDisposeCallback(node, asyncContextDispose);
}
if (ancestorBindingInfo && ancestorBindingInfo.asyncContext) {
ancestorBindingInfo.asyncContext.asyncDescendants.add(node);
this.ancestorBindingInfo = ancestorBindingInfo;
}
}
if (ancestorBindingInfo && ancestorBindingInfo.asyncContext) {
ancestorBindingInfo.asyncContext.asyncDescendants.push(node);
this.ancestorBindingInfo = ancestorBindingInfo;
notifyAncestor() {
if (this.ancestorBindingInfo && this.ancestorBindingInfo.asyncContext) {
this.ancestorBindingInfo.asyncContext.descendantComplete(this.node);
}
}
descendantComplete(node) {
this.asyncDescendants.delete(node);
if (!this.asyncDescendants.size && this.childrenComplete) {
this.completeChildren();
}
}
completeChildren() {
this.childrenComplete = true;
if (this.bindingInfo.asyncContext && !this.asyncDescendants.size) {
this.bindingInfo.asyncContext = null;
ko.utils.domNodeDisposal.removeDisposeCallback(this.node, asyncContextDispose);
ko.bindingEvent.notify(this.node, ko.bindingEvent.descendantsComplete);
this.notifyAncestor();
}
}
}
AsyncCompleteContext.prototype.notifyAncestor = function () {
if (this.ancestorBindingInfo && this.ancestorBindingInfo.asyncContext) {
this.ancestorBindingInfo.asyncContext.descendantComplete(this.node);
}
};
AsyncCompleteContext.prototype.descendantComplete = function (node) {
ko.utils.arrayRemoveItem(this.asyncDescendants, node);
if (!this.asyncDescendants.length && this.childrenComplete) {
this.completeChildren();
}
};
AsyncCompleteContext.prototype.completeChildren = function () {
this.childrenComplete = true;
if (this.bindingInfo.asyncContext && !this.asyncDescendants.length) {
this.bindingInfo.asyncContext = null;
ko.utils.domNodeDisposal.removeDisposeCallback(this.node, asyncContextDispose);
ko.bindingEvent.notify(this.node, ko.bindingEvent.descendantsComplete);
this.notifyAncestor();
}
};
ko.bindingEvent = {
childrenComplete: "childrenComplete",
@ -310,7 +312,7 @@
cyclicDependencyStack = []; // Keeps track of a depth-search so that, if there's a cycle, we know which bindings caused it
ko.utils.objectForEach(bindings, function pushBinding(bindingKey) {
if (!bindingsConsidered[bindingKey]) {
var binding = ko['getBindingHandler'](bindingKey);
var binding = ko.bindingHandlers[bindingKey];
if (binding) {
// First add dependencies (if any) of the current binding
if (binding['after']) {

View file

@ -1,22 +1,7 @@
(() => {
const defaultBindingAttributeName = "data-bind",
bindingCache = {},
createBindingsStringEvaluatorViaCache = (bindingsString, cache, options) => {
var cacheKey = bindingsString + (options && options['valueAccessors'] || '');
return cache[cacheKey]
|| (cache[cacheKey] = createBindingsStringEvaluator(bindingsString, options));
},
createBindingsStringEvaluator = (bindingsString, options) => {
// Build the source for a function that evaluates "expression"
// For each scope variable, add an extra level of "with" nesting
// Example result: with(sc1) { with(sc0) { return (expression) } }
var rewrittenBindings = ko.expressionRewriting.preProcessBindings(bindingsString, options),
functionBody = "with($context){with($data||{}){return{" + rewrittenBindings + "}}}";
return new Function("$context", "$element", functionBody);
},
bindingCache = new Map,
// The following function is only used internally by this default provider.
// It's not part of the interface definition for a general binding provider.
@ -26,18 +11,6 @@
case 8: return ko.virtualElements.virtualNodeBindingValue(node); // Comment node
}
return null;
},
// The following function is only used internally by this default provider.
// It's not part of the interface definition for a general binding provider.
parseBindingsString = (bindingsString, bindingContext, node, options) => {
try {
var bindingFunction = createBindingsStringEvaluatorViaCache(bindingsString, bindingCache, options);
return bindingFunction(bindingContext, node);
} catch (ex) {
ex.message = "Unable to parse bindings.\nBindings value: " + bindingsString + "\nMessage: " + ex.message;
throw ex;
}
};
ko.bindingProvider = new class
@ -53,8 +26,29 @@
}
getBindingAccessors(node, bindingContext) {
var bindingsString = getBindingsString(node, bindingContext);
return bindingsString ? parseBindingsString(bindingsString, bindingContext, node, { 'valueAccessors': true }) : null;
var bindingsString = getBindingsString(node);
if (bindingsString) {
try {
let options = { 'valueAccessors': true },
cacheKey = bindingsString,
bindingFunction = bindingCache.get(cacheKey);
if (!bindingFunction) {
// Build the source for a function that evaluates "expression"
// For each scope variable, add an extra level of "with" nesting
// Example result: with(sc1) { with(sc0) { return (expression) } }
var rewrittenBindings = ko.expressionRewriting.preProcessBindings(bindingsString, options),
functionBody = "with($context){with($data||{}){return{" + rewrittenBindings + "}}}";
bindingFunction = new Function("$context", "$element", functionBody);
bindingCache.set(cacheKey, bindingFunction);
}
return bindingFunction(bindingContext, node);
} catch (ex) {
ex.message = "Unable to parse bindings.\nBindings value: " + bindingsString
+ "\nMessage: " + ex.message;
throw ex;
}
}
return null;
}
};

View file

@ -37,4 +37,4 @@ ko.bindingHandlers['hasfocus'] = {
}
}
};
ko.expressionRewriting.twoWayBindings['hasfocus'] = true;
ko.expressionRewriting.twoWayBindings.add('hasfocus');

View file

@ -62,7 +62,7 @@ ko.bindingHandlers['textInput'] = {
ko.computed(updateView, { disposeWhenNodeIsRemoved: element });
}
};
ko.expressionRewriting.twoWayBindings['textInput'] = true;
ko.expressionRewriting.twoWayBindings.add('textInput');
// textinput is an alias for textInput
ko.bindingHandlers['textinput'] = {

View file

@ -9,20 +9,18 @@ ko.bindingHandlers['value'] = {
return;
}
var eventsToCatch = [];
var eventsToCatch = new Set;
var requestedEventsToCatch = allBindings.get("valueUpdate");
var elementValueBeforeEvent = null;
if (requestedEventsToCatch) {
// Allow both individual event names, and arrays of event names
if (typeof requestedEventsToCatch == "string") {
eventsToCatch = [requestedEventsToCatch];
eventsToCatch.add(requestedEventsToCatch);
} else {
eventsToCatch = requestedEventsToCatch ? requestedEventsToCatch.filter((item, index) =>
requestedEventsToCatch.indexOf(item) === index
) : [];
requestedEventsToCatch.forEach(item => eventsToCatch.add(item));
}
ko.utils.arrayRemoveItem(eventsToCatch, "change"); // We'll subscribe to "change" events later
eventsToCatch.delete("change"); // We'll subscribe to "change" events later
}
var valueUpdateHandler = () => {
@ -113,4 +111,4 @@ ko.bindingHandlers['value'] = {
},
'update': () => {} // Keep for backwards compatibility with code that may have wrapped value binding
};
ko.expressionRewriting.twoWayBindings['value'] = true;
ko.expressionRewriting.twoWayBindings.add('value');

View file

@ -58,7 +58,8 @@ ko.expressionRewriting = (() => {
var result = [], toks = str.match(bindingToken), key, values = [], depth = 0;
if (toks.length > 1) {
for (var i = 0, tok; tok = toks[i]; ++i) {
var i = 0, tok;
while ((tok = toks[i++])) {
var c = tok.charCodeAt(0);
// A comma signals the end of a key/value pair if depth is zero
if (c === 44) { // ","
@ -108,7 +109,7 @@ ko.expressionRewriting = (() => {
}
// Two-way bindings include a write function that allow the handler to update the value even if it's not an observable.
var twoWayBindings = {};
var twoWayBindings = new Set;
function preProcessBindings(bindingsStringOrKeyValueArray, bindingOptions) {
bindingOptions = bindingOptions || {};
@ -119,14 +120,13 @@ ko.expressionRewriting = (() => {
return (obj && obj['preprocess']) ? (val = obj['preprocess'](val, key, processKeyValue)) : true;
}
if (!bindingParams) {
if (!callPreprocessHook(ko['getBindingHandler'](key)))
if (!callPreprocessHook(ko.bindingHandlers[key]))
return;
if (twoWayBindings[key] && (writableVal = getWriteableValue(val))) {
if (twoWayBindings.has(key) && (writableVal = getWriteableValue(val))) {
// For two-way bindings, provide a write method in case the value
// isn't a writable observable.
var writeKey = typeof twoWayBindings[key] == 'string' ? twoWayBindings[key] : key;
propertyAccessorResultStrings.push("'" + writeKey + "':function(_z){" + writableVal + "=_z}");
propertyAccessorResultStrings.push("'" + key + "':function(_z){" + writableVal + "=_z}");
}
}
// Values are wrapped in a function so that each value can be accessed independently

View file

@ -1,49 +1,44 @@
(() => {
ko.dependencyDetection = (() => {
var outerFrames = [],
currentFrame,
lastId = 0,
var outerFrames = [],
currentFrame,
lastId = 0,
begin = options => {
outerFrames.push(currentFrame);
currentFrame = options;
},
begin = options => {
outerFrames.push(currentFrame);
currentFrame = options;
},
end = () => currentFrame = outerFrames.pop();
end = () => currentFrame = outerFrames.pop();
return {
begin: begin,
ko.dependencyDetection = {
begin: begin,
end: end,
end: end,
registerDependency: subscribable => {
if (currentFrame) {
if (!ko.isSubscribable(subscribable))
throw new Error("Only subscribable things can act as dependencies");
currentFrame.callback.call(currentFrame.callbackTarget, subscribable,
subscribable._id || (subscribable._id = ++lastId));
}
},
ignore: (callback, callbackTarget, callbackArgs) => {
try {
begin();
return callback.apply(callbackTarget, callbackArgs || []);
} finally {
end();
}
},
getDependenciesCount: () => {
return currentFrame && currentFrame.computed.getDependenciesCount();
},
isInitial: () => {
return currentFrame && currentFrame.isInitial;
},
computed: () => {
return currentFrame && currentFrame.computed;
registerDependency: subscribable => {
if (currentFrame) {
if (!ko.isSubscribable(subscribable))
throw new Error("Only subscribable things can act as dependencies");
currentFrame.callback.call(currentFrame.callbackTarget, subscribable,
subscribable._id || (subscribable._id = ++lastId));
}
};
},
ignore: (callback, callbackTarget, callbackArgs) => {
try {
begin();
return callback.apply(callbackTarget, callbackArgs || []);
} finally {
end();
}
},
getDependenciesCount: () => currentFrame && currentFrame.computed.getDependenciesCount(),
isInitial: () => currentFrame && currentFrame.isInitial,
computed: () => currentFrame && currentFrame.computed
};
})();

View file

@ -35,18 +35,15 @@ ko.computed = (evaluatorFunctionOrOptions, options) => {
function computedObservable() {
if (arguments.length > 0) {
if (typeof writeFunction === "function") {
// Writing a value
writeFunction(...arguments);
} else {
if (typeof writeFunction !== "function") {
throw new Error("Cannot write a value to a ko.computed unless you specify a 'write' option. If you wish to read the current value, don't pass any parameters.");
}
// Writing a value
writeFunction(...arguments);
return this; // Permits chained assignments
} else {
// Reading the value
if (!state.isDisposed) {
ko.dependencyDetection.registerDependency(computedObservable);
}
state.isDisposed || ko.dependencyDetection.registerDependency(computedObservable);
if (state.isDirty || (state.isSleeping && computedObservable.haveDependenciesChanged())) {
computedObservable.evaluateImmediate();
}
@ -132,23 +129,23 @@ function computedBeginDependencyDetectionCallback(subscribable, id) {
}
function evaluateImmediate_CallReadThenEndDependencyDetection(state, dependencyDetectionContext) {
// This function is really part of the evaluateImmediate_CallReadWithDependencyDetection logic.
// You'd never call it from anywhere else. Factoring it out means that evaluateImmediate_CallReadWithDependencyDetection
// can be independent of try/finally blocks, which contributes to saving about 40% off the CPU
// overhead of computed evaluation (on V8 at least).
// This function is really part of the evaluateImmediate_CallReadWithDependencyDetection logic.
// You'd never call it from anywhere else. Factoring it out means that evaluateImmediate_CallReadWithDependencyDetection
// can be independent of try/finally blocks, which contributes to saving about 40% off the CPU
// overhead of computed evaluation (on V8 at least).
try {
return state.readFunction();
} finally {
ko.dependencyDetection.end();
try {
return state.readFunction();
} finally {
ko.dependencyDetection.end();
// For each subscription no longer being used, remove it from the active subscriptions list and dispose it
if (dependencyDetectionContext.disposalCount && !state.isSleeping) {
ko.utils.objectForEach(dependencyDetectionContext.disposalCandidates, computedDisposeDependencyCallback);
}
// For each subscription no longer being used, remove it from the active subscriptions list and dispose it
if (dependencyDetectionContext.disposalCount && !state.isSleeping) {
ko.utils.objectForEach(dependencyDetectionContext.disposalCandidates, computedDisposeDependencyCallback);
}
state.isStale = state.isDirty = false;
}
state.isStale = state.isDirty = false;
}
}
var computedFn = {
@ -170,12 +167,8 @@ var computedFn = {
return false;
}
var dependencies = this.getDependencies();
if (dependencies.includes(obs)) {
return true;
}
return !!dependencies.find(dep =>
dep.hasAncestorDependency && dep.hasAncestorDependency(obs)
);
return dependencies.includes(obs)
|| !!dependencies.find(dep => dep.hasAncestorDependency && dep.hasAncestorDependency(obs));
},
addDependencyTracking: function (id, target, trackingObj) {
if (this[computedState].pure && target === this) {

View file

@ -1,5 +1,5 @@
const observableLatestValue = Symbol('_latestValue'),
length = 'length';
length = 'length';
ko.observable = initialValue => {
function observable() {

View file

@ -12,26 +12,22 @@ ko.observableArray = initialValues => {
ko.observableArray['fn'] = {
'remove': function (valueOrPredicate) {
var underlyingArray = this.peek();
var removedValues = [];
var predicate = typeof valueOrPredicate == "function" && !ko.isObservable(valueOrPredicate) ? valueOrPredicate : function (value) { return value === valueOrPredicate; };
var removed = false;
var predicate = typeof valueOrPredicate == "function" && !ko.isObservable(valueOrPredicate)
? valueOrPredicate : value => value === valueOrPredicate;
var i = underlyingArray.length;
while (i--) {
var value = underlyingArray[i];
if (predicate(value)) {
if (removedValues.length === 0) {
this.valueWillMutate();
}
if (underlyingArray[i] !== value) {
throw Error("Array modified during remove; cannot remove item");
}
removedValues.push(value);
removed || this.valueWillMutate();
removed = true;
underlyingArray.splice(i, 1);
}
}
if (removedValues.length) {
this.valueHasMutated();
}
return removedValues;
removed && this.valueHasMutated();
}
};

View file

@ -41,7 +41,8 @@ var defaultEvent = "change";
var ko_subscribable_fn = {
init: instance => {
instance._subscriptions = { "change": [] };
instance._subscriptions = new Map();
instance._subscriptions.set("change", new Set);
instance._versionNumber = 1;
},
@ -52,7 +53,7 @@ var ko_subscribable_fn = {
var boundCallback = callbackTarget ? callback.bind(callbackTarget) : callback;
var subscription = new koSubscription(self, boundCallback, () => {
ko.utils.arrayRemoveItem(self._subscriptions[event], subscription);
self._subscriptions.get(event).delete(subscription);
if (self.afterSubscriptionRemove)
self.afterSubscriptionRemove(event);
});
@ -60,9 +61,9 @@ var ko_subscribable_fn = {
if (self.beforeSubscriptionAdd)
self.beforeSubscriptionAdd(event);
if (!self._subscriptions[event])
self._subscriptions[event] = [];
self._subscriptions[event].push(subscription);
if (!self._subscriptions.has(event))
self._subscriptions.set(event, new Set);
self._subscriptions.get(event).add(subscription);
return subscription;
},
@ -73,16 +74,14 @@ var ko_subscribable_fn = {
this.updateVersion();
}
if (this.hasSubscriptionsForEvent(event)) {
var subs = event === defaultEvent && this._changeSubscriptions || this._subscriptions[event].slice(0);
var subs = event === defaultEvent && this._changeSubscriptions || new Set(this._subscriptions.get(event));
try {
ko.dependencyDetection.begin(); // Begin suppressing dependency detection (by setting the top frame to undefined)
var i = 0, subscription;
while ((subscription = subs[i++])) {
subs.forEach(subscription => {
// In case a subscription was disposed during the arrayForEach cycle, check
// for isDisposed on each subscription before invoking its callback
if (!subscription._isDisposed)
subscription._callback(valueToNotify);
}
subscription._isDisposed || subscription._callback(valueToNotify);
});
} finally {
ko.dependencyDetection.end(); // End suppressing dependency detection
}
@ -141,7 +140,7 @@ var ko_subscribable_fn = {
if (!isDirty || !self._notificationIsPending) {
didUpdate = !isDirty;
}
self._changeSubscriptions = self._subscriptions[defaultEvent].slice(0);
self._changeSubscriptions = new Set(self._subscriptions.get(defaultEvent));
self._notificationIsPending = ignoreBeforeChange = true;
pendingValue = value;
finish();
@ -163,7 +162,7 @@ var ko_subscribable_fn = {
},
hasSubscriptionsForEvent: function(event) {
return this._subscriptions[event] && this._subscriptions[event].length;
return (this._subscriptions.get(event) || []).size;
},
isDifferent: function(oldValue, newValue) {

View file

@ -66,5 +66,3 @@ ko.tasks = (() => {
return tasks;
})();
ko.exportSymbol('tasks', ko.tasks);

View file

@ -1,26 +1,27 @@
(() => {
ko.utils.domData = new (function () {
let uniqueId = 0,
dataStoreKeyExpandoPropertyName = "__ko__" + (Date.now()),
dataStore = new WeakMap();
let uniqueId = 0,
dataStoreKeyExpandoPropertyName = "__ko__" + (Date.now()),
dataStore = new WeakMap();
return {
get: (node, key) => (dataStore.get(node) || {})[key],
set: (node, key, value) => {
if (dataStore.has(node)) {
dataStore.get(node)[key] = value;
} else {
let dataForNode = {};
dataForNode[key] = value;
dataStore.set(node, dataForNode);
}
return value;
},
getOrSet: function(node, key, value) {
return this.get(node, key) || this.set(node, key, value);
},
clear: node => dataStore.delete(node),
ko.utils.domData = {
get: (node, key) => (dataStore.get(node) || {})[key],
set: (node, key, value) => {
if (dataStore.has(node)) {
dataStore.get(node)[key] = value;
} else {
let dataForNode = {};
dataForNode[key] = value;
dataStore.set(node, dataForNode);
}
return value;
},
getOrSet: function(node, key, value) {
return this.get(node, key) || this.set(node, key, value);
},
clear: node => dataStore.delete(node),
nextKey: () => (uniqueId++) + dataStoreKeyExpandoPropertyName
};
nextKey: () => (uniqueId++) + dataStoreKeyExpandoPropertyName
};
})();

View file

@ -6,22 +6,21 @@ ko.utils.domNodeDisposal = (() => {
const getDisposeCallbacksCollection = (node, createIfNotFound) => {
var allDisposeCallbacks = ko.utils.domData.get(node, domDataKey);
if ((allDisposeCallbacks === undefined) && createIfNotFound) {
allDisposeCallbacks = [];
if (createIfNotFound && !allDisposeCallbacks) {
allDisposeCallbacks = new Set;
ko.utils.domData.set(node, domDataKey, allDisposeCallbacks);
}
return allDisposeCallbacks;
},
destroyCallbacksCollection = node => ko.utils.domData.set(node, domDataKey, undefined),
destroyCallbacksCollection = node => ko.utils.domData.set(node, domDataKey, null),
cleanSingleNode = node => {
// Run all the dispose callbacks
var callbacks = getDisposeCallbacksCollection(node);
if (callbacks) {
callbacks = callbacks.slice(0); // Clone, as the array may be modified during iteration (typically, callbacks will remove themselves)
for (var i = 0; i < callbacks.length; i++)
callbacks[i](node);
// Clone, as the array may be modified during iteration (typically, callbacks will remove themselves)
(new Set(callbacks)).forEach(callback => callback(node));
}
// Erase the DOM data
@ -49,14 +48,14 @@ ko.utils.domNodeDisposal = (() => {
addDisposeCallback : (node, callback) => {
if (typeof callback != "function")
throw new Error("Callback must be a function");
getDisposeCallbacksCollection(node, 1).push(callback);
getDisposeCallbacksCollection(node, 1).add(callback);
},
removeDisposeCallback : (node, callback) => {
var callbacksCollection = getDisposeCallbacksCollection(node);
if (callbacksCollection) {
ko.utils.arrayRemoveItem(callbacksCollection, callback);
callbacksCollection.length || destroyCallbacksCollection(node);
callbacksCollection.delete(callback);
callbacksCollection.size || destroyCallbacksCollection(node);
}
},

View file

@ -1,14 +1,4 @@
ko.utils = {
arrayRemoveItem: (array, itemToRemove) => {
var index = array.indexOf(itemToRemove);
if (index > 0) {
array.splice(index, 1);
}
else if (index === 0) {
array.shift();
}
},
extend: (target, source) => {
source && Object.entries(source).forEach(prop => target[prop[0]] = prop[1]);
return target;
@ -26,11 +16,7 @@ ko.utils = {
return target;
},
emptyDomNode: domNode => {
while (domNode.firstChild) {
ko.removeNode(domNode.firstChild);
}
},
emptyDomNode: domNode => [...domNode.childNodes].forEach(child => ko.removeNode(child)),
moveCleanedNodesToContainerElement: nodes => {
// Ensure it's a real array, as we're about to reparent the nodes and
@ -133,11 +119,8 @@ ko.utils = {
throw error;
}, 0),
registerEventHandler: (element, eventType, handler) => {
var wrappedHandler = ko.utils.catchFunctionErrors(handler);
element.addEventListener(eventType, wrappedHandler, false);
},
registerEventHandler: (element, eventType, handler) =>
element.addEventListener(eventType, ko.utils.catchFunctionErrors(handler), false),
triggerEvent: (element, eventType) => {
if (!(element && element.nodeType))

View file

@ -1,3 +1 @@
ko.version = "##VERSION##";
ko.exportSymbol('version', ko.version);
ko.exportSymbol('version', "##VERSION##");

View file

@ -31,15 +31,14 @@
while (currentNode = currentNode.nextSibling) {
if (isEndComment(currentNode)) {
ko.utils.domData.set(currentNode, matchedEndCommentDataKey, true);
depth--;
if (depth === 0)
if (!--depth)
return children;
}
children.push(currentNode);
if (isStartComment(currentNode))
depth++;
++depth;
}
if (!allowUnbalanced)
throw new Error("Cannot find closing comment tag to match: " + startComment.nodeValue);
@ -49,9 +48,9 @@
function getMatchingEndComment(startComment, allowUnbalanced) {
var allVirtualChildren = getVirtualChildren(startComment, allowUnbalanced);
if (allVirtualChildren) {
if (allVirtualChildren.length > 0)
return allVirtualChildren[allVirtualChildren.length - 1].nextSibling;
return startComment.nextSibling;
return (allVirtualChildren.length
? allVirtualChildren[allVirtualChildren.length - 1]
: startComment).nextSibling;
}
return null; // Must have no matching end comment, and allowUnbalanced is true
}
@ -64,8 +63,7 @@
emptyNode: node => {
if (isStartComment(node)) {
var virtualChildren = getVirtualChildren(node);
for (var i = 0, j = virtualChildren.length; i < j; i++)
ko.removeNode(virtualChildren[i]);
virtualChildren && [...virtualChildren].forEach(child => ko.removeNode(child));
} else
ko.utils.emptyDomNode(node);
},
@ -73,64 +71,48 @@
setDomNodeChildren: (node, childNodes) => {
if (isStartComment(node)) {
ko.virtualElements.emptyNode(node);
var endCommentNode = node.nextSibling; // Must be the next sibling, as we just emptied the children
for (var i = 0, j = childNodes.length; i < j; i++)
endCommentNode.parentNode.insertBefore(childNodes[i], endCommentNode);
node.after(...childNodes);
} else
ko.utils.setDomNodeChildren(node, childNodes);
},
prepend: (containerNode, nodeToPrepend) => {
var insertBeforeNode;
if (isStartComment(containerNode)) {
// Start comments must always have a parent and at least one following sibling (the end comment)
insertBeforeNode = containerNode.nextSibling;
containerNode = containerNode.parentNode;
} else {
insertBeforeNode = containerNode.firstChild;
}
containerNode.insertBefore(nodeToPrepend, insertBeforeNode);
// Start comments must always have a parent and at least one following sibling (the end comment)
isStartComment(containerNode)
? containerNode.nextSibling.before(nodeToPrepend)
: containerNode.prepend(nodeToPrepend);
},
insertAfter: (containerNode, nodeToInsert, insertAfterNode) => {
if (insertAfterNode) {
// Children of start comments must always have a parent and at least one following sibling (the end comment)
var insertBeforeNode = insertAfterNode.nextSibling;
if (isStartComment(containerNode)) {
containerNode = containerNode.parentNode;
}
containerNode.insertBefore(nodeToInsert, insertBeforeNode);
} else {
ko.virtualElements.prepend(containerNode, nodeToInsert);
}
insertAfterNode
? insertAfterNode.after(nodeToInsert)
: ko.virtualElements.prepend(containerNode, nodeToInsert);
},
firstChild: node => {
if (isStartComment(node)) {
return (!node.nextSibling || isEndComment(node.nextSibling)) ? null : node.nextSibling;
let next = node.nextSibling;
return (!next || isEndComment(next)) ? null : next;
}
if (node.firstChild && isEndComment(node.firstChild)) {
let first = node.firstChild;
if (first && isEndComment(first)) {
throw new Error("Found invalid end comment, as the first child of " + node);
}
return node.firstChild;
return first;
},
nextSibling: node => {
if (isStartComment(node)) {
node = getMatchingEndComment(node);
}
if (node.nextSibling && isEndComment(node.nextSibling)) {
if (isUnmatchedEndComment(node.nextSibling)) {
let next = node.nextSibling;
if (next && isEndComment(next)) {
if (isUnmatchedEndComment(next)) {
throw Error("Found end comment without a matching opening comment, as child of " + node);
}
return null;
}
return node.nextSibling;
return next;
},
hasBindingValue: isStartComment,