Почему предоставление наблюдаемого с помощью сопоставленного объекта работает с вводом, а не с выбором? - PullRequest
0 голосов
/ 07 мая 2018

Рассмотрим следующие два примера отображения объекта из JSON и присвоения его наблюдаемой:

Первый пример позволяет пользователю изменять входное значение, сохранять текущее значение и загружать сохраненные данные в любое время:

var personAsJson = "";
var handlerVM = function () {
  var self = this;
  self.person = ko.observable();
  self.init = function () {
    self.person(new personVM("john", 1));
  }
  self.changeName = function () {
    var currentName = "";
    var vowels = "aieou";
    var consonants = "bcdfghjklmnpqrstvwxyz";
    for (var i = 0; i < 2; i++) {
      currentName += vowels.charAt(Math.floor(Math.random() * vowels.length));
      currentName += consonants.charAt(Math.floor(Math.random() * consonants.length));
    }
    self.person().name(currentName);
  }
  self.save = function () {
    personAsJson = ko.toJSON(self.person);
    console.log(personAsJson);
  }
  self.load = function () {
    loadedPerson = ko.mapping.fromJSON(personAsJson);
    self.person(loadedPerson);
  }
  self.log = function () {
    console.log(ko.toJSON(self.person));
  }
}
var personVM = function (name, id) {
  var self = this;
  self.name = ko.observable(name);
  self.id = ko.observable(id);
}

var handler = new handlerVM();
handler.init();
ko.applyBindings(handler);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://rawgit.com/SteveSanderson/knockout.mapping/master/build/output/knockout.mapping-latest.js
"></script>
<div data-bind="with: person">
  <input data-bind="value: name" />
</div>
<div>
  <button data-bind="text: 'change name', click: changeName" />
  <button data-bind="text: 'save', click: save" />
  <button data-bind="text: 'load', click: load" />
  <button data-bind="text: 'log current person', click: log" />
</div>

Второй пример делает то же самое - только с выбором. Однако функция загрузки не работает:

var currentPersonAsJson = "";
var handlerVM = function () {
  var self = this;
  self.persons = ko.observableArray([
      new personVM("john", 1),
      new personVM("paul", 2),
      new personVM("viki", 3),		
  ]); 
  self.currentPerson = ko.observable();     
  self.save = function () {
    currentPersonAsJson = ko.toJSON(self.currentPerson);
    console.log(currentPersonAsJson);
  }
  self.load = function () {
    loadedPerson = ko.mapping.fromJSON(currentPersonAsJson);
    self.currentPerson(loadedPerson);
  }
  self.log = function () {
    console.log(ko.toJSON(self.currentPerson));
  }
}
var personVM = function (name, id) {
  var self = this;
  self.name = ko.observable(name);
  self.id = ko.observable(id);
}

var handler = new handlerVM();
ko.applyBindings(handler);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://rawgit.com/SteveSanderson/knockout.mapping/master/build/output/knockout.mapping-latest.js
"></script>
<div>
  <div>
     <select data-bind="options: persons,
                        optionsText: 'name',
                        optionsCaption: 'choose a person',
                        value: currentPerson"
     >
     </select>
  </div>
  <div>
     <button data-bind="text: 'save', click: save" />
     <button data-bind="text: 'load', click: load" />
     <button data-bind="text: 'log current person', click: log" />
  </div>
</div>

Почему второй пример не работает и как его можно исправить?

1 Ответ

0 голосов
/ 07 мая 2018

Когда вы изменяете значение <select> с помощью кода, нокаут ищет новое значение в списке options, чтобы он мог обновить пользовательский интерфейс.

Когда вы устанавливаете currentPerson (который связан с value) на что-либо, что не присутствует в массиве persons (который связан с options), пользовательский интерфейс будет не обновляется.

Когда вы сохраняете человека, вы сериализуетесь в JSON. После реконструкции вы получите новый объект, который нокаутом не может совпасть с ранее связанными параметрами.

Существует два возможных решения:

  1. Используйте привязку optionsValue, чтобы нокаут использовал свойство id для сопоставления лиц, а не проверки экземпляра, или
  2. Выполните ручной поиск ранее созданной модели представления при загрузке человека.

Я реализовал решение 2 в следующем примере.

var currentPersonAsJson = null;

var handlerVM = function () {
  var self = this;
  self.persons = ko.observableArray([
      new PersonVM("john", 1),
      new PersonVM("paul", 2),
      new PersonVM("viki", 3),		
  ]); 
  self.currentPerson = ko.observable();     
  self.save = function () {
    currentPersonAsJson = ko.toJSON(self.currentPerson);
    console.log(currentPersonAsJson);
  }
  self.load = function () {
    var loadedPerson = ko.mapping.fromJSON(currentPersonAsJson);
    // loadedPerson is a new instance, so it won't match anything
    // inside self.persons
    // Let's do a manual lookup:
    
    var matchedVM = self.persons().find(
      p => p.id() === loadedPerson.id
    );
    
    if (matchedVM) {
      loadedPerson = matchedVM;
    }
    // Edge case: We've loaded something that we don't know:
    else {
      self.persons.push(loadedPerson);
    }
    
    self.currentPerson(loadedPerson);
  }
  self.log = function () {
    console.log(ko.toJSON(self.currentPerson));
  }
}
var PersonVM = function (name, id) {
  var self = this;
  self.name = ko.observable(name);
  self.id = ko.observable(id);
}

var handler = new handlerVM();
ko.applyBindings(handler);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://rawgit.com/SteveSanderson/knockout.mapping/master/build/output/knockout.mapping-latest.js
"></script>
<div>
  <div>
     <select data-bind="options: persons,
                        optionsText: 'name',
                        optionsCaption: 'choose a person',
                        value: currentPerson"
     >
     </select>
  </div>
  <div>
     <button data-bind="text: 'save', click: save" />
     <button data-bind="text: 'load', click: load" />
     <button data-bind="text: 'log current person', click: log" />
  </div>
</div>

Редактирование бонуса: решение 1 :

var currentPersonAsJson = null;

var handlerVM = function () {
  var self = this;
  self.persons = ko.observableArray([
      new PersonVM("john", 1),
      new PersonVM("paul", 2),
      new PersonVM("viki", 3),		
  ]); 
  self.currentPerson = ko.observable();     
  self.save = function () {
    currentPersonAsJson = ko.toJSON(self.currentPerson);
    console.log(currentPersonAsJson);
  }
  self.load = function () {
    self.currentPerson(currentPersonAsJson);
  }
  self.log = function () {
    console.log(ko.toJSON(self.currentPerson));
  }
}
var PersonVM = function (name, id) {
  var self = this;
  self.name = ko.observable(name);
  self.id = ko.observable(id);
}

var handler = new handlerVM();
ko.applyBindings(handler);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://rawgit.com/SteveSanderson/knockout.mapping/master/build/output/knockout.mapping-latest.js
"></script>
<div>
  <div>
     <select data-bind="options: persons,
                        optionsText: 'name',
                        optionsCaption: 'choose a person',
                        value: currentPerson,
                        optionsValue: 'id'"
     >
     </select>
  </div>
  <div>
     <button data-bind="text: 'save', click: save" />
     <button data-bind="text: 'load', click: load" />
     <button data-bind="text: 'log current person', click: log" />
  </div>
</div>
...