Cleanup KnockoutJS

This commit is contained in:
djmaze 2021-09-28 14:40:24 +02:00
parent 4c7ce61bc0
commit 4142526ba6
9 changed files with 121 additions and 279 deletions

View file

@ -8,8 +8,6 @@
// (0, eval)('this') is a robust way of getting a reference to the global object
// For details, see http://stackoverflow.com/questions/14119988/return-this-0-evalthis/14120023#14120023
var document = window['document'],
navigator = window['navigator'],
JSON = window["JSON"],
koExports = {};
// Internally, all KO objects are attached to koExports (even the non-exported ones whose names will be minified by the closure compiler).
// In the future, the following "ko" variable may be made distinct from "koExports" so that private objects are not externally reachable.
@ -55,7 +53,7 @@ ko.utils = {
var templateDocument = (nodesArray[0] && nodesArray[0].ownerDocument) || document;
var container = templateDocument.createElement('div');
nodes.forEach(node => container.append(ko.cleanNode(node)));
nodesArray.forEach(node => container.append(ko.cleanNode(node)));
return container;
},
@ -97,7 +95,7 @@ ko.utils = {
// Rule [B]
while (continuousNodeArray.length > 1
&& continuousNodeArray[continuousNodeArray.length - 1].parentNode !== parentNode)
continuousNodeArray.length--;
--continuousNodeArray.length;
// Rule [C]
if (continuousNodeArray.length > 1) {
@ -119,39 +117,11 @@ ko.utils = {
string.trim() :
string.toString().replace(/^[\s\xa0]+|[\s\xa0]+$/g, ''),
stringStartsWith: (string, startsWith) => {
string = string || "";
if (startsWith.length > string.length)
return false;
return string.substring(0, startsWith.length) === startsWith;
},
domNodeIsContainedBy: (node, containedByNode) =>
containedByNode.contains(node.nodeType !== 1 ? node.parentNode : node),
domNodeIsAttachedToDocument: node => ko.utils.domNodeIsContainedBy(node, node.ownerDocument.documentElement),
catchFunctionErrors: delegate => {
return ko['onError'] ? function () {
try {
return delegate.apply(this, arguments);
} catch (e) {
ko['onError'] && ko['onError'](e);
throw e;
}
} : delegate;
},
setTimeout: (handler, timeout) => setTimeout(ko.utils.catchFunctionErrors(handler), timeout),
deferError: error => setTimeout(() => {
ko['onError'] && ko['onError'](error);
throw error;
}, 0),
registerEventHandler: (element, eventType, handler) =>
element.addEventListener(eventType, ko.utils.catchFunctionErrors(handler), false),
triggerEvent: (element, eventType) => {
if (!(element && element.nodeType))
throw new Error("element must be a DOM node when calling triggerEvent");
@ -306,7 +276,9 @@ ko.tasks = (() => {
if (nextIndexToProcess > mark) {
if (++countMarks >= 5000) {
nextIndexToProcess = taskQueueLength; // skip all tasks remaining in the queue since any of them could be causing the recursion
ko.utils.deferError(Error("'Too much recursion' after processing " + countMarks + " task groups."));
setTimeout(() => {
throw Error(`'Too much recursion' after processing ${countMarks} task groups.`)
}, 0)
break;
}
mark = taskQueueLength;
@ -314,7 +286,7 @@ ko.tasks = (() => {
try {
task();
} catch (ex) {
ko.utils.deferError(ex);
setTimeout(() => { throw ex }, 0);
}
}
}
@ -381,7 +353,7 @@ function throttle(callback, timeout) {
var timeoutInstance;
return () => {
if (!timeoutInstance) {
timeoutInstance = ko.utils.setTimeout(() => {
timeoutInstance = setTimeout(() => {
timeoutInstance = 0;
callback();
}, timeout);
@ -393,7 +365,7 @@ function debounce(callback, timeout) {
var timeoutInstance;
return () => {
clearTimeout(timeoutInstance);
timeoutInstance = ko.utils.setTimeout(callback, timeout);
timeoutInstance = setTimeout(callback, timeout);
};
}
@ -915,7 +887,7 @@ ko.extenders['trackArrayChanges'] = (target, options) => {
case 'push':
offset = arrayLength;
case 'unshift':
for (var index = 0; index < argsLength; index++) {
for (let index = 0; index < argsLength; index++) {
pushDiff('added', args[index], offset + index);
}
break;
@ -936,7 +908,7 @@ ko.extenders['trackArrayChanges'] = (target, options) => {
endAddIndex = startIndex + argsLength - 2,
endIndex = Math.max(endDeleteIndex, endAddIndex),
additions = [], deletions = [];
for (var index = startIndex, argsIndex = 2; index < endIndex; ++index, ++argsIndex) {
for (let index = startIndex, argsIndex = 2; index < endIndex; ++index, ++argsIndex) {
if (index < endDeleteIndex)
deletions.push(pushDiff('deleted', rawArray[index], index));
if (index < endAddIndex)
@ -1169,7 +1141,7 @@ var computedFn = {
throttleEvaluationTimeout = computedObservable['throttleEvaluation'];
if (throttleEvaluationTimeout && throttleEvaluationTimeout >= 0) {
clearTimeout(this[computedState].evaluationTimeoutInstance);
this[computedState].evaluationTimeoutInstance = ko.utils.setTimeout(() =>
this[computedState].evaluationTimeoutInstance = setTimeout(() =>
computedObservable.evaluateImmediate(true /*notifyChange*/)
, throttleEvaluationTimeout);
} else if (computedObservable._evalDelayed) {
@ -2098,24 +2070,6 @@ ko.expressionRewriting = (() => {
}
};
// Given a function that returns bindings, create and return a new object that contains
// binding value-accessors functions. Each accessor function calls the original function
// so that it always gets the latest value and all dependencies are captured. This is used
// by ko.applyBindingsToNode and getBindingsAndMakeAccessors.
function makeAccessorsFromFunction(callback) {
return ko.utils.objectMap(ko.dependencyDetection.ignore(callback), (value, key) =>
() => callback()[key]
);
}
// Given a bindings function or object, create and return a new object that contains
// binding value-accessors functions. This is used by ko.applyBindingsToNode.
function makeBindingAccessors(bindings, context, node) {
return (typeof bindings === 'function')
? makeAccessorsFromFunction(bindings.bind(null, context, node))
: ko.utils.objectMap(bindings, value => () => value);
}
function validateThatBindingIsAllowedForVirtualElements(bindingName) {
var validator = ko.virtualElements.allowedBindings[bindingName];
if (!validator)
@ -2773,9 +2727,8 @@ ko.expressionRewriting = (() => {
}
})();
var attrHtmlToJavaScriptMap = { 'class': 'className', 'for': 'htmlFor' };
ko.bindingHandlers['attr'] = {
'update': (element, valueAccessor, allBindings) => {
'update': (element, valueAccessor) => {
var value = ko.utils.unwrapObservable(valueAccessor()) || {};
ko.utils.objectForEach(value, function(attrName, attrValue) {
attrValue = ko.utils.unwrapObservable(attrValue);
@ -2787,7 +2740,7 @@ ko.bindingHandlers['attr'] = {
// To cover cases like "attr: { checked:someProp }", we want to remove the attribute entirely
// when someProp is a "no value"-like value (strictly null, false, or undefined)
// (because the absence of the "checked" attr is how to mark an element as not checked, etc.)
var toRemove = (attrValue === false) || (attrValue === null) || (attrValue === undefined);
var toRemove = (attrValue === false) || (attrValue == null);
if (toRemove) {
namespace ? element.removeAttributeNS(namespace, attrName) : element.removeAttribute(attrName);
} else {
@ -2867,7 +2820,7 @@ ko.bindingHandlers['event'] = {
var eventsToHandle = valueAccessor() || {};
ko.utils.objectForEach(eventsToHandle, eventName => {
if (typeof eventName == "string") {
ko.utils.registerEventHandler(element, eventName, function (event) {
element.addEventListener(eventName, function (event) {
var handlerReturnValue;
var handlerFunction = valueAccessor()[eventName];
if (!handlerFunction)
@ -2942,10 +2895,13 @@ ko.bindingHandlers['hasfocus'] = {
var handleElementFocusIn = handleElementFocusChange.bind(null, true);
var handleElementFocusOut = handleElementFocusChange.bind(null, false);
ko.utils.registerEventHandler(element, "focus", handleElementFocusIn);
ko.utils.registerEventHandler(element, "focusin", handleElementFocusIn);
ko.utils.registerEventHandler(element, "blur", handleElementFocusOut);
ko.utils.registerEventHandler(element, "focusout", handleElementFocusOut);
var registerEventHandler = (event, handler) =>
element.addEventListener(event, handler);
registerEventHandler("focus", handleElementFocusIn);
registerEventHandler("focusin", handleElementFocusIn);
registerEventHandler("blur", handleElementFocusOut);
registerEventHandler("focusout", handleElementFocusOut);
// Assume element is not focused (prevents "blur" being called initially)
element[hasfocusLastValue] = false;
@ -2984,10 +2940,10 @@ ko.bindingHandlers['html'] = {
function makeWithIfBinding(bindingKey, isWith, isNot) {
ko.bindingHandlers[bindingKey] = {
'init': (element, valueAccessor, allBindings, viewModel, bindingContext) => {
var didDisplayOnLastUpdate, savedNodes, contextOptions = {}, needAsyncContext;
var savedNodes, contextOptions = {}, needAsyncContext;
if (isWith) {
var as = allBindings.get('as'), noChildContext = false;
var as = allBindings.get('as');
contextOptions = { 'as': as, 'exportDependencies': true };
}
@ -3032,8 +2988,6 @@ function makeWithIfBinding(bindingKey, isWith, isNot) {
ko.bindingEvent.notify(element, ko.bindingEvent.childrenComplete);
}
didDisplayOnLastUpdate = shouldDisplay;
}, { disposeWhenNodeIsRemoved: element });
return { 'controlsDescendantBindings': true };
@ -3210,7 +3164,7 @@ ko.bindingHandlers['style'] = {
ko.utils.objectForEach(value, (styleName, styleValue) => {
styleValue = ko.utils.unwrapObservable(styleValue);
if (styleValue === null || styleValue === undefined || styleValue === false) {
if (styleValue == null || styleValue === false) {
// Empty string removes the value, whereas null/undefined have no effect
styleValue = "";
}
@ -3235,7 +3189,7 @@ ko.bindingHandlers['submit'] = {
'init': (element, valueAccessor, allBindings, viewModel, bindingContext) => {
if (typeof valueAccessor() != "function")
throw new Error("The value for a submit binding must be a function");
ko.utils.registerEventHandler(element, "submit", event => {
element.addEventListener("submit", event => {
var handlerReturnValue;
var value = valueAccessor();
try { handlerReturnValue = value.call(bindingContext['$data'], element); }
@ -3284,26 +3238,15 @@ ko.bindingHandlers['textInput'] = {
}
};
var deferUpdateModel = event => {
if (!timeoutHandle) {
// The elementValueBeforeEvent variable is set *only* during the brief gap between an
// event firing and the updateModel function running. This allows us to ignore model
// updates that are from the previous state of the element, usually due to techniques
// such as rateLimit. Such updates, if not ignored, can cause keystrokes to be lost.
elementValueBeforeEvent = element.value;
timeoutHandle = ko.utils.setTimeout(updateModel, 4);
}
};
var updateView = () => {
var modelValue = ko.utils.unwrapObservable(valueAccessor());
if (modelValue === null || modelValue === undefined) {
if (modelValue == null) {
modelValue = '';
}
if (elementValueBeforeEvent !== undefined && modelValue === elementValueBeforeEvent) {
ko.utils.setTimeout(updateView, 4);
setTimeout(updateView, 4);
return;
}
@ -3316,7 +3259,7 @@ ko.bindingHandlers['textInput'] = {
};
var onEvent = (event, handler) =>
ko.utils.registerEventHandler(element, event, handler);
element.addEventListener(event, handler);
onEvent('input', updateModel);
@ -3350,6 +3293,8 @@ ko.bindingHandlers['value'] = {
var eventsToCatch = new Set;
var requestedEventsToCatch = allBindings.get("valueUpdate");
var elementValueBeforeEvent = null;
var registerEventHandler = (event, handler) =>
element.addEventListener(event, handler);
if (requestedEventsToCatch) {
// Allow both individual event names, and arrays of event names
@ -3373,7 +3318,7 @@ ko.bindingHandlers['value'] = {
// This is useful, for example, to catch "keydown" events after the browser has updated the control
// (otherwise, ko.selectExtensions.readValue(this) will receive the control's value *before* the key event)
var handler = valueUpdateHandler;
if (ko.utils.stringStartsWith(eventName, "after")) {
if ((eventName||'').startsWith("after")) {
handler = () => {
// The elementValueBeforeEvent variable is non-null *only* during the brief gap between
// a keyX event firing and the valueUpdateHandler running, which is scheduled to happen
@ -3383,11 +3328,11 @@ ko.bindingHandlers['value'] = {
// techniques like rateLimit can trigger model changes at critical moments that will
// override the user's inputs, causing keystrokes to be lost.
elementValueBeforeEvent = ko.selectExtensions.readValue(element);
ko.utils.setTimeout(valueUpdateHandler, 0);
setTimeout(valueUpdateHandler, 0);
};
eventName = eventName.substring("after".length);
eventName = eventName.substring(5);
}
ko.utils.registerEventHandler(element, eventName, handler);
registerEventHandler(eventName, handler);
});
var updateFromModel;
@ -3396,7 +3341,7 @@ ko.bindingHandlers['value'] = {
// For file input elements, can only write the empty string
updateFromModel = () => {
var newValue = ko.utils.unwrapObservable(valueAccessor());
if (newValue === null || newValue === undefined || newValue === "") {
if (newValue == null || newValue === "") {
element.value = "";
} else {
ko.dependencyDetection.ignore(valueUpdateHandler); // reset the model to match the element
@ -3408,7 +3353,7 @@ ko.bindingHandlers['value'] = {
var elementValue = ko.selectExtensions.readValue(element);
if (elementValueBeforeEvent !== null && newValue === elementValueBeforeEvent) {
ko.utils.setTimeout(updateFromModel, 0);
setTimeout(updateFromModel, 0);
return;
}
@ -3434,7 +3379,7 @@ ko.bindingHandlers['value'] = {
var updateFromModelComputed;
ko.bindingEvent.subscribe(element, ko.bindingEvent.childrenComplete, () => {
if (!updateFromModelComputed) {
ko.utils.registerEventHandler(element, "change", valueUpdateHandler);
registerEventHandler("change", valueUpdateHandler);
updateFromModelComputed = ko.computed(updateFromModel, { disposeWhenNodeIsRemoved: element });
} else if (allBindings.get('valueAllowUnset')) {
updateFromModel();
@ -3443,7 +3388,7 @@ ko.bindingHandlers['value'] = {
}
}, null, { 'notifyImmediately': true });
} else {
ko.utils.registerEventHandler(element, "change", valueUpdateHandler);
registerEventHandler("change", valueUpdateHandler);
ko.computed(updateFromModel, { disposeWhenNodeIsRemoved: element });
}
},
@ -3587,8 +3532,7 @@ makeEventHandlerShortcut('click');
: null;
}
function executeTemplate(targetNodeOrNodeArray, renderMode, template, bindingContext, options) {
options = options || {};
function executeTemplate(targetNodeOrNodeArray, renderMode, template, bindingContext) {
var firstTargetNode = targetNodeOrNodeArray && getFirstNodeFromPossibleArray(targetNodeOrNodeArray);
var templateDocument = (firstTargetNode || template || {}).ownerDocument;