KnockoutJS 3.X API 第七章 其他技术(2) 使用扩展器来增加可观察量(监控属性)
Knockout observables提供了支持读取/写入值并在值改变时通知订阅者所需的基本功能。 但在某些情况下,您可能希望向可观察者添加其他功能。 这可能包括通过在可观察者前面放置一个可写的计算可观察符来向可观察或截取写入添加额外的属性。 敲除扩展器提供了一种简单和灵活的方式来对可观察者进行这种类型的扩充。
如何创建扩展器
创建扩展器涉及向ko.extenders对象添加一个函数。 函数接受observable本身作为第一个参数和第二个参数中的任何选项。 然后它可以返回observable或返回一些新的像一个计算的observable,它以某种方式使用原来的observable。
这个简单的logChange扩展器订阅了observable,并使用控制台写入任何更改以及可配置的消息。
ko.extenders.logChange = function(target, option) { target.subscribe(function(newValue) { console.log(option + ": " + newValue); }); return target; };
您将通过调用observable的extend函数并传递包含日志Change属性的对象来使用此扩展器。
this.firstName = ko.observable("Bob").extend({logChange: "first name"});
如果firstName observable值更改为Ted,那么控制台将显示名字:Ted。
示例1:强制输入为数字
此示例创建一个扩展器,该扩展器强制对可观察对象的写入被四舍五入到可配置的精度级别。 在这种情况下,扩展器将返回一个新的可写的计算可观察,它将位于真正的可观察的拦截写入之前。
(round to whole number)
(round to two decimals)
UI源码:
<p><input data-bind="value: myNumberOne" /> (round to whole number)</p> <p><input data-bind="value: myNumberTwo" /> (round to two decimals)</p>
视图模型源码:
ko.extenders.numeric = function(target, precision) { //create a writable computed observable to intercept writes to our observable var result = ko.pureComputed({ read: target, //always return the original observables value write: function(newValue) { var current = target(), roundingMultiplier = Math.pow(10, precision), newValueAsNum = isNaN(newValue) ? 0 : +newValue, valueToWrite = Math.round(newValueAsNum * roundingMultiplier) / roundingMultiplier; //only write if it changed if (valueToWrite !== current) { target(valueToWrite); } else { //if the rounded value is the same, but a different value was written, force a notification for the current field if (newValue !== current) { target.notifySubscribers(valueToWrite); } } } }).extend({ notify: 'always' }); //initialize with current value to make sure it is rounded appropriately result(target()); //return the new computed observable return result; }; function AppViewModel(one, two) { this.myNumberOne = ko.observable(one).extend({ numeric: 0 }); this.myNumberTwo = ko.observable(two).extend({ numeric: 2 }); } ko.applyBindings(new AppViewModel(221.2234, 123.4525));
注意,为了自动从UI中删除被拒绝的值,有必要在计算的observable上使用.extend({notify:'always'})。 没有这个,用户可能输入一个无效的newValue,当四舍五入得到一个不变的valueToWrite。 然后,由于模型值不会改变,因此在UI中没有更新文本框的通知。 使用{notify:'always'}会导致文本框刷新(删除被拒绝的值),即使计算的属性没有更改值。
示例2:向可观察者添加验证
此示例创建一个扩展器,允许将observable标记为必需。 这个扩展器不是返回一个新的对象,而是简单地向现有的observable添加额外的子可观察量。 因为observables是函数,它们实际上可以有自己的属性。 但是,当视图模型转换为JSON时,子可观察项将被删除,我们将只剩下我们的实际observable的值。 这是一个很好的方法来添加只与UI相关的附加功能,而不需要发送回服务器。
UI源码:
<p data-bind="css: { error: firstName.hasError }"> <input data-bind='value: firstName, valueUpdate: "afterkeydown"' /> <span data-bind='visible: firstName.hasError, text: firstName.validationMessage'> </span> </p> <p data-bind="css: { error: lastName.hasError }"> <input data-bind='value: lastName, valueUpdate: "afterkeydown"' /> <span data-bind='visible: lastName.hasError, text: lastName.validationMessage'> </span> </p>
视图模型晕吗:
ko.extenders.required = function(target, overrideMessage) { //add some sub-observables to our observable target.hasError = ko.observable(); target.validationMessage = ko.observable(); //define a function to do validation function validate(newValue) { target.hasError(newValue ? false : true); target.validationMessage(newValue ? "" : overrideMessage || "This field is required"); } //initial validation validate(target()); //validate whenever the value changes target.subscribe(validate); //return the original observable return target; }; function AppViewModel(first, last) { this.firstName = ko.observable(first).extend({ required: "Please enter a first name" }); this.lastName = ko.observable(last).extend({ required: "" }); } ko.applyBindings(new AppViewModel("Bob","Smith"));
应用多个扩展程序
多个扩展器可以在对可观察者的.extended方法的单个调用中应用。
this.firstName = ko.observable(first).extend({ required: "Please enter a first name", logChange: "first name" });
在这种情况下,required和logChange扩展器都会对我们的observable执行。