在淘汰赛中,假设我们有这样的情况:
var person = {
name: "The H. Dude",
telecom: [
"mailto:dude@host.com",
"tel:+1-987-654-3210"
]
}
我有一个数据绑定元素,如下所示:
<label>Name: <input type="text" data-bind="value: name"/></label>
<label>Phone:<input type="text" data-bind="value: telecom.find(url => url.startsWith('tel:'))"/></label>
<label>Email:<input type="text" data-bind="value: telecom.find(url => url.startsWith('mailto:'))"/></label>
这是正确的。但是,这将击中用户的头部与URL方案前缀。
所以,我们要做的是这样的事情:
data-bind="value: telecom.find(url => url.startsWith('tel:')).substring('tel:'.length)"
和
data-bind="value: telecom.find(url => url.startsWith('mailto:')).substing('mailto:'.length)"
分别为。
对于只读属性,这一点很好,我可能只是显示它。但是当我输入一个新的电话号码时,表达式以一个函数调用结束,这是不能被写入的,当然substring函数不知道如何向后工作,在用户输入的值被写入对象之前,预先挂起“tel:“或“mailto:“前缀。
我曾经深入研究过XForms引擎,在此之前,我用类似于XPath的路径语言创建了自己的UI框架,它专门用于双向数据绑定。在那里,我发明了“转换”的概念,它是一对函数,在这种情况下,您可以说:
telPrefixConversion = {
prefix: "tel:",
forward: function(value) { return value.substring(this.prefix.length); }
backward: function(value) { return prefix + value; }
}
我想这在击倒对手时也会很有用。那么在捆绑时我可以说
data-bind="{value: telecom.find(url => url.startsWith('mailto:')), conversion: telPrefixConversion}"
并且现在knockout-3.5.0.debug.js第2842行可以执行以下操作:
if (twoWayBindings[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("'" + writeKey + "':function(_z){" + conversion.backward(writableVal) + "=_z}");
现在,我已经可以想到一种方法来实现这一点,即使用计算的可观察性来代替,但这似乎是一个沉重的负担。这种转换原理非常强大,因为它还可以将复杂对象转换为UI字符串表示,然后在返回的过程中,它又会再次转换为复杂对象。
我也很想在knockout中实现这个功能,因为我已经在其他数据绑定UI工具上实现过两次了。我应该继续吗?还是我错过了这样一个简单的功能?
尝试1 -计算的可观察值:从那时起,我就开始使用计算可观察性来解决我的迫切需要,但我发现,当你在某种重复的组中有多个电话号码时,这是可行的。例如,假设你有一个朋友列表,其中有姓名、电子邮件和电话号码,添加计算便笺属性只是为了将值与字符串表示进行转换是不好的。
这里的第一个答案也暗示了计算可观测性,但是我最初的尝试和那个答案中所暗示的都太特殊了,我们希望有能力在任何地方做它,不管它是一个对象的属性还是另一个对象的属性,不管它们是否在数组中重复。
所以我在想这样的事情:
class Conversion {
constructor(object, property) {
let self = this;
object[property] = ko.computed({
read: function() {
return self.m2v(ko.util.unwrapObservable(this[property]));
},
write: function(value) {
this[property](m2v(value)); // <<<< infinite recursion????
},
owner: object
});
}
model2view(modelValue) { throw new Error("undefined abstract function called"); }
view2model(viewValue) { throw new Error("undefined abstract function called"); }
}
这些model 2 view和view 2 model函数随后可以被覆盖,以处理我的前缀,或者日期格式as in this other question等。
我被困在这个问题上:
1.我们用可观察值代替了实际值
1.当我们将视图值分配给可观察值时,我们将进入这个循环
我们仍然需要一些新的属性来存储视图的值,而不是保存实际模型值的属性,这正是我试图避免的,我将这个函数对提供给了其他的绑定选项。
尝试2 -扩展器我发现可观察到的扩展器几乎可以完成这个任务:
ko.extenders.prefixConversion = function(target, prefix) {
const read = function() { return target().substring(prefix.length); };
const result = ko.pureComputed({
read: read,
write: function(value) { target(prefix + value); }
});
result(read());
return result;
}
这与下面的初始化器一起使用,并且还与这些作为具有URL元素的对象的电信事物一起使用,而不仅仅是作为普通字符串的URL,例如:
o = {...电信:[ { url:“电话:+1-987-654-3210”},{ url:“mailto:dude@host.com“}]¡}
然后将其转化为可观测的
telecom
.filter(t => t.url.match('^(tel|mailto):'))
.forEach(t => {
const prefix = t.url.substring(0, value.indexOf(':')+1);
t.url = ko.observable(t.url).extend({prefixConversion: prefix});
});
nice和general适用于不同的前缀。下面是我们最终绑定它们的方法:
<label>Email:<input data-bind="value: telecom.find(t => t.url.startsWith('mailto:')).url"/></label>
<label>Phone:<input data-bind="value: telecom.find(t => t.url().startsWith('tel:')).url"/></label>
这会导致telecom的值被清除,因为它会把去掉前缀的值存回去,它几乎可以工作,我可以看到读和写函数如何产生正确的值,写添加前缀,读删除它,但是在某个地方,不知何故,没有前缀的值被写入到可观察的._state._latestValue中,然后我们完全失去了对它的控制。
我不知道这是不是一个bug,我甚至不知道这个没有前缀的值是如何被写入可观察状态的。
但最终,我发现即使是这样的方法也太昂贵了。我认为这个问题严格地适用于绑定模式view和text,也许还适用于textInput,也就是说,只要最终的表示是字符串。它只适用于UI表面表示,它与所表示的对象及其属性无关。任何代码都不应该获得tel:和邮件地址:前缀,这仅适用于用户,因此,此转换可以绑定到绑定处理程序。
我自己的解决方案因此,我用以前使用XForms框架解决这个问题的方法来解决这个问题。我添加了v2 t和t2 v函数:v2 t表示值到文字,而t2 v表示文字到值。例如:
<input ...
data-bind="..."
data-t2v="return t.indexOf('tel:') == 0 ? t : 'tel:' + t;"
data-v2t="var p = v.substring(0,4); return p == 'tel:' ? v.substring(4) : v;"
.../>
这些属性在第一次初始化期间(或在需要时延迟)转换为函数:
if(!(element.t2v === null || element.t2v)) {
const att = element.getAttribute("t2v");
element.t2v = att ? new Function("t", att) : null;
}
if(!(element.v2t === null || element.v2t)) {
const att = element.getAttribute("v2t");
element.v2t = att ? new Function("v", att) : null;
}
然后在一些选定的地方,我在分别阅读和写入之前分别检查和使用element.t2v和.v2t属性。示例:
element.value = element.v2t ? element.v2t(element, value) : value;
这是一个我已经使用了十年的解决方案,我认为它是正确的。当UI文本交互更复杂时,它不会被使用。任何需要击键事件粒度或在其他方面复杂的东西,比如需要从其他对象输入,都会被不同地处理。
我已经实施了这个方案,现在正在工作。但这是对淘汰赛的一个改变,这个问题并不是没有意义的,因为也许淘汰赛的人可能会有一个不同的解决方案。
例如,我注意到还有ko.utils.domData,我还不了解它,但它可能提供了一种实现方法。但我的解决方案在不同的数据绑定框架中测试了很长时间,现在在knockout中实现了,我认为它是正确的解决方案,因为再次强调,值的测试和值到文本的转换是关于小部件和它如何呈现模型数据值的。而不是数据。
2条答案
按热度按时间vfwfrxfs1#
使用内置的writable computed functionality,无需修改Knockout即可实现此目的。
假设您有一个人员列表,如下所示:
然后,您可以在视图模型上定义一个构造函数,该函数返回一个可写的计算:
并将其绑定到您的输入:
工作演示:https://jsfiddle.net/thebluenile/ujb50vwn/
bkhjykvo2#
在我看来,你试图在视图中做太多的事情,而knockout希望你在视图模型中添加更多的逻辑。
下面是一个简单的Person View Model的示例,它完成了两件事:
第一个