使用映射插件时,在Knockout中未定义beforeChange值 [英] beforeChange value is undefined in Knockout when using the mapping plugin

查看:106
本文介绍了使用映射插件时,在Knockout中未定义beforeChange值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

基于此问题的答案,我尝试通过以下代码在可观察到的值中获取值. /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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆