首页 > 解决方案 > 表格上的下拉菜单,允许每行仅选择一次项目

问题描述

我是 Knockout 的新手,我已经成功地从我的数据源创建了一个记录表。我需要在表中的每一行添加一个包含两个选项“主要”和“次要”的下拉列表。我需要从表中的其他项目中删除选定的选项。例如,如果表中的第一行选择了“主要”,我需要不允许再次为其他行选择它。我的行数未知。

标签: knockout.js

解决方案


要实现此功能,您的各个行需要有权访问其他行以检查正在使用的值。在我们实现这个特性之前,让我们先编写一个简单的例子,这样我们就知道我们正在使用什么。

初始点

如果您运行下面的代码片段,您将看到没有您描述的功能的一般 UI。

在 Stackoverflow 上提出问题时,您通常会自己包含这些示例,因此人们有一个很好的起点来帮助您解决问题!但既然你是一个新的贡献者,这个是我的。

function Row(id) {
  this.name = id;
  this.selectedSource = ko.observable(null);
  this.sourceOptions = [ "Primary", "Secondary" ]
};


function App() {
  this.rows = ko.observableArray([]);
  
  let lastRowId = 0;
  this.addRow = () => {
    this.rows.push(
      new Row(lastRowId++)
    );
  }
};

const app = new App();
ko.applyBindings(app);

app.addRow();
app.addRow();
app.addRow();
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

<table data-bind="foreach: rows">
  <tr>
    <td data-bind="text: name"></td>
    <td>
      <select data-bind="value: selectedSource,
                         options: sourceOptions,
                         enable: sourceOptions.length,
                         optionsCaption: 'select source'">
                         </select>
    </td>
  </tr>
</table>
<button data-bind="click: addRow">add row</button>

添加新功能

上面的例子假设一个静态列表sourceOptions。然而,实际上,它是一个计算列表:

  • 从所有可能的值开始
  • 删除其他行选择的所有值

要实现此功能,我们首先需要访问其他行:

/* Row accepts a reference to its siblings (including itself) */
function Row(is, allRows) { /* ... */}

/* App passes a reference to all rows when constructing new ones */
new Row(0, this.rows);

现在我们已经可以访问其他行,我们可以检查它们的选择并将它们从可用选项列表中删除:

// Remove this row from the list
const otherRows = ko.pureComputed(() =>  
  allRows().filter(row => row !== this)
);

// Create a Set of all selections
const otherSelections = ko.pureComputed(() =>
  new Set(otherRows().map(row => row.selectedSource()))
);

// Take the Base list and remove any element that is in otherSelections
this.sourceOptions = ko.pureComputed(() =>
  [ "Primary", "Secondary" ]
    .filter(s => !otherSelections().has(s))    
);

查看下面的可运行片段以查看它的实际效果。我还添加了一个enable绑定来指示何时没有选项可供选择!

如果事情仍然不清楚,请发表评论。我很乐意提供帮助。

function Row(id, allRows) {
  this.name = id;
  this.selectedSource = ko.observable(null);
  
  const otherRows = ko.pureComputed(() => 
    allRows().filter(row => row !== this)
  );
  
  const otherSelections = ko.pureComputed(() =>
    new Set(otherRows().map(row => row.selectedSource()))
  );
  
  this.sourceOptions = ko.pureComputed(() =>
    [ "Primary", "Secondary" ]
      .filter(s => !otherSelections().has(s))    
  );
};


function App() {
  this.rows = ko.observableArray([]);
  
  let lastRowId = 0;
  this.addRow = () => {
    this.rows.push(
      new Row(lastRowId++, this.rows)
    );
  }
};

const app = new App();
ko.applyBindings(app);

app.addRow();
app.addRow();
app.addRow();
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

<table data-bind="foreach: rows">
  <tr>
    <td data-bind="text: name"></td>
    <td>
      <select data-bind="value: selectedSource,
                         options: sourceOptions,
                         enable: sourceOptions().length,
                         optionsCaption: 'select source'">
                         </select>
    </td>
  </tr>
</table>
<button data-bind="click: addRow">add row</button>


推荐阅读