我知道我可以捕获输入框上的keydown事件并从处理程序返回false以阻止将按键应用于输入框。我想在更高的层次上使用knockout绑定做一些事情,也就是说,我想停止即将发生的更新,例如,在检查输入框中键入的值是否非法之后。我不希望非法值被应用到我的视图模型,然后我将不得不"手动"撤销它。我想在视图模型以任何方式改变之前停止它
我拥有的另一个真实用例是,我想在输入被应用到视图模型之前改变一些关于它的东西。例如,我可能想在更新之前保存当前状态的副本。
如何在knockout框架中执行此操作?我可以处理mounsedown或focus事件来准备保存值,然后让更新发生,然后在需要时撤销它,但我想要停止更新本身。
这是我尝试的:
<html>
<head>
<title>Initial Write Protect Example</title>
<script src="knockout-3.5.0.debug.js" type="text/javascript"></script>
<script type="text/javascript">
const data = {
value: ko.computed({
owner: this,
read: function() { return "foo"; },
write: function(x) { console.log("writing: " + x); debugger; }
}),
};
let allowKey = true;
const changeFn = function(x) { console.log("changed: ", x, data.value()); debugger; return false; };
const beforeChangeFn = function(x) { console.log("about to change: ", x, data.value()); debugger; return false; };
const keydownFn = function(x,event) { console.log("keydown: " + event.key + " allow? " + allowKey); return allowKey; };
const keyupFn = function(x,event) { console.log("keyupn: " + event.key); return false; };
</script>
</head>
<body>
<p>
<span>Current Value:</span>
<span data-bind="text: value"></span>
<br/>
<input data-bind="value: value,
event:{
change: changeFn,
beforeChange: beforeChangeFn,
keydown: keydownFn,
keyup: keyupFn
}"></input>
</p>
<script type="text/javascript">ko.applyBindings(data);</script>
</body>
</html>
由此可见,虽然我可以停止一个键生效(在调试器中设置allowKey为false),但我不能停止更改,没有beforeChange事件被发送(它在ko源代码中被提到过一次,所以我想我会尝试一下),并且更改事件发生在ko之后。可观察到的写作。所以,太晚了,当change事件触发时,视图模型已经被改变了。
我想我可以进入knockout源代码来修复自己一个很好的beforeChange事件,其返回值为false将停止进一步处理(并且实际上将输入框中的值还原为之前的值)。但我在想,我是不是已经该走别的路了?
:
我现在知道了我可以为一个可观察对象订阅beforeChange事件:
observable.subscribe((newValue, eventName) => handle(newValue, eventName, 'beforeChange');
我在这里明确了两个参数,以指出新值在这些处理程序中不可用。这是一个遗憾,因为我也需要新的值。
下面是如何使用可写计算来防止更新传播到源可观察对象的方法。
您可以使用扩展器使其易于重用。我编写了扩展程序来接受一个谓词函数,该函数接受单个字符串并返回true或false。取决于你想用它做什么,你可能想要支持一个包含prevValue
和nextValue
的谓词。这将导致validator(target(), str)
。
注意:没有响应的输入字段可能会给用户带来糟糕的用户体验,所以请确保为在输入字段中输入的用户提供反馈。
ko.extenders.validate = function(target, validator) {
return ko.computed({
read: target,
write: str => {
if (!validator(str)) {
console.log(`Attempt to update ${data()} to ${str}, which is invalid.`);
target.notifySubscribers(true);
} else data(str);
}
}).extend({ notify: "always" });
}
const data = ko.observable("bdfgh");
ko.applyBindings({
data: data.extend({ validate: charsAreInAlphabeticalOrder })
});
function charsAreInAlphabeticalOrder(str) {
if (str.length <= 1) return true;
return (
str[0].localeCompare(str[1]) === -1 &&
charsAreInAlphabeticalOrder(str.slice(1))
);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<label>
Enter characters alpabetically
<input type="text" data-bind="textInput: data">
</label>