Reputation: 3
I am new to Knockout and I am have successfully created a table of records from my data source. I need to add a dropdown with two choices "Primary" and "Secondary" to each row in the table. I need to remove the selected option from the other items in the table. For example if the first row in the table has selected "Primary" I need to not allow it to be selected again for the other rows. I have an unknown number of rows.
Upvotes: 0
Views: 47
Reputation: 23372
To implement this feature, your individual rows need to have access to the other rows to check for values that are in use. Before we implement this feature, let's code a naive example first, so we know what we're working with.
If you run the snippet below, you'll see the general UI without the feature you describe.
When asking a question on Stackoverflow, you usually include these kinds of examples yourself, so people have a great starting point to help you solve your problems! But since you're a new contributor, this one is on me 😉.
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>
The example above assumes a static list of sourceOptions
. In reality however, it's a computed list:
To implement this feature, we'll first need access to the other rows:
/* 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);
Now that we've got access to the other rows, we can check their selections and remove them from the list of available options:
// 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))
);
Check out the runnable snippet below to see it in action. I've also added an enable
binding to indicate when no options are left to select!
Leave a comment if things are still unclear. I'm happy to help.
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>
Upvotes: 1