Remove button UI issue inside nested templates - Knockout.js - javascript

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>

Related

Access array items and finding their length

I'm trying to create a table on an html page using knockout. The table headers and columns can vary.
I'm using an Array containing other Arrays and looping through.
Here is an example:
Array is not observable
var Headers = ["Name", "Course", "DateofBirth"];
var Columns = ["John", "Math", "23/05/1979"]
self.Person.push(Headers);
self.Person.push(Columns);
Each Person got the above Arrays
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<!-- ko if: value().Persons.length > 0 -->
<!-- ko foreach: value().Persons-->
<div>
<table>
<thead>
<tr>
<!-- ko foreach: Headers -->
<th data-bind="text: $data">
</th>
<!--/ko-->
</tr>
</thead>
<tbody>
<!-- ko if: Columns.length <= 3 -->
<tr>
<!-- ko foreach: Columns -->
<td data-bind="text: $data"></td>
<!--/ko-->
</tr>
<!--/ko-->
<!-- ko ifnot: Columns.length > 3 -->
<tr data-bind="foreach: $data">
<td data-bind="text: $data"></td>
</tr>
<!--/ko-->
</tbody>
</table>
</div>
<!--/ko-->
<!--/ko-->
So Persons is the main Array and within it there is Headers and Columns. The reason why I'm checking Columns size is to add more <td> if there is more than three columns, so I end up with having $data corresponding to each header.
If Columns is less than or equal to three, then create one <td> with the data. Otherwise, create multiple <td> with the data.
I'm getting an error in Console saying Cannot read property 'length' of null
I'm handling the position of Columns items under each header through a javascript which splits the Columns into equal chunks based on the Headers size. So each item in Columns will be, in this example, a size of three.
Not sure if I'm missing something. Any ideas or suggestions?

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

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.

Update Knockoutjs table

I have a table bound to knockoutjs using foreach.
<table>
<thead>
<tr>
<th>ID</th>
<th>BALANCE</th>
<th>GENDER</th>
<th>AGE</th>
</tr>
</thead>
<tbody>
<!-- ko foreach: data -->
<tr>
<!-- ko foreach: Object.keys($data) -->
<td>
<label data-bind="text: $parent[$data]" />
</td>
<!-- /ko -->
</tr>
<!-- /ko -->
</tbody>
</table>
Table rows iterate an observableArray (about 2000 items).
After table is rendered, I need to edit a row but table do not render the row changed.
How can I do this whithout clear observableArray and reload it again?
Here JSFIDDLE
Thank you
You need to make your data array properties observable so knockout would observe the changes. I'd suggest you to use knockout mapping to do the job of creating observables for you, like this:
var foo = new MyVM();
var mapping = {
create: function(options) {
return ko.mapping.fromJS(options.data);
}
};
ko.mapping.fromJS(myJS, mapping, foo.data);
But you need to change your markup so it won't just iterate through object properties, but explicitly specify which property should be used:
<tbody>
<!-- ko foreach: data -->
<tr>
<td>
<label data-bind="text: _id" />
</td>
<td>
<label data-bind="text: balance" />
</td>
<td>
<label data-bind="text: gender" />
</td>
<td>
<label data-bind="text: age" />
</td>
</tr>
<!-- /ko -->
</tbody>
Here is working demo. Of course you can make observables yourself, then just rewrite your code, so every property of each item in data array would be an observable.
Edit:
Well, since we don't know the actual properties, I'd suggest the following code to make observables:
var foo = new MyVM();
for (var i=0, n = myJSON.length; i < n; i++) {
for (var prop in myJSON[i])
if (myJSON[i].hasOwnProperty(prop))
myJSON[i][prop] = ko.observable(myJSON[i][prop]);
}
foo.data(myJSON);
And in your view model function:
self.changeRow = function (){
if(typeof self.data() != "undefined"){
self.data()[0]["gender"]("male");
}
};
See updated demo.

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.

How to render a table with some fixed and some dynamic columns

I want to get a table with these columns:
[Name]
[Club]
[Dynamic1]
[Dynamic2]
[Dynamic3]
etc etc.
I tried this:
<table>
<tbody data-bind="template: { name: 'rowTmpl', foreach: runners }">
</tbody>
</table>
<script id="rowTmpl" type="text/html">
<tr data-bind="template: { name: 'colTmpl', foreach: radios }" >
<td data-bind="text: name"></td>
<td data-bind="text: club"></td>
</tr>
</script>
<script id="colTmpl" type="text/html">
<td>aa</td>
</script>
#section Scripts{
<script src="/Scripts/knockout-1.3.0beta.js" type="text/javascript"></script>
<script type="text/javascript">
var vm = {
id: 1,
name: 'H21',
radios: ['2km', '4km', 'mål'],
runners: ko.observableArray([
{ name: 'Mikael Eliasson', club: 'Göteborg-Majorna OK', radios: ko.observableArray([{}, {}, {}]) },
{ name: 'Ola Martner', club: 'Göteborg-Majorna OK', radios: ko.observableArray([{}, {}, {}]) }
])
};
ko.applyBindings(vm);
</script>
}
My problem is that the tds inside colTmpl is not databoud, it's empty and placed after the third column with the text 'aa'. See this fiddle.
If you are using 1.3 beta (your fiddle is referencing the latest build), then you can do this:
<table>
<tbody data-bind="foreach: runners">
<tr>
<td data-bind="text: name"></td>
<td data-bind="text: club"></td>
<!-- ko foreach: radios-->
<td>aa</td>
<!-- /ko -->
</tr>
</tbody>
</table>
Sample here: http://jsfiddle.net/rniemeyer/bd7DT/
If you need to do this prior to 1.3 with jQuery templates, then you would want to pass the first item in your array into the template via templateOptions and do an {{if}} to check if you are on the first radio and render the two cells. Another option in jQuery templates is to use {{each}} around your dynamic cells rather than the foreach option of the template binding on the parent. You would lose some efficiency, if your columns are frequently changing dynamically. I can provide a sample for these two options, if necessary.
It is because of the fact that the content of <tr data-bind="template: { name: 'colTmpl', foreach: radios }" > is getting replaced by the template you specify.
If you instead do:
<script id="rowTmpl" type="text/html">
<tr>
<td data-bind="text: name"></td>
<td data-bind="text: club"></td>
<td data-bind="template: { name: 'colTmpl', foreach: radios }" ></td>
</tr>
</script>
<script id="colTmpl" type="text/html">
<span> . aa . </span>
</script>
It will render.

Categories

Resources