How can I select all checkboxes in a column using knockout? - javascript

Disclaimer: The environment I'm working in has to be completely inline. The HTML calls to a JS file I am not allowed to edit. That being said, I'm trying to select/deselect all checkboxes in a column when I click the header row. I want to be able to select/deselect any individual row below the header as usual but when I check the header I want EVERY row underneath to select/deselect.
Right now my problem is that selecting the header row only selects or deselects once. So clicking it once unchecks every row but then the functionality stops. For what it's worth the check box doesn't appear in the header row either. That's a different problem though.
The problem resides in the first table row --> label class tag. Any suggestions?
<tbody>
<tr>
<td class="feature-name">All</td>
<!-- ko foreach: $parent.featureHeadings[$index()] -->
<td data-bind="css:{alt: !($index() % 2)}">
<label class="multiple-checkboxes">
<input type="checkbox" data-bind="checked: $parent.dataItems.every(function (acct) {
return !acct.features[$index()].isVisible() ||
acct.features[$index()].value(); }),
click: function (data, index, model, event)
{
var newValue = event.srcElement.checked;
data.forEach(function (acct) {
if (acct.features[index].isVisible())
acct.features[index].value(newValue);
}
);
}.bind($data, $parent.dataItems, $index())" />
</label>
</td>
<!-- /ko -->
</tr>
<!-- ko foreach: dataItems -->
<tr>
<td class="feature-name" data-bind="text: name"></td>
<!-- ko foreach: features -->
<td class="setting" data-bind="highlightOverride: isOverridden(), css: { alt: !($index() % 2) }, highlightHelpHint: isHintActive">
<!-- ko if: type === 'Boolean' -->
<label class="checkbox" data-bind="css: { checked: value }, fadeVisible: isVisible()"><input type="checkbox" data-bind="checked: value" /></label>
<!-- /ko -->
</td>
<!-- /ko -->
</tr>
<!-- /ko -->
</tbody>
Edit: Thank you all for reading. Sorry I could not offer more details to allow you to help me. Here is the final solution.
<label class="multiple-checkboxes" data-bind="css: { checked: $parent.dataItems.every(function (acct) { return !acct.features[$index()].isVisible() || acct.features[$index()].value(); }) }, click: function (data, index, model, event) { var newValue = !event.srcElement.classList.contains('checked'); data.forEach(function (acct) { if (acct.features[index].isVisible()) acct.features[index].value(newValue); }); }.bind($data, $parent.dataItems, $index())">

This works:
<label class="multiple-checkboxes" data-bind="css: { checked: $parent.dataItems.every(function (acct) { return !acct.features[$index()].isVisible() || acct.features[$index()].value(); }) }, click: function (data, index, model, event) { var newValue = !event.srcElement.classList.contains('checked'); data.forEach(function (acct) { if (acct.features[index].isVisible()) acct.features[index].value(newValue); }); }.bind($data, $parent.dataItems, $index())">
I'd like to mark the question as answered but you can see I am new here.

Related

Show/Hide div within a single row in a foreach loop - KnockoutJS

I've tried several ways of doing this with no success. Would love some advice!
Goal: I have a table where each row is an order, but where within that row, if changes need to be made, a div appears underneath (in red). This needs to show/hide when a button on that row is clicked/toggled (Button is: Make Changes)
Issue: I have all the buttons working apart from the make changes toggle. Tried the visible observable, but the closest I could get was toggling the div's visibility for the whole table, not per row.
//Class to represent a row in the table
function orderDetail(order, orderChange) {
var self = this;
self.order = ko.observable(order);
self.orderChange = ko.observable(orderChange);
}
//Overall viewmodel, plus initial state
function FoodViewModel() {
var self = this;
self.foodTypes = [
{ foodType: "Please Select"},
{ foodType: "Veg"},
{ foodType: "Meat"}
];
self.orders = ko.observableArray([
new orderDetail(self.foodTypes[0], self.foodTypes[0])
]);
// Add and remove rows
self.addOrder = function() {
self.orders.push(new orderDetail(self.foodTypes[0], self.foodTypes[0]));
}
self.removeOrder = function(order) { self.orders.remove(order) }
}
ko.applyBindings(new FoodViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<table>
<thead>
<tr>
<th>Orders</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: orders">
<tr>
<td>
<div><select data-bind="options: $root.foodTypes, value: order, optionsText: 'foodType'" id="foodList"></select></div>
<div><select data-bind="options: $root.foodTypes, optionsText: 'foodType', value: orderChange" id="foodListChange" style="color: red;"></select></div>
</td>
<td>
<button class="button button2" >Make Changes</button>
</td>
<td>
<button class="button button1" href="#" data-bind="click: $root.removeOrder">Remove</button>
</td>
</tr>
</tbody>
</table>
<button data-bind="click: addOrder" class="button">Add Order</button>
Thanks in advance!
If you want that the user interface reacts to something in Knockout, make an observable.
In this case you want to display part of the UI conditionally (apparently to toggle an edit mode), so let's create:
an observable editMode that is either true or false, to store the UI state
a function toggleEditMode that toggles between the two states, to bind it to the button
an if: editMode and an ifnot: editMode binding, to show different parts of the UI accordingly
function OrderDetail(params) {
var self = this;
params = params || {};
self.order = ko.observable(params.order);
self.orderChange = ko.observable(params.orderChange);
self.editMode = ko.observable(true);
self.buttonCaption = ko.pureComputed(function () {
return self.editMode() ? "Done" : "Edit";
});
self.toggleEditMode = function () {
self.editMode(!self.editMode());
}
}
function OrderList(params) {
var self = this;
params = params || {};
self.foodTypes = ko.observableArray(params.foodTypes);
self.orders = ko.observableArray();
self.addOrder = function(foodType) {
self.orders.push(new OrderDetail());
}
self.removeOrder = function(order) {
self.orders.remove(order);
}
}
var vm = new OrderList({
foodTypes: [
{foodType: "Veg"},
{foodType: "Meat"}
]
});
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<table>
<thead>
<tr>
<th style="width: 150px;">Orders</th>
<th>Actions</th>
</tr>
</thead>
<tbody data-bind="foreach: orders">
<tr>
<td>
<div data-bind="ifnot: editMode">
<!-- ko with: order -->
<span data-bind="text: foodType"></span>
<!-- /ko -->
</div>
<div data-bind="if: editMode">
<select data-bind="
options: $root.foodTypes,
value: order,
optionsText: 'foodType',
optionsCaption: 'Please select…'
"></select>
</div>
</td>
<td>
<button class="button button2" data-bind="
click: toggleEditMode,
text: buttonCaption,
enable: order
"></button>
<button class="button button1" href="#" data-bind="
click: $root.removeOrder
">Remove</button>
</td>
</tr>
</tbody>
</table>
<button data-bind="click: addOrder" class="button">Add Order</button>
<hr>
<pre data-bind="text: ko.toJSON($root, null, 2)"></pre>
Notes
Don't make "Please Select" part of your food types. That's what the optionsCaption binding is for.
I've parameterized the viewmodels (see the params object). This will work better than hard-coding values or using long argument lists, especially if you want to use a mapping plugin later.
The "Done" button is disabled as long as no order is selected, via the enable: order binding, i.e. if the order property is empty, the enable binding will keep the button disabled.
The with: order binding serves a similar purpose. It will only display its contents when there actually is an order value to display. This will prevent rendering errors with incomplete OrderDetail instances.

Make one element visible depending on the value of another in knockout

I have three elements in my HTML, the Question, The Score, The Comment. The Score is an HTML SELECT, with "Poor", "Good" and "Excellent" as it's options.
I only want the Comment field to be visbile if the Score is not = "Good".
<!-- ko foreach: questions -->
<tr>
<td data-bind="text: question"></td>
<td><select data-bind="options: availableScores"></select></td>
<td>
<!-- ko if: availableScores() != 'Good' -->
<input data-bind="textInput: comment" />
<!-- /ko -->
</td>
</tr>
<!-- /ko -->
Any advice appreciated, thanks.
I assume that the comment textbox must only be visible if the selected score differs from the value 'Good'.
For doing so, the selected value must be tracked and bound to the listbox,
here below via the property selectedScore.
A minimal MCVE shows this behaviour.
var Question = function() {
_self = this;
_self.question = "";
_self.comment = ko.observable("");
_self.availableScores = ["Good", "Poor", "Excellent"];
_self.selectedScore = ko.observable("Good");
}
var vm = new Question();
ko.applyBindings(vm);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<select data-bind="options: availableScores, value: selectedScore"></select>
<!-- ko if: selectedScore() != 'Good' -->
Comment: <input data-bind="textInput: comment" />
<!-- /ko -->
how about you modify this line:
<!-- ko if: availableScores() != 'Good' -->
to something like:
<!-- ko if: displayComments() -->
and in your code add something like:
this.displayComments = ko.computed(function(){ return this.availableScores() !== 'Good'; });

Automatically update field in current row of an observablearray from textInput field

I have a table bound to a view model. When I select the table row, a field (notes) is updated from this:
<tbody data-bind="foreach: namespace.PersonResults.model">
<tr data-bind="click: $root.selectItem, css: {selected: $root.isSelected($data)}">
<td data-bind="text: Forename"></td>
<td data-bind="text: Surname"></td>
<td data-bind="text: PostCode"></td>
<td data-bind="text: Notes" style="display: none"></td>
</tr>
</tbody>
The field in the same div as the table (this is a single text area that should be updated when selecting the row on the table above, and update the table by the time the user chooses another row).
<textarea data-bind="textInput: editNotes"></textarea>
the viewModel is currently doing this:
var resultsViewModel = function() {
var self = this;
self.model = ko.observableArray();
self.editNotes = ko.observable();
self.selectItem = function(record) {
self.selectedItem(record);
self.editNotes(record.Notes);
}
self.getData () {
// some ajax stuff to populate the table
}
}
This works fine for displaying the notes in the textarea, but how do I get this to go the other way, and populate the field in the observableArray if the user has altered the contents of the textarea?
You can just bind a td to the same property as the textarea. E.g.:
var resultsViewModel = function() {
var self = this;
self.editNotes = ko.observable('initial value');
}
var vm = {
selectedResult: ko.observable(null),
results: [new resultsViewModel(), new resultsViewModel()]
};
vm.selectResult = function(result) { vm.selectedResult(result); };
ko.applyBindings(vm);
.selected { background-color: pink; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<table><tbody data-bind="foreach: results">
<tr data-bind="css: { selected: $root.selectedResult() === $data }">
<td data-bind="text: editNotes"></td>
<td><button data-bind="click: $root.selectResult">Select</button></td>
</tr>
</tbody></table>
<!-- ko with: selectedResult -->
<textarea data-bind="textInput: editNotes"></textarea>
<!-- /ko -->
You need to bind to the value of a textarea:
<td>
<textArea data-bind="value: $data.Notes"></textArea>
</td>
I've knocked up a quick and simplified demo in this fiddle
Edited to add:
Here's an updated fiddle that's more in line with what you want: fiddle

Remove button UI issue inside nested templates - Knockout.js

I'm in a bit of trouble from good 20 hours now. I am using knockout.js and dynamically add/remove rows from html table. I am having trouble in displaying an extra column for the remove button dynamically, my template is:
<table class="tg">
<tbody data-bind="template: {name: 'LineItemsBodyScript', foreach: LineItemFields, afterRender: addRowRemoveButton}"></tbody>
</table>
//template that gets called from HTML table.
<script id="LineItemsBodyScript" type="text/html">
<!-- ko ifnot: isFirsElement($index) -->
<tr data-bind="template: {name: 'LineItemDataTemplate', foreach: $data }"></tr>
<!-- /ko -->
</script>
//template called inside the template
<script id="LineItemDataTemplate" type="text/html">
<td><input type="text" data-bind="value: FieldValue, visible: IsVisible, enable: IsUpdatable" class="table-column" /></td>
</script>
If i add remove button in 'LineItemDataTemplate' template, it renders the remove button after every column (makes sense). And if i add remove button in 'LineItemsBodyScript', it gets overwritten by the child template. My model is, List>.
How and where could i add the remove button?
<td><input type='button' value="Remove" /></td>
I looked around and found afterRender afterAdd methods but they are not going to solve the issue.
Note: No. of columns are unknown (therefore i made a generic class for Column-Name & Column-Value)
You can add an extra <td> in the LineItemDataTemplate template when it's being rendered for the last field (grootboek) for each row that is not the header row:
Last field when: $index() == $parentContext.$data.length - 1
Not header row (first row): $parentContext.$index() > 0
Which results in:
<script id="LineItemDataTemplate" type="text/html">
<td><input type="text"
data-bind="value: FieldValue, visible: IsVisible,
enable: IsUpdatable"
class="table-column" /></td>
<!-- ko if: $parentContext.$index() > 0
&& $index() == $parentContext.$data.length - 1 -->
<td>
<button data-bind="click: removeLineItem">Remove</button>
</td>
<!-- /ko -->
</script>

Build knockout model and view dynamically, radio buttons not being set

I am in the process of making one of my previous questions fully dynamic in that the model is built from server data, and the view loops through the viewmodel via the knockout ko foreach functionality.
The problems I am facing are:
The radio options don't stay with the value set, i.e. I click on the Operating System, and then select a Database option, and then the Operating System setting disappears.
The dependent options (in this case database and clustering) do not have their initial selection selected when the dependent option changes (i.e. when OS changes, DB should go back to the first option, none).
My fiddle is here and i think the problem is either related to the code below:
computedOptions.subscribe(function () {
var section = this;
console.log("my object: %o", section);
section.selection(section.options()[0].sku);
},section);
Or my view bindings:
<!-- ko foreach: selectedOptions -->
<h3><span data-bind="text: description"></span></h3>
<table class="table table-striped table-condensed">
<thead>
<tr>
<th colspan="2" style="text-align: left;">Description</th>
<th>Price</th>
</tr>
</thead>
<tbody>
<!-- ko foreach: options -->
<tr>
<td><input type="radio" name="$parent.name" data-bind="checkedValue: $data, checked: $parent.selection" /></td>
<td style="text-align: left;"><span data-bind="text: name"></span></td>
<td style="text-align: left;"><span data-bind="text: price"></span></td>
</tr>
<!-- /ko -->
</tbody>
</table>
<!-- /ko -->
I am not sure which and would appreciate a fresh eyes as my brain hurts from the jsfiddle session.
You have two problems:
You are not correctly binding your radio button's names: name="$parent.name" is not a knockout binding expression and it just assigns the string "$parent.name" to all of your radio buttons. What you need is to use the attr binding:
<input type="radio" data-bind="checkedValue: $data,
checked: $parent.selection,
attr: { name: $parent.name }" />
The initial selection is not working because you are using the checkedValue: $dataoption this means that your checked should contain the whole object and not just one property (sku) so you need to change your computedOptions.subscribe to:
computedOptions.subscribe(function () {
var section = this;
section.selection(section.options()[0]);
},section);
Demo JSFiddle.

Categories

Resources