0

我有一张只读电话号码表。电话号码有一个号码和一个类型(例如移动电话或家庭电话)。每个电话号码都有一个“编辑”按钮,单击该按钮后,您可以编辑电话号码并在模式对话框中键入。我将 Knockoutjs 用于只读表和编辑器。该表绑定到一个 observableArrayPhoneVMs并且编辑器在单个PhoneVM. 因为我希望用户在应用任何更改之前必须在模态上单击“确定”,所以模态在所选副本上工作PhoneVM,当他们单击“确定”时,它会替换PhoneVM表绑定到的 observableArray 中最初单击的. 这一切都很好。

现在我需要允许在与只读表相同的页面上编辑列表中的第一个电话(没有模式)。这个想法是为了在工作流程的早期更容易输入第一部电话。因此,您将在页面上输入您的手机,它会自动出现在下面的只读列表中,您也可以在模态中正常编辑它。我以为 Knockout 会让这件事变得简单,但我遇到了障碍。从这一点来看,只显示一个错误的例子会更容易。在这个小提琴中执行以下操作:https ://jsfiddle.net/ph4mhsof/

  1. 从文本框中编辑电话号码和标签。请注意所有电话列表中的第一部电话也会更新。
  2. 在下拉列表中更改电话类型。请注意,所有电话表中的类型 ID 和类型名称都发生了相应的变化。
  3. 单击删除第一部电话。第二部手机成为新的第一部手机。
  4. 从文本框中编辑电话号码和标签。请注意所有电话列表中的第一部电话按预期更新。
  5. 在下拉列表中更改电话类型。请注意所有电话列表中的类型 ID 更新。类型名称不会更新。

我正在使用自定义绑定将类型名称绑定到选择的文本。似乎该绑定的 init 函数中的 valueAccessor 必须专门指向原始的第一个 PhoneVM 的PhoneTypeName属性,但我需要它做的是指向firstPhone计算属性的PhoneTypeName. 有没有什么办法解决这一问题?

原始jsfiddle的副本:

function phoneListVM() {
  var _self = this;
  this.phones = ko.observableArray([
    new phoneVM(1, "Mobile", "123-234-3456"),
    new phoneVM(2, "Home", "654-343-3211")
  ]);

  this.firstPhone = ko.computed(function() {
    return _self.phones()[0];
  });
}

function phoneVM(typeID, typeName, Number) {
  this.PhoneTypeID = ko.observable(typeID);
  this.PhoneTypeName = ko.observable(typeName);
  this.PhoneNumber1 = ko.observable(Number);
}

ko.bindingHandlers.selectedTextValue = {
  init: function(element, valueAccessor) {
    var value = valueAccessor();

    $(element).change(function() {
      value($("option:selected", this).text());
    });
  },
  update: function(element, valueAccessor) {}
};

$(document).ready(function() {
  var phoneList = new phoneListVM()
  ko.applyBindings(phoneList);
  $("button").click(function() {
    phoneList.phones.shift();
  });
});
.editor{
  background-color:rgba(200,200,250, 0.2);
  border: 1px solid rgba(0,0,0, 0.2);
  padding: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<h4>
Edit First Phone
</h4>
<div class="editor">
  <p>Phone Number:
    <input data-bind="value: firstPhone().PhoneNumber1" />
  </p>
  <p>Phone Type:
    <select data-bind="value:firstPhone().PhoneTypeID, selectedTextValue:firstPhone().PhoneTypeName">
      <option value="0"></option>
      <option value="1">Mobile</option>
      <option value="2">Home</option>
    </select>
  </p>
</div>

<h4>
All Phones
</h4>
<table>
  <thead>
    <tr>
      <th>Type ID</th>
      <th>Type Name</th>
      <th>Number</th>
    </tr>
  </thead>
  <tbody data-bind="foreach:phones">
    <tr>
      <td><span data-bind="text:PhoneTypeID"></span></td>
      <td><span data-bind="text:PhoneTypeName"></span></td>
      <td><span data-bind="text:PhoneNumber1"></span></td>
    </tr>
  </tbody>
</table>
<button type="button">
  Remove First Phone
</button>

4

3 回答 3

0

我同意 customBinding 过度工作的其他两个答案。但是正如您所说,您不能轻易更改代码,我将向您展示您的问题。

在您的自定义绑定声明中,您刚刚定义了init函数但将update函数留空。那就是问题所在。当您在选择框上进行更改时,change会触发事件但没有事件处理程序,因此没有发生任何事情。

事实上,您的自定义绑定已change成功将事件处理程序添加到选择框。这就是为什么在删除第一个电话号码之前,一切正常。但是当您删除第一个电话号码时,事件处理程序将被删除,因为您只能将一个事件处理程序添加到事件的选择框change

解决方案是:将您的init函数留空,并将所有当前init函数内容移动到update函数中,如下所示。

ko.bindingHandlers.selectedTextValue = {
  init: function(element, valueAccessor) {

  },
  update: function(element, valueAccessor) {
    var value = valueAccessor();

    $(element).change(function() {
      value($("option:selected", this).text());
    });
  }
};
于 2017-10-18T01:36:11.397 回答
0

在我看来,自定义绑定是矫枉过正的。我已经用一些方法和一个专用的选择的 observable 更新了你的代码,所以你总是知道选择了哪部手机。

让我知道这是否是你要找的。

function phoneListVM() {
  var _self = this;
  this.phones = ko.observableArray([
  	// Removed typeName
    new phoneVM(1, "123-234-3456"),
    new phoneVM(2, "654-343-3211")
  ]);
  
  // Observable to see which phone is currently selected
  this.SelectedPhone = ko.observable(_self.phones().length > 0 ? _self.phones()[0] : '');
  
  // Allow editing whichever phone they want
  this.EditPhone = function(obj) {
    _self.SelectedPhone(obj);
  };
  
  // Remove first phone and check if there are any more phones, if so add it to the selected phone
  this.RemoveFirstPhone = function() {
    var firstPhone = _self.phones()[0];
    if(firstPhone) {
        _self.phones.remove(firstPhone);
        _self.SelectedPhone(_self.phones().length > 0 ? _self.phones()[0] : '');
    }
  }
}

// Removed typeName and made it computed. Could be replaced with some lodash _.find if you are storing an array of types in the global space
function phoneVM(typeID, Number) {
  var self = this;
  this.PhoneTypeID = ko.observable(typeID);
  this.PhoneNumber1 = ko.observable(Number);
  this.PhoneTypeName = ko.computed(function() {
    switch (self.PhoneTypeID().toString()) {
      case '1':
        return 'Mobile';
        break;
      case '2':
        return 'Home';
        break;
    }
  });
}
$(document).ready(function() {
  var phoneList = new phoneListVM()
  ko.applyBindings(phoneList);
});
.editor {
  background-color: rgba(255, 255, 255, 0.7);
  border: 1px solid rgba(0, 0, 0, 0.2);
  padding: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<h4>
Edit First Phone
</h4>
<div class="editor" data-bind="with: SelectedPhone, visible: SelectedPhone()">
  <p>Phone Number:
    <input data-bind="value: PhoneNumber1" />
  </p>
  <p>Phone Type:
    <select data-bind="value:PhoneTypeID">
      <option value="0"></option>
      <option value="1">Mobile</option>
      <option value="2">Home</option>
    </select>
  </p>
</div>

<h4>
All Phones
</h4>
<table>
  <thead>
    <tr>
      <th>Type ID</th>
      <th>Type Name</th>
      <th>Number</th>
    </tr>
  </thead>
  <tbody data-bind="foreach:phones">
    <tr>
      <td><span data-bind="text:PhoneTypeID"></span></td>
      <td><span data-bind="text:PhoneTypeName()"></span></td>
      <td><span data-bind="text:PhoneNumber1"></span></td>
      <td>
        <button data-bind="click: $root.EditPhone">
          Edit
        </button>
      </td>
    </tr>
  </tbody>
</table>
<button type="button" data-bind="click: RemoveFirstPhone, visible: phones().length > 0">
  Remove First Phone
</button>

于 2017-10-17T21:13:06.853 回答
0

你可能想多了。无需创建自定义活页夹。您可以使用淘汰赛的选项绑定

// create an array
var phoneTypes = [{
  text: "Mobile",
  value: 1
}, {
  text: "Home",
  value: 2
}];

function phoneListVM() {
  var _self = this;
  // this will be bound to the dropdown
  _self.phoneTypes = ko.observableArray(phoneTypes)
  this.phones = ko.observableArray([
    new phoneVM(1, "Mobile", "123-234-3456"),
    new phoneVM(2, "Home", "654-343-3211")
  ]);

  this.firstPhone = ko.computed(function() {
    return _self.phones()[0];
  });
}

function phoneVM(typeID, typeName, Number) {
  var self = this;

  this.PhoneTypeID = ko.observable(typeID);
  this.PhoneNumber1 = ko.observable(Number);

  // get the value from the phonetypes array using the PhoneTypeID
  self.PhoneTypeName = ko.computed(function() {
    var type = phoneTypes.filter(function(a) {
      return a.value === self.PhoneTypeID()
    });
    return type.length > 0 ? type[0].text : undefined;
  })
}

并将 HTML 更改为:

<select data-bind="options: phoneTypes,
                   optionsText: 'text',
                   optionsValue: 'value',
                   optionsCaption: 'Choose',
                   value: firstPhone().PhoneTypeID">
</select>

您可以将复杂对象作为 中的选定值knockout。因此,您可以拥有一个PhoneType属性phoneVM并将 2 个属性绑定PhoneTypetext.

这是一个更新的小提琴


我不太了解为什么您只允许在第一个选项上进行编辑,或者用户将如何编辑第二个选项。但是,你可以看看这个关于如何使每个项目在项目列表中可编辑的小提琴。


评论后更新:

即使select不是由淘汰赛的绑定创建的,您仍然不需要自定义绑定。您可以像我之前建议的那样制作PhoneTypeNameacomputed属性,并textoptions基于PhoneTypeID.

function phoneVM(typeID, typeName, Number) {
  var self = this;
  this.PhoneTypeID = ko.observable(typeID);
  this.PhoneNumber1 = ko.observable(Number);

  self.PhoneTypeName = ko.computed(function() {
    var type = $('#select option[value='+self.PhoneTypeID() +']');
    return type.length > 0 ? type.text() : undefined;
  });
}

这是一个更新的小提琴

您的更改事件没有被触发的原因可能是因为element您添加的事件现在已从 DOM 中删除。

于 2017-10-17T21:14:15.870 回答