如何通过索引将 observableArray 绑定到元素

How to bind observableArray by index to the element

我一直在开发一个 Web 应用程序,它将显示 table 包含候选人列表。我想将候选集合(从服务器获取)显示到 table,但要显示固定的行数,比方说 5 行。因此,如果集合只有 2 个候选人,table 将有 5 行,只有前 2 行包含数据,其余 3 行是空行。

我无法使用 foreach 绑定来解决这个问题,所以我尝试了 this post

因为 post observableArray 中的对象是不可观察的,所以我试图让它们可观察并且它仍然有效。但是当我在我的代码中尝试时,它在我的 js 中抛出错误:

Unable to process binding "text: function (){return Candidates[0]().CandidateNumber }"
Message: AssignedCandidates[0] is not a function

我仍然不确定我错过了什么。请帮忙。

这是我的 js 文件:

var CandidateViewModel = function (data) {
    var self = this;
    self.CandidateId = ko.observable();
    self.CandidateNumber = ko.observable();
    self.Name = ko.observable();
    self.Status = ko.observable()
    ko.mapping.fromJS(data, {}, self);
}

var mappingCandidateList = {
    Candidates: {
        create: function (options) {
            return new CandidateViewModel(options.data);
        }
    }
}

var CandidateListViewModel = function (data) {
    var self = this;
    self.Candidates = ko.observableArray();
    self.AssignedCount = ko.observable();
    self.ProcessingCount = ko.observable();
    self.RejectingCount = ko.observable();
    self.PassedCount = ko.observable();
    self.FailedCount = ko.observable();
    self.PagingInfo = ko.observable();

    var getData = function (param) {
        $.ajax({
            url: api("Candidate/GetCandidates"),
            data: param,
            type: 'GET',
            dataType: 'JSON'
        }).done(function (data) {
            ko.mapping.fromJS(data, mappingCandidateList, self);
        });
    }
}

ko.applyBindings(new CandidateListViewModel (), document.getElementById('candidate-container'));

这是 html

<table>
    <thead>
        <tr>
            <th>number</th>
            <th>name</th>
            <th>status</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td data-bind="text: Candidates()[0].CandidateNumber"></td>
            <td data-bind="text: Candidates()[0].Name"></td>
            <td data-bind="text: Candidates()[0].Status"></td>
        </tr>
    </tbody>
</table>

可以 使用 foreach 绑定来获得保证的 5 行,您只需要一个计算的 属性 来确保5.

例如:

  • 定义固定行数(NR_OF_ROWS)
  • 创建一个 pureComputed 并使用 foreach
  • 绑定到它
  • 使 pureComputed return 成为一个长度为 NR_OF_ROWS
  • 的数组
  • 循环这个数组并为每个有可用索引的索引注入一个candidate
  • 如果没有更多候选项,则注入一个空候选项,告诉您的视图要呈现什么。

var VM = function() {
  const NR_OF_ROWS = 5;

  this.candidates = ko.observableArray([
    { id: 1, firstName: "Jane", lastName: "Doe" },
    { id: 2, firstName: "John", lastName: "Doe" }
  ]);
  
  this.displayRows = ko.pureComputed(function() {
    const emptyCandidate = { id: "-", firstName: "-", lastName: "-", empty: true };
    const candidates = this.candidates();
    const rows = [];
    
    for (let i = 0; i < NR_OF_ROWS; i += 1) {
      rows.push(candidates[i] || emptyCandidate);
    }
    
    return rows;
  }, this);
  
  // For demo
  this.newCandidate = {
    firstName: ko.observable(""),
    lastName: ko.observable(""),
    add: function() {
      this.candidates.push({ 
        firstName: this.newCandidate.firstName(), 
        lastName: this.newCandidate.lastName(), 
        id: this.candidates().length + 1 
      })
    }.bind(this)
  }
}

ko.applyBindings(new VM());
tr:nth-child(even) { background: #efefef; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

<table>
  <thead>
    <tr>
      <th>ID</th>
      <th>First Name</th>
      <th>Last Name</th>
    </tr>
  </thead>
  <tbody data-bind="foreach: displayRows">
    <td data-bind="text: id"></td>
    <td data-bind="text: firstName"></td>
    <td data-bind="text: lastName"></td>
  </tbody>
</table>

<div data-bind="with: newCandidate">
  <input type="text" placeholder="first name" data-bind="textInput: firstName">
  <input type="text" placeholder="last name" data-bind="textInput: lastName">
  <button data-bind="click: add">add</button>
</div>


编辑: 如果您更愿意为空行使用特殊视图,而不是特殊视图模型,您可以使用模板:

var VM = function() {
  const NR_OF_ROWS = 5;

  this.candidates = ko.observableArray([
    { id: 1, firstName: "Jane", lastName: "Doe" },
    { id: 2, firstName: "John", lastName: "Doe" }
  ]);
  
  this.displayRows = ko.pureComputed(function() {
    const candidates = this.candidates();
    const rows = [];
    
    for (let i = 0; i < NR_OF_ROWS; i += 1) {
      rows.push(candidates[i] || emptyCandidate);
    }
    
    return rows;
  }, this);
  
  // For demo
  this.newCandidate = {
    firstName: ko.observable(""),
    lastName: ko.observable(""),
    add: function() {
      this.candidates.push({ 
        firstName: this.newCandidate.firstName(), 
        lastName: this.newCandidate.lastName(), 
        id: this.candidates().length + 1 
      })
    }.bind(this)
  }
}

ko.applyBindings(new VM());
tr:nth-child(even) { background: #efefef; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

<table>
  <thead>
    <tr>
      <th>ID</th>
      <th>First Name</th>
      <th>Last Name</th>
    </tr>
  </thead>
  <tbody data-bind="foreach: displayRows">
    <td data-bind="text: id"></td>
    <td data-bind="text: firstName"></td>
    <td data-bind="text: lastName"></td>
  </tbody>
</table>

<div data-bind="with: newCandidate">
  <input type="text" placeholder="first name" data-bind="textInput: firstName">
  <input type="text" placeholder="last name" data-bind="textInput: lastName">
  <button data-bind="click: add">add</button>
</div>