使用映射插件时,在Knockout中未定义beforeChange值 [英] beforeChange value is undefined in Knockout when using the mapping plugin
问题描述
基于此问题的答案,我尝试通过以下代码在可观察到的值中获取值. /p>
var phoneBook;
function debug(s) {
$("#log").append('<br>' + s);
}
function PhoneNumber(data) {
var self = this;
self.phoneType = ko.observable();
self.phoneNumber = ko.observable();
self.phoneNumber.subscribe(function(newValue) {
debug('newvalue: ' + newValue);
});
self.phoneNumber.subscribe(function(previousValue) {
debug(previousValue);
}, self, "beforeChange");
ko.mapping.fromJS(data, PhoneNumber.mapping, self);
}
PhoneNumber.mapping = {};
function Contact(data) {
var self = this;
self.name = ko.observable();
self.email = ko.observable();
self.phones = ko.observableArray();
ko.mapping.fromJS(data, Contact.mapping, self);
}
Contact.mapping = {
phones: {
create: function(options) {
return new PhoneNumber(options.data);
}
}
};
function PhoneBook(data) {
var self = this;
self.contacts = ko.observableArray();
ko.mapping.fromJS(data, PhoneBook.mapping, self);
}
PhoneBook.mapping = {
contacts: {
create: function(options) {
return new Contact(options.data);
}
}
};
var phoneBookData = {
contacts: [{
name: 'John',
email: 'address@domain.com',
phones: [{
phoneType: 'Home Phone',
phoneNumber: '999-888-777'
}, {
phoneType: 'Business Phone',
phoneNumber: '444-888-777'
}]
},
{
name: 'John2',
email: '222address@domain.com',
phones: [{
phoneType: '22Home Phone',
phoneNumber: '22999-888-777'
}, {
phoneType: '22Business Phone',
phoneNumber: '444-888-777'
}]
}
]
};
var phoneBookDataOther = {
contacts: [{
name: 'peter',
email: 'address@domain.com',
phones: [{
phoneType: 'Home Phone',
phoneNumber: '999-888-777'
}, {
phoneType: 'Business Phone',
phoneNumber: '444-888-777'
}]
},
{
name: 'almond',
email: '222address@domain.com',
phones: [{
phoneType: '22Home Phone',
phoneNumber: '22999-888-777'
}, {
phoneType: '22Business Phone',
phoneNumber: '444-888-777'
}]
}
]
};
function dofunc() {
ko.mapping.fromJS(phoneBookDataOther, phoneBook);
}
$(document).ready(function() {
phoneBook = new PhoneBook(phoneBookData);
ko.applyBindings(phoneBook);
setTimeout(dofunc, 5000)
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.5.0/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.min.js"></script>
<ul data-bind="foreach: contacts">
<li>
<div data-bind="text: name"></div>
<div data-bind="text: email"></div>
<ul data-bind="foreach: phones">
<li>
<span data-bind="text: phoneType"></span>:
<span data-bind="text: phoneNumber"></span>
</li>
</ul>
</li>
</ul>
<div>
<p id="log"></p>
</div>
当再次调用映射插件时(在5秒超时后),将发生change事件,但是previousValue
始终以undefined
的形式出现.
我做错了什么?
这里也是jsfiddle: https://jsfiddle.net/icinema/ungbz27s/1 /
这里的问题是您使用的映射插件错误,并且您的测试数据没有意义.
当您向完全相同的可观察值写入新值时,将永远只有一个上一个"值.但是,当您映射一组完全不同的数据时,映射插件将丢弃所有视图模型,并创建新的视图模型.
应该如何知道在第一轮中名称为"John"的对象应该是在第二轮中名称为"peter"的同一个人?不可以因此,它会将所有联系人(包括他们的所有电话号码)排除掉并创建新联系人.在这种情况下,永远不会有上一个"值.
您需要的是
- 给联系人和电话号码一个键,这样在呼叫
ko.mapping.fromJS
时,它们可以被识别为同一对象. - 通过在映射配置中添加
key
函数,告诉映射插件哪个对象的属性应该是关键.
阅读映射插件的文档-阅读整本书,不是一开始就是这么多.
在下面的示例中,我将name
用作联系人的键,将phoneType
用作电话的键,并且修改了测试数据,以使它们在两组电话簿中具有相同的名称和电话类型.您可能想使用联系人ID号作为键,而不是姓名.
使用key
函数的优点是敲除将仅更新DOM中的电话号码文本,而不是丢弃并重新创建整个<li>
及其中的所有内容,因为它可以识别现有的viewmodel实例和保留它们.这样可以减少渲染时间.
/* global ko, $ */
function debug(s) {
$("#log").append('<br>' + s);
}
function PhoneNumber(data) {
var self = this;
self.phoneType = ko.observable();
self.phoneNumber = ko.observable();
self.phoneNumber.subscribe(function(newValue) {
debug('new value: ' + newValue);
});
self.phoneNumber.subscribe(function(previousValue) {
debug('previous value: ' + previousValue);
}, self, "beforeChange");
ko.mapping.fromJS(data, PhoneNumber.mapping, self);
}
PhoneNumber.mapping = {};
function Contact(data) {
var self = this;
self.name = ko.observable();
self.email = ko.observable();
self.phones = ko.observableArray();
ko.mapping.fromJS(data, Contact.mapping, self);
}
Contact.mapping = {
phones: {
create: function(options) {
return new PhoneNumber(options.data);
},
key: function (data) {
return ko.unwrap(data.phoneType);
}
}
};
function PhoneBook(data) {
var self = this;
self.contacts = ko.observableArray();
ko.mapping.fromJS(data, PhoneBook.mapping, self);
}
PhoneBook.mapping = {
contacts: {
create: function(options) {
return new Contact(options.data);
},
key: function (data) {
return ko.unwrap(data.name);
}
}
};
var phoneBookData = {
contacts: [{
name: 'John',
email: 'john@domain.com',
phones: [{
phoneType: 'Home Phone',
phoneNumber: '999-888-777-old'
}, {
phoneType: 'Business Phone',
phoneNumber: '444-888-777-old'
}]
},
{
name: 'Peter',
email: 'peter@domain.com',
phones: [{
phoneType: 'Home Phone',
phoneNumber: '22999-888-777-old'
}, {
phoneType: 'Business Phone',
phoneNumber: '444-888-777-old'
}]
}
]
};
var phoneBookDataOther = {
contacts: [{
name: 'John',
email: 'john@domain.com',
phones: [{
phoneType: 'Home Phone',
phoneNumber: '999-888-777-new'
}, {
phoneType: 'Business Phone',
phoneNumber: '444-888-777-new'
}]
},
{
name: 'Peter',
email: 'peter@domain.com',
phones: [{
phoneType: 'Home Phone',
phoneNumber: '22999-888-777-new'
}, {
phoneType: 'Business Phone',
phoneNumber: '444-888-777-new'
}]
}
]
};
$(document).ready(function() {
var phoneBook = new PhoneBook(phoneBookData);
ko.applyBindings(phoneBook);
debug('<hr>');
setTimeout(function dofunc() {
ko.mapping.fromJS(phoneBookDataOther, phoneBook);
}, 3000);
});
#log { font-family: monospace; font-size: small; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.5.0/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.min.js"></script>
<ul data-bind="foreach: contacts">
<li>
<div data-bind="text: name"></div>
<div data-bind="text: email"></div>
<ul data-bind="foreach: phones">
<li>
<span data-bind="text: phoneType"></span>:
<span data-bind="text: phoneNumber"></span>
</li>
</ul>
</li>
</ul>
<hr>
<div id="log"></div>
Based on the answer of this question, I try to get the value before change in an observable with the following code.
var phoneBook;
function debug(s) {
$("#log").append('<br>' + s);
}
function PhoneNumber(data) {
var self = this;
self.phoneType = ko.observable();
self.phoneNumber = ko.observable();
self.phoneNumber.subscribe(function(newValue) {
debug('newvalue: ' + newValue);
});
self.phoneNumber.subscribe(function(previousValue) {
debug(previousValue);
}, self, "beforeChange");
ko.mapping.fromJS(data, PhoneNumber.mapping, self);
}
PhoneNumber.mapping = {};
function Contact(data) {
var self = this;
self.name = ko.observable();
self.email = ko.observable();
self.phones = ko.observableArray();
ko.mapping.fromJS(data, Contact.mapping, self);
}
Contact.mapping = {
phones: {
create: function(options) {
return new PhoneNumber(options.data);
}
}
};
function PhoneBook(data) {
var self = this;
self.contacts = ko.observableArray();
ko.mapping.fromJS(data, PhoneBook.mapping, self);
}
PhoneBook.mapping = {
contacts: {
create: function(options) {
return new Contact(options.data);
}
}
};
var phoneBookData = {
contacts: [{
name: 'John',
email: 'address@domain.com',
phones: [{
phoneType: 'Home Phone',
phoneNumber: '999-888-777'
}, {
phoneType: 'Business Phone',
phoneNumber: '444-888-777'
}]
},
{
name: 'John2',
email: '222address@domain.com',
phones: [{
phoneType: '22Home Phone',
phoneNumber: '22999-888-777'
}, {
phoneType: '22Business Phone',
phoneNumber: '444-888-777'
}]
}
]
};
var phoneBookDataOther = {
contacts: [{
name: 'peter',
email: 'address@domain.com',
phones: [{
phoneType: 'Home Phone',
phoneNumber: '999-888-777'
}, {
phoneType: 'Business Phone',
phoneNumber: '444-888-777'
}]
},
{
name: 'almond',
email: '222address@domain.com',
phones: [{
phoneType: '22Home Phone',
phoneNumber: '22999-888-777'
}, {
phoneType: '22Business Phone',
phoneNumber: '444-888-777'
}]
}
]
};
function dofunc() {
ko.mapping.fromJS(phoneBookDataOther, phoneBook);
}
$(document).ready(function() {
phoneBook = new PhoneBook(phoneBookData);
ko.applyBindings(phoneBook);
setTimeout(dofunc, 5000)
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.5.0/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.min.js"></script>
<ul data-bind="foreach: contacts">
<li>
<div data-bind="text: name"></div>
<div data-bind="text: email"></div>
<ul data-bind="foreach: phones">
<li>
<span data-bind="text: phoneType"></span>:
<span data-bind="text: phoneNumber"></span>
</li>
</ul>
</li>
</ul>
<div>
<p id="log"></p>
</div>
The change event happens when the mapping plugin is called a again time (here after a timeout of 5 seconds), but the previousValue
always comes out as undefined
.
What i am doing wrong?
Here is the jsfiddle, too: https://jsfiddle.net/icinema/ungbz27s/1/
The problem here is that you are using the mapping plugin wrong, and that your test data makes no sense.
There will only ever be a "previous" value, when you write a new value to the exact same observable. But the mapping plugin will throw away all your viewmodels and make new ones when you map a completely different set of data.
How is it supposed to know that the object with the name "John" in the first round is supposed to be the same person that has the name "peter" in the second round? It can't. So it throws out all the contacts including all their phone numbers and makes new ones. There never is a "previous" value in this scenario.
What you need is
- Give the contacts and phone numbers a key, so they can be identified as the same object across calls to
ko.mapping.fromJS
. - Tell the mapping plugin which of the object's properties is supposed to be the key, by adding a
key
function to the mapping configuration.
Read the documentation of the mapping plugin - read the entire thing, it's not that much to begin with.
In the below example I used name
as the key for contacts and phoneType
as the key for phones, and I amended the test data so that they have the same names and phone types across both sets. You probably want to use a contact ID number as the key instead of the name.
The advantage of using the key
function is that knockout will only update the phone number text in the DOM, instead of throwing out and recreating the whole <li>
and everything in it, because it can recognize existing viewmodel instances and keep them. This will cut down on rendering time.
/* global ko, $ */
function debug(s) {
$("#log").append('<br>' + s);
}
function PhoneNumber(data) {
var self = this;
self.phoneType = ko.observable();
self.phoneNumber = ko.observable();
self.phoneNumber.subscribe(function(newValue) {
debug('new value: ' + newValue);
});
self.phoneNumber.subscribe(function(previousValue) {
debug('previous value: ' + previousValue);
}, self, "beforeChange");
ko.mapping.fromJS(data, PhoneNumber.mapping, self);
}
PhoneNumber.mapping = {};
function Contact(data) {
var self = this;
self.name = ko.observable();
self.email = ko.observable();
self.phones = ko.observableArray();
ko.mapping.fromJS(data, Contact.mapping, self);
}
Contact.mapping = {
phones: {
create: function(options) {
return new PhoneNumber(options.data);
},
key: function (data) {
return ko.unwrap(data.phoneType);
}
}
};
function PhoneBook(data) {
var self = this;
self.contacts = ko.observableArray();
ko.mapping.fromJS(data, PhoneBook.mapping, self);
}
PhoneBook.mapping = {
contacts: {
create: function(options) {
return new Contact(options.data);
},
key: function (data) {
return ko.unwrap(data.name);
}
}
};
var phoneBookData = {
contacts: [{
name: 'John',
email: 'john@domain.com',
phones: [{
phoneType: 'Home Phone',
phoneNumber: '999-888-777-old'
}, {
phoneType: 'Business Phone',
phoneNumber: '444-888-777-old'
}]
},
{
name: 'Peter',
email: 'peter@domain.com',
phones: [{
phoneType: 'Home Phone',
phoneNumber: '22999-888-777-old'
}, {
phoneType: 'Business Phone',
phoneNumber: '444-888-777-old'
}]
}
]
};
var phoneBookDataOther = {
contacts: [{
name: 'John',
email: 'john@domain.com',
phones: [{
phoneType: 'Home Phone',
phoneNumber: '999-888-777-new'
}, {
phoneType: 'Business Phone',
phoneNumber: '444-888-777-new'
}]
},
{
name: 'Peter',
email: 'peter@domain.com',
phones: [{
phoneType: 'Home Phone',
phoneNumber: '22999-888-777-new'
}, {
phoneType: 'Business Phone',
phoneNumber: '444-888-777-new'
}]
}
]
};
$(document).ready(function() {
var phoneBook = new PhoneBook(phoneBookData);
ko.applyBindings(phoneBook);
debug('<hr>');
setTimeout(function dofunc() {
ko.mapping.fromJS(phoneBookDataOther, phoneBook);
}, 3000);
});
#log { font-family: monospace; font-size: small; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.5.0/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.min.js"></script>
<ul data-bind="foreach: contacts">
<li>
<div data-bind="text: name"></div>
<div data-bind="text: email"></div>
<ul data-bind="foreach: phones">
<li>
<span data-bind="text: phoneType"></span>:
<span data-bind="text: phoneNumber"></span>
</li>
</ul>
</li>
</ul>
<hr>
<div id="log"></div>
这篇关于使用映射插件时,在Knockout中未定义beforeChange值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!