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

时间:2017-08-24 09:20:02

标签: knockout.js knockout-mapping-plugin

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

我无法使用foreach绑定来解决这个问题,所以我尝试了this 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>

1 个答案:

答案 0 :(得分:0)

可以使用foreach绑定来获得保证的5行,你只需要一个计算属性来确保5的确切长度。

例如:

  • 定义固定行数(NR_OF_ROWS
  • 创建pureComputed并使用foreach
  • 绑定到该pureComputed
  • 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>
public class LinearLayoutManagerWithSmoothScroller extends LinearLayoutManager {

    public LinearLayoutManagerWithSmoothScroller(Context context) {
        super(context, HORIZONTAL, false);
    }

    @Override
    public int findFirstVisibleItemPosition() {
        return super.findFirstVisibleItemPosition();
    }

    @Override
    public int findLastVisibleItemPosition() {
        return super.findLastVisibleItemPosition();
    }

    public LinearLayoutManagerWithSmoothScroller(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
    }

    @Override
    public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
                                       int position) {
        RecyclerView.SmoothScroller smoothScroller = new TopSnappedSmoothScroller(recyclerView.getContext());
        smoothScroller.setTargetPosition(position);
        startSmoothScroll(smoothScroller);
    }

    private class TopSnappedSmoothScroller extends LinearSmoothScroller {
        public TopSnappedSmoothScroller(Context context) {
            super(context);

        }

        @Override
        public PointF computeScrollVectorForPosition(int targetPosition) {
            return LinearLayoutManagerWithSmoothScroller.this
                    .computeScrollVectorForPosition(targetPosition);
        }

        @Override
        protected int getVerticalSnapPreference() {
            return SNAP_TO_START;
        }
    }
}