mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-09-02 14:07:04 +03:00
115 lines
5.9 KiB
JavaScript
115 lines
5.9 KiB
JavaScript
ko.bindingHandlers['value'] = {
|
|
'init': (element, valueAccessor, allBindings) => {
|
|
var isSelectElement = element.matches("SELECT"),
|
|
isInputElement = element.matches("INPUT");
|
|
|
|
// If the value binding is placed on a radio/checkbox, then just pass through to checkedValue and quit
|
|
if (isInputElement && (element.type == "checkbox" || element.type == "radio")) {
|
|
ko.applyBindingAccessorsToNode(element, { 'checkedValue': valueAccessor });
|
|
return;
|
|
}
|
|
|
|
var eventsToCatch = new Set,
|
|
requestedEventsToCatch = allBindings.get("valueUpdate"),
|
|
elementValueBeforeEvent = null,
|
|
updateFromModel,
|
|
registerEventHandler = (event, handler) =>
|
|
element.addEventListener(event, handler),
|
|
|
|
valueUpdateHandler = () => {
|
|
elementValueBeforeEvent = null;
|
|
var modelValue = valueAccessor();
|
|
var elementValue = ko.selectExtensions.readValue(element);
|
|
ko.expressionRewriting.writeValueToProperty(modelValue, allBindings, 'value', elementValue);
|
|
};
|
|
|
|
if (requestedEventsToCatch) {
|
|
// Allow both individual event names, and arrays of event names
|
|
if (typeof requestedEventsToCatch == "string") {
|
|
eventsToCatch.add(requestedEventsToCatch);
|
|
} else {
|
|
requestedEventsToCatch.forEach(item => eventsToCatch.add(item));
|
|
}
|
|
eventsToCatch.delete("change"); // We'll subscribe to "change" events later
|
|
}
|
|
|
|
eventsToCatch.forEach(eventName => {
|
|
// The syntax "after<eventname>" means "run the handler asynchronously after the event"
|
|
// 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 ((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
|
|
// at the earliest asynchronous opportunity. We store this temporary information so that
|
|
// if, between keyX and valueUpdateHandler, the underlying model value changes separately,
|
|
// we can overwrite that model value change with the value the user just typed. Otherwise,
|
|
// 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);
|
|
setTimeout(valueUpdateHandler, 0);
|
|
};
|
|
eventName = eventName.slice(5);
|
|
}
|
|
registerEventHandler(eventName, handler);
|
|
});
|
|
|
|
if (isInputElement && element.type == "file") {
|
|
// For file input elements, can only write the empty string
|
|
updateFromModel = () => {
|
|
var newValue = ko.utils.unwrapObservable(valueAccessor());
|
|
if (newValue == null || newValue === "") {
|
|
element.value = "";
|
|
} else {
|
|
ko.dependencyDetection.ignore(valueUpdateHandler); // reset the model to match the element
|
|
}
|
|
}
|
|
} else {
|
|
updateFromModel = () => {
|
|
var newValue = ko.utils.unwrapObservable(valueAccessor());
|
|
var elementValue = ko.selectExtensions.readValue(element);
|
|
|
|
if (elementValueBeforeEvent !== null && newValue === elementValueBeforeEvent) {
|
|
setTimeout(updateFromModel, 0);
|
|
return;
|
|
}
|
|
|
|
var valueHasChanged = newValue !== elementValue;
|
|
|
|
if (valueHasChanged || elementValue === undefined) {
|
|
if (isSelectElement) {
|
|
var allowUnset = allBindings.get('valueAllowUnset');
|
|
ko.selectExtensions.writeValue(element, newValue, allowUnset);
|
|
if (!allowUnset && newValue !== ko.selectExtensions.readValue(element)) {
|
|
// If you try to set a model value that can't be represented in an already-populated dropdown, reject that change,
|
|
// because you're not allowed to have a model value that disagrees with a visible UI selection.
|
|
ko.dependencyDetection.ignore(valueUpdateHandler);
|
|
}
|
|
} else {
|
|
ko.selectExtensions.writeValue(element, newValue);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
if (isSelectElement) {
|
|
var updateFromModelComputed;
|
|
ko.bindingEvent.subscribe(element, ko.bindingEvent.childrenComplete, () => {
|
|
if (!updateFromModelComputed) {
|
|
registerEventHandler("change", valueUpdateHandler);
|
|
updateFromModelComputed = ko.computed(updateFromModel, { disposeWhenNodeIsRemoved: element });
|
|
} else if (allBindings.get('valueAllowUnset')) {
|
|
updateFromModel();
|
|
} else {
|
|
valueUpdateHandler();
|
|
}
|
|
}, null, { 'notifyImmediately': true });
|
|
} else {
|
|
registerEventHandler("change", valueUpdateHandler);
|
|
ko.computed(updateFromModel, { disposeWhenNodeIsRemoved: element });
|
|
}
|
|
},
|
|
'update': () => {} // Keep for backwards compatibility with code that may have wrapped value binding
|
|
};
|
|
ko.expressionRewriting.twoWayBindings.add('value');
|