knockout - header checkbox stays unchecked in table header when clicked - checkbox

I am new to knockout and I am stuck at a problem for last couple of days - I am sure it is something silly but cant figure out. Any help will be appreciate.
I am trying to select and deselect all rows in a table based on the header check box column. The SelectAll function works and selects/unselects all rows in table but the header remains unckecked?
<tr>
<th><input type="checkbox" data-bind="click: selectAll, checked: AllChecked"></th>
<th>#Html.Vocab("Document")</th>
<th>#Html.Vocab("Notes")</th>
<th>#Html.Vocab("Created")</th>
</tr>
<tbody data-bind="foreach: DocumentRows">
<tr >
<td><input type="checkbox" data-bind="checked: IsSelected"></td>
<td><data-bind="text: Notes"></td>
</tr>
</tbody>
And here is the script:
//Document
class Document {
Id: KnockoutObservable<number>;
Notes: KnockoutObservable<string>;
IsSelected: KnockoutObservable<boolean>;
constructor(data?) {
this.Id = ko.observable(0);
this.Notes = ko.observable("").extend({ defaultValue: "" });
this.IsSelected = ko.observable(false);
if (data != null) {
ko.mapping.fromJS(data, {}, this);
}
}
};
//DocumentS VIEW MODEL
class DocumentsViewModel {
DocumentRows: KnockoutObservableArray<Document>;
IsAnySelected: KnockoutObservable<boolean>;//used for delete button
constructor(params) {
this.DocumentRows = ko.observableArray([]);
this.selectedIds = ko.observableArray([]);
}
InitComputed = () => {
this.AllChecked= ko.pureComputed({
read: function () {
return this.selectedIds().length === this.DocumentRows().length;
},
write: function (value) {
this.selectedIds(value ? this.DocumentRows.slice(0) : []);
},
owner: this
}
this.IsAnySelected = ko.pureComputed(() => {
var isChecked = false;
ko.utils.arrayForEach(this.DocumentRows(), function (item) {
if (item.IsSelected()) {
isChecked = true;
}
});
return isChecked;
});
}
selectAll = (): void => {
if (this.selectedIds().length > 0) {
this.selectedIds.removeAll();
ko.utils.arrayForEach(this.DocumentRows(), function (item) {
item.IsSelected(false);
});
} else {
ko.utils.arrayPushAll(this.selectedIds(), this.DocumentRows())
ko.utils.arrayForEach(this.DocumentRows(), function (item) {
item.IsSelected(true);
});
}
}
}

Related

checkAll checkbox in angularjs not working

I am trying to save the checked check boxes data into an array, I am able push the data into array when one checkbox is checked, but when I check the checkAll check box all checkboxes are not getting selected, if I checked all the checkboxes and uncheck the checkAll checkbox all are not getting unchecked.
I have tried using javascript it worked as I expected, but I am trying to do it in angularjs and getting failed to achieve.
HTML:
<div ng-if="RosList!= null">
<table align='center' width='80%' >
<tr class='trdesign'>
<td>
<input type="hidden" id="RosList" data-ng-model="RosList" name="RosList" value="{{RosList}}">
<input type="checkbox" id="all" data-ng-model="checkedAll" ng-change="toggleCheckAll()" />
</td>
<td> Sl No</td>
<td> RO No.</td>
</tr>
<tr ng-repeat="user in RosList" >
<td>
<input type="checkbox" value="{{user.do_ro_no}}" data-ng-
model="user.checked" ng-change="modifyArrayToPost(user)"/>
</td>
<td>{{user.slno}}</td>
<td>{{user.do_ro_no}} </td>
</tr>
</table>
<tt>array: {{arrayToPost}}</tt>
</div>
Angularjs:
$scope.arrayToPost = [];
$scope.toggleCheckAll = function() {
if($scope.checkedAll) {
angular.forEach($scope.RosList, function(user) {
user.checked = true;
$scope.modifyArrayToPost(user);
});
}
else {
angular.forEach($scope.RosList, function(user) {
user.checked = false;
$scope.modifyArrayToPost(user);
});
}
}
$scope.modifyArrayToPost = function(user) {
if(user.checked && $scope.arrayToPost.indexOf(user.do_ro_no) == -1){
$scope.arrayToPost.push(user.do_ro_no);
}
else if(!user.checked) {
$scope.arrayToPost.splice($scope.arrayToPost.indexOf(user.do_ro_no), 1);
}
}
$scope.$watch('RosList', function() {
var allSet = true;
var allClear = true;
angular.forEach($scope.RosList, function(user) {
if (user.checked) {
allClear = false;
} else {
allSet = false;
}
});
var checkAll = $element.find('#all');
checkAll.prop('indeterminate', false);
if (allSet) {
$scope.checkedAll = true;
} else if (allClear) {
$scope.checkedAll = false;
} else {
$scope.checkedAll = false;
checkAll.prop('indeterminate', true);
}
}, true);

Push and splice into array when checkall and checkbox is checked in angularjs

I am trying to push and splice the elements based on checkall, single checkbox clicked, my problem is I am getting a list from angularjs post request and displayed it using ng-repeat I have given provision to enter some text in a new column along with ng-repeat data. Now based on the user selection of checkall or single checkbox clicked I am pushing the data into array. Here I am able to push the data when the user clicked on single checkbox, but when the user clicked on chekall checkbox 0, 1 are pushing the array instead of textbox value. Any help will be greatly appreciated.
Html
<table class='reportstd' align='center' width='80%'>
<tr class='trdesign'>
<td>
<input type="checkbox" name="checkAll" id="all" data-ng-model="checkedAll" data-ng-change="toggleCheckAll()" />
</td>
<td> Sl No</td>
<td> RO No.</td>
<td> Truck No.</td>
</tr>
<tr data-ng-repeat="user in RosList">
<td> <input type="checkbox" value="{{user.do_ro_no}}" data-ng-model="user.checked" data-ng-change="modifyArrayToPost(user,truck_no[$index])" /> </td>
<td>{{$index + 1}}</td>
<td>{{user.do_ro_no}}</td>
<td><input type='text' data-ng-model="truck_no[$index]" id="truck_no_{{$index}}" name="truck_no_{{$index}}" value=""></td>
</tr>
</table>
<table>
<tr>
<td colspan='2'><input type="submit" id="btn_submit" name='sea' value='Search' data-ng-submit="postROs(arrayToPost)" /></td>
</tr>
</table>
Angularjs
$scope.arrayToPost = [];
$scope.toggleCheckAll = function() {
if ($scope.checkedAll) {
angular.forEach($scope.RosList, function(user, truckno) {
user.checked = true;
$scope.modifyArrayToPost(user, truckno);
});
} else {
angular.forEach($scope.RosList, function(user, truckno) {
user.checked = false;
$scope.modifyArrayToPost(user, truckno);
});
}
}
$scope.modifyArrayToPost = function(user, truckno) {
if (user.checked && truckno != null && $scope.arrayToPost.indexOf(user.do_ro_no) == -1) {
$scope.arrayToPost.push(user.do_ro_no, truckno);
} else if (!user.checked) {
$scope.arrayToPost.splice($scope.arrayToPost.indexOf(user.do_ro_no, truckno), 2);
}
}
$scope.$watch('RosList', function() {
var allSet = true;
var allClear = true;
angular.forEach($scope.RosList, function(user, truckno) {
if (user.checked) {
allClear = false;
} else {
allSet = false;
}
});
var checkAll = $element.find('#all');
checkAll.prop('indeterminate', false);
if (allSet) {
$scope.checkedAll = true;
} else if (allClear) {
$scope.checkedAll = false;
} else {
$scope.checkedAll = false;
checkAll.prop('indeterminate', true);
}
}, true);
$scope.RosList = [
{do_ro_no: "217PALV000201898", slno: 1, },
{do_ro_no: "317PALV000201898", slno: 2, }
]
truck_no model is not coming from RosList.
You should initialize truck_no in your controller as $scope.truck_no = [] in order to access the values, and in your $scope.toggleCheckAll function change $scope.modifyArrayToPost(user, truckno); to $scope.modifyArrayToPost(user, $scope.truck_no[truckno]);
EDIT:
I've slightly modified your code to handle all cases.
Demo: https://next.plnkr.co/edit/DnzsCFkPQU8ByFZ8
If I understand the issue correctly, I think that the solution is much simpler. The main confusation is that there is not just only one "source of truth" - you hold a state for each row and also all the do_ro_no's.
I suggest to keep track only for each row and calculate the arrayToPost whenever you need.
Like this:
angular.module('app', []).controller('ctrl', ($scope, $element) => {
$scope.truck_no = [];
$scope.RosList = [{
do_ro_no: "217PALV000201898",
slno: 1,
},
{
do_ro_no: "317PALV000201898",
slno: 2,
}
];
$scope.getTruckNo = () => {
return $scope.truck_no.filter((t, index) => {
return $scope.RosList[index].checked;
});
}
$scope.getArrayToPost = () => {
return $scope.RosList
.filter(ros => ros.checked)
.map(ros => ros.do_ro_no);
}
$scope.arrayToPost = [];
$scope.toggleCheckAll = function() {
if ($scope.checkedAll) {
//angular.forEach($scope.RosList, function(user, truckno) {
// user.checked = true;
// $scope.modifyArrayToPost(user, truckno);
//});
$scope.RosList.forEach(ros => ros.checked = true);
} else {
//angular.forEach($scope.RosList, function(user, truckno) {
// user.checked = false;
// $scope.modifyArrayToPost(user, truckno);
//});
$scope.RosList.forEach(ros => ros.checked = false);
}
}
//$scope.modifyArrayToPost = function(user, truckno) {
// if (user.checked && truckno != null && $scope.arrayToPost.indexOf(user.do_ro_no) == -1) {
// $scope.arrayToPost.push(user.do_ro_no, truckno);
// } else if (!user.checked) {
// $scope.arrayToPost.splice($scope.arrayToPost.indexOf(user.do_ro_no, truckno), 2);
// }
//}
//$scope.$watch('RosList', function() {
// var allSet = true;
// var allClear = true;
// angular.forEach($scope.RosList, function(user, truckno) {
// if (user.checked) {
// allClear = false;
// } else {
// allSet = false;
// }
// });
//
// var checkAll = $element.find('#all');
// checkAll.prop('indeterminate', false);
// if (allSet) {
// $scope.checkedAll = true;
// } else if (allClear) {
// $scope.checkedAll = false;
// } else {
// $scope.checkedAll = false;
// checkAll.prop('indeterminate', true);
// }
//}, true);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
<table class='reportstd' align='center' width='80%'>
<tr class='trdesign'>
<td>
<input type="checkbox" name="checkAll" id="all" data-ng-model="checkedAll" data-ng-change="toggleCheckAll()" />
</td>
<td> Sl No</td>
<td> RO No.</td>
<td> Truck No.</td>
</tr>
<tr data-ng-repeat="user in RosList">
<td> <input type="checkbox" value="{{user.do_ro_no}}" data-ng-model="user.checked" data-ng-change="modifyArrayToPost(user,truck_no[$index])" /> </td>
<td>{{$index + 1}}</td>
<td>{{user.do_ro_no}}</td>
<td><input type='text' data-ng-model="truck_no[$index]" id="truck_no_{{$index}}" name="truck_no_{{$index}}" value=""></td>
</tr>
</table>
<table>
<tr>
<td colspan='2'><input type="submit" id="btn_submit" name='sea' value='Search' data-ng-submit="postROs(arrayToPost)" /></td>
</tr>
</table>
<pre>
{{getTruckNo() | json}}
</pre>
</div>
The array is the result of getTruckNo() as you can see in the snippet.

ReactCSSTransition on table rows and empty table state

I'm looking to implement a table in ReactJS with the following features:
initially empty
rows are dynamically added and removed
when there are no rows, an empty state (e.g. a box saying "Table empty") should be displayed
when a row is removed, there should be a fade out transition
when the first row is added, there should be no fade out transition on the empty state
I came up with two approaches using ReactCSSTransitionGroup.
1. Wrap only rows into ReactCSSTransitionGroup
Codepen: https://codepen.io/skyshell/pen/OpVwYK
Here, the table body is rendered in:
renderTBodyContent: function() {
var items = this.state.items;
if (items.length === 0) {
return (
<tbody><tr><td colSpan="2">TABLE EMPTY</td></tr></tbody>
);
}
const rows = this.state.items.map(function(name) {
return (
<tr key={name}>
<td>{name[0]}</td>
<td>{name[1]}</td>
</tr>
);
});
return (
<ReactCSSTransitionGroup
component="tbody"
transitionName="example"
transitionEnter={false}
transitionLeave={true}>
{rows}
</ReactCSSTransitionGroup>
);}
The issue is that the last row to be removed does not get the fade out transition before disappearing since the ReactCSSTransitionGroup is not rendered when item.length === 0.
2. Wrap table body into ReactCSSTransitionGroup
Codepen: https://codepen.io/skyshell/pen/RpbKVb
Here, the entire renderTBodyContent method is wrapped into ReactCSSTransitionGroup within the render method:
<ReactCSSTransitionGroup
component="tbody"
transitionName="example"
transitionEnter={false}
transitionLeave={true}>
{this.renderTBodyContent()}
</ReactCSSTransitionGroup>
And the RenderTBody method looks like:
renderTBodyContent: function() {
var items = this.state.items;
if (items.length === 0) {
return (
<tr><td colSpan="2">TABLE EMPTY</td></tr>
);
}
const rows = this.state.items.map(function(name) {
return (
<tr key={name}>
<td>{name[0]}</td>
<td>{name[1]}</td>
</tr>
);
});
return rows;}
The issue is that the empty state gets animated too.
Any suggestions on how to obtain the desired behaviour?
Thanks!
Thank you realseanp for your pointers. Using the low level API and TweenMax instead of CSS transitions, I came up with the following solution. First, introduce a Row component:
var Row = React.createClass({
componentWillLeave: function(callback) {
var el = React.findDOMNode(this);
TweenMax.fromTo(el, 1, {opacity: 1}, {opacity: 0, onComplete: callback})
},
componentDidLeave: function() {
this.props.checkTableContent();
},
render: function() {
const name = this.props.name;
return (
<tr>
<td>{name[0]}</td>
<td>{name[1]}</td>
</tr>
);
}
});
Then populate the table based on an isEmpty flag:
var Table = React.createClass({
getInitialState: function() {
return {
items: [],
isEmpty: true
};
},
addRow: function() {
var items = this.state.items;
var firstName = firstNames[Math.floor(Math.random() * firstNames.length)];
var lastName = lastNames[Math.floor(Math.random() * lastNames.length)];
items.push([firstName, lastName]);
this.setState({items: items, isEmpty: false});
},
removeLastRow: function() {
var items = this.state.items;
if (items.length != 0) {
items.splice(-1, 1);
this.setState({items: items});
}
},
checkTableContent: function() {
if (this.state.items.length > 0) {
this.setState({isEmpty: false});
}
else {
this.setState({isEmpty: true});
this.forceUpdate();
}
},
renderTBodyContent: function() {
if (this.state.isEmpty) {
return (
<tr><td colSpan="2">TABLE EMPTY</td></tr>
);
}
var that = this;
const rows = this.state.items.map(function(name) {
return <Row
key={name}
name={name}
checkTableContent={that.checkTableContent} />;
});
return rows;
},
render: function() {
return (
<div>
<button onClick={this.addRow}>Add row</button>
<button onClick={this.removeLastRow}>Remove row</button>
<table>
<thead>
<tr>
<th>First name</th>
<th>Last name</th>
</tr>
</thead>
<ReactTransitionGroup
component="tbody"
transitionName="example"
transitionEnter={false}
transitionLeave={true}>
{this.renderTBodyContent()}
</ReactTransitionGroup>
</table>
</div>
);
}
});
Codepen: https://codepen.io/skyshell/pen/yMYMmv

Generate Table dynamically display error Uncaught Error: Invariant Violation

I'm creating a table that uses data from a json, the json "policies" change when I click on different links in the page, the thing is that when I click and the state change, I have to generate the table again with the new json, but I get an
Uncaught Error: Invariant Violation: processUpdates(): Unable to find child 1 of element. This probably means the DOM was unexpectedly mutated (e.g., by the browser), usually due to forgetting a <tbody> when using tables, nesting tags like <form>, <p>, or <a>, or using non-SVG elements in an <svg> parent. Try inspecting the child nodes of the element with React ID .0.1.0.2.3.1.1.2.0.1.
The first time the page loads the table is correctly generated.
module.exports = React.createClass({
onPoliciesChange: function (policiesStore) {
this.setState(policiesStore);
},
getInitialState: function () {
return {
policies: []
};
},
componentDidMount: function () {
this.unsubscribeAlertsStore = AlertsStore.listen(this.onPoliciesChange);
},
componentWillUnmount: function () {
this.unsubscribeAlertsStore();
},
cols: [
{ key: 'name', label: 'Name'},
{ key: 'severity', label: 'Severity' },
{ key: 'width', label: 'Width' },
{ key: 'pulsate', label: 'Pulsate' }
],
generateHeaders: function () {
var cols = this.cols; // [{key, label}]
// generate our header (th) cell components
return cols.map(function (colData) {
return <th key={colData.key}> {colData.label} </th>;
});
},
generateRows: function () {
var slf = this;
var cols = this.cols, // [{key, label}]
data = this.data;
//per each item
return this.state.policies.map(function (item) {
// handle the column data within each row
var cells = cols.map(function (colData) {
return <td> {item[colData.key]} </td>;
});
return <tr key={item.id}> {cells} </tr>;
});
},
render: function () {
var headerComponents = this.generateHeaders(),
rowComponents = this.generateRows();
return (
<table className="table table-condensed table-striped">
<thead> {headerComponents} </thead>
<tbody> {rowComponents} </tbody>
</table>
);
}
});
I just move the from the render to the function that creates the rows:
generateRows: function () {
var severity = this.renderSeverity();
var slf = this;
var cols = this.cols, // [{key, label}]
data = this.data;
//per each item
return this.state.policies.map(function (item) {
// handle the column data within each row
var cells = cols.map(function (colData) {
if (colData.key === 'width') {
//return <td> <input type="checkbox" name="vehicle" value="Bike" onChange={slf.onChange.bind(null, id) }/></td>;
return <td> <input type="checkbox" onChange={slf.onChangeWidth.bind(null, item.id) }/></td>;
} else if (colData.key === 'pulsate') {
return <td> <input type="checkbox" onChange={slf.onChangePulsate.bind(null, item.id) }/></td>;
} if (colData.key === 'policyCheck') {
return <td> <input type="checkbox" onChange={slf.onChangePolicy.bind(null, item.id) }/></td>;
} else if (colData.key === 'severity') {
// colData.key might be "firstName"
return <td>{item[colData.key]} {slf.renderSeverity(item.severity) }</td>;
} else {
// colData.key might be "firstName"
return <td> {item[colData.key]} </td>;
}
});
return <tbody><tr key={item.id}> {cells} </tr></tbody>;
});
}

How can we disable other checkboxes by clicking a checkbox from a list in Angular2

I have an list of checkboxes. In this I want to disable other checkboxes When I have checked any item from list. Now if I uncheck that item then now all checkboxes need to be enabled.
This is the plunker - http://plnkr.co/edit/5FykLiZBQtQb2b31D9kE?p=preview
On unchecking any checkbox all the rest checkboxes are still disabled. How can I do this?
This is app.ts file -
import {bootstrap} from 'angular2/platform/browser';
import {Component} from 'angular2/core'
#Component({
selector: 'my-app',
template: `
<h2>{{title}}</h2>
<label *ngFor="let cb of checkboxes">
{{cb.label}}<input [disabled]="cb.status" type="checkbox"
[(ngModel)]="cb.state" (click)="buttonState(cb.id)">
</label><br />
{{debug}}
`
})
class App {
title = "Angular 2 - enable button based on checkboxes";
checkboxes = [{label: 'one',id: '0', status: ''},{label: 'two', id: '1', status:''},{label: 'three', id: '2', status: ''}];
constructor() {
//this.buttonState();
console.log("constructor called"); }
buttonState(id) {
console.log( id + "Button State Called");
//return this.checkboxes[0].status;
if(this.checkboxes[id].state == true){
this.checkboxes[id].status = true;
this.checkboxes[(id+1)%3].status = false;
this.checkboxes[(id+2)%3].status = false;
console.log("True Block");
}
else (this.checkboxes[id].state == false) {
this.checkboxes[id].status = false;
this.checkboxes[(id+1)%3].status = true;
this.checkboxes[(id+2)%3].status = true;
console.log("False Block");
}
}
get debug() {
return JSON.stringify(this.checkboxes);
}
}
bootstrap(App, [])
.catch(err => console.error(err));
Use this buttonState function instead yours:
buttonState(id) {
this.checkboxes.forEach(function(checkbox) {
if (checkbox.id !== id) {
checkbox.status = !checkbox.status;
}
})
}
see plunker example: http://plnkr.co/edit/ASzUR2jawpbifvwBXNO9?p=preview
another solution..not a good one...but still you can try it if you find any problem
class App {
title = "Angular 2 - enable button based on checkboxes";
checkboxes = [{label: 'one',id: '0', status: ''},{label: 'two', id: '1', status:''},{label: 'three', id: '2', status: ''}];
constructor() {
console.log("constructor called");
}
buttonState(id) {
console.log( this.checkboxes[id].state + "Button State Called");
//return this.checkboxes[0].status;
if(this.checkboxes[id].state == true ){
this.checkboxes[id].status = false;
this.checkboxes[(id+1)%3].status = false;
this.checkboxes[(id+2)%3].status = false;
console.log("True Block");
}
else if(this.checkboxes[id].state == false || this.checkboxes[id].state == undefined) {
this.checkboxes[id].status = false;
this.checkboxes[(id+1)%3].status = true;
this.checkboxes[(id+2)%3].status = true;
console.log("False Block");
}
}
http://plnkr.co/edit/mAvSQbyP9oh84LWfpGIm?p=preview

Resources