ReactJS checkable list issue - javascript

I'm trying to create React component that contains a long list (~500 items) with checkboxes opposite each item. It should toggle checked state of each item and toggle checked state of all items in the list. I implemented that component, as I see that. But my solution has low performance and some time lag when I toggle checkbox. When I integrated that in page, it work slower than this jsFiddle example.
jsFiddle
What I'm doing wrong? Should I choose another way to work with items data?
var Hello = React.createClass({
getInitialState: function () {
var db = [];
for (var i = 0, l = 100; i < l; i++) {
db.push({
name: Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 5),
value: i
});
}
return {
db: db
};
},
checkAll: function (ev) {
var items = this.state.db.slice();
items.forEach(function (v) {
v.checked = ev.target.checked;
});
this.setState({db: items});
},
handleCheck: function (ev) {
debugger;
var id = ev.target.dataset.id;
var items = this.state.db.slice();
var item = items.filter(function (v) {
return v.value == id;
})[0];
item.checked = ev.target.checked;
console.log(items.filter(function (v) {
return v.checked;
}));
this.state({db: items});
},
render: function () {
return <div>
<table>
<thead>
<tr>
<th>Name</th>
<th>Value</th>
<th>
<input type="checkbox" onChange={this.handleCheck} id="check-all"/>
<label htmlFor="check-all">Check all</label>
</th>
</tr>
</thead>
<tbody> {
this.state.db.map(function (v, i) {
return (
<tr key={i}>
<td>{v.name}</td>
<td>{v.value}</td>
<td>
<input id={'item-'+i} type="checkbox"
data-id={i}
onChange={this.handleCheck}
checked={v.checked}/>
<label htmlFor={'item-'+i}>Check this</label>
</td>
</tr>
);
}.bind(this))
}
</tbody>
</table>
</div>;
}
});
SOLUTION
I have many cells in my table with complex hierarchy. When I toggle checkbox value, all cells rerendered, including ones with unchanged values, that causes huge time lag. I splited up my component to some small components with shouldComponentUpdate callbacks and that works fine. Thanks all for help!

I've made some improvements to your code: https://jsfiddle.net/Lfbodh90/1/
What I've changed:
In the event handler, you can get the item by index, speeding up lookup time a lot:
handleCheck: function (id) {
var items = this.state.db;
var item = items[id]
item.checked = ev.target.checked;
this.setState({db: items});
}
Another thing: when creating the checkbox, you can pass the index parameter directly, using bind:
<input id={'item-'+i} type="checkbox"
onChange={this.handleCheck.bind(this, i)}
checked={v.checked}/>
<label htmlFor={'item-'+i}>Check this</label>
I've also removed the slice calls which are not necessary in the example given.
I've increased the number of items on the JSFiddle above just for testing. Now when you check/uncheck an specific checkbox, it's really fast.
Hope this helped

Related

How to retrieve true/false from a checkbox input?

I have a table, and each row of the table has two checkboxes. I am going through each row of the table and trying to send a boolean back to the controller (true if checked, false if not), but the value being passed back is always false. For reference, this is how I have been setting up the table:
#foreach (var item in Model.lockers)
{
<tr>
<td>
#Html.CheckBoxFor(modelItem => item.IsActive, new { htmlAttributes = new { #class = "form-control IsEnabled" } })
</td>
<td>
#Html.CheckBoxFor(modelItem => item.IsADA, new { htmlAttributes = new { #class = "form-control IsADA" } })
</td>
</tr>
}
And this is my code to retrieve the information from this checkbox:
var list = [];
$('#tblMaster tbody tr').each(function (index, ele) {
var LockerDoorMaster = {
IsActive: $('.IsEnabled', this).is(":checked"),
IsADA: $('.IsADA', this).is(":checked")
}
list.push(LockerDoorMaster);
});
How can I modify my code so that the value 'true' will be passed back if the checkbox is checked? Thank you!
<html>
<body>
<form name="y">
<input type="checkbox" name="inputOne">
<input type="checkbox" name="inputTwo">
</form>
<p id="demo"></p>
<script>
if(document.forms["form"]["inputOne"].selected == "true"){ document.getElementById("demo").innerHTML = "You Selected The First Button";}
if(document.forms["form"]["inputTwo"].selected == "true"){ document.getElementById("demo").innerHTML = "You Selected The Second Button";}
</script>
</body>
</html>
sometimes plain vanilla js is the best way to go.
var cb = document.getElementById('cb').checked;
console.log(cb);
var cb2 = document.getElementById('cb2').checked;
console.log(cb2);
<input id='cb' type='checkbox'>
<input id='cb2' type='checkbox' checked>
If i understood you correctly, you need to add an event that will trigger on checkbox change, because otherwise you will always have the checkbox initial value when you submit the data in your list.
var list = [];
function setList() {
list = [];
$('#tblMaster tbody tr').each(function (index, ele) {
var LockerDoorMaster = {
IsActive: $('.IsEnabled', this).prop('checked'),
IsADA: $('.IsADA', this).prop('checked')
};
list.push(LockerDoorMaster);
});
}
setList();
$('.IsEnabled').on('change', function () {
setList();
});
$('.IsADA').on('change', function () {
setList();
});
Basically this code updates your list every time the checkbox value changes. Although this is not the best approach, but it answers your question

Javascript table age range filter

I created a table that contain employees information. I want to filter the table by gender and age. I create the filter for the gender, but the filter for the age. I had hardtime to find a solution for it. For example the filter i want to do is to find employees between two number, like all employees between age 23 and 30. here is my code.
export default class EmployeeList extends React.Component {
constructor() {
super();
this.state = {
employees: []
};
}
componentDidMount() {
this.employeesTracker = Tracker.autorun(() => {
// Meteor.subscribe('employees');
const employees = Employees.find().fetch();
this.setState({ employees });
});
}
componentWillUnmount() {
this.employeesTracker.stop();
}
renderEmployeesListItems() {
return this.state.employees.map(employee => {
return (
<tr key={employee._id}>
<td>{employee.name}</td>
<td>{employee.email}</td>
<td>{employee.age}</td>
<td>{employee.gender}</td>
<td>{employee.city}</td>
<td><Link className="link button" to={`/employee-detail/${employee._id}`}>EDIT</Link></td>
<td><button className="button pointer" onClick={() => Employees.remove({_id: employee._id})}>DELETE</button></td>
</tr>
);
});
}
// --------------------------
//Gender Filter
myFunction() {
var input, filter, table, tr, td, i;
input = document.getElementById("genName").value;
filter = input;
table = document.getElementById("myTable");
tr = table.getElementsByTagName("tr");
for (i = 0; i < tr.length; i++) {
td = tr[i].getElementsByTagName("td")[3];
if (td) {
if (td.innerHTML.toUpperCase().indexOf(filter) > -1) {
tr[i].style.display = "";
} else {
tr[i].style.display = "none";
}
}
}
}
render() {
return (
<div>
<select id="genName" onChange={this.myFunction.bind(this)}>
<option value="">All Genders</option>
<option value="M">Male</option>
<option value="F">Female</option>
</select>
<p>Minimum age:</p>
<input type="text" id="min" name="min"/>
<p>Maximum age:</p>
<input type="text" id="max" name="max"/>
<table id="myTable" className="employeeTable">
<tbody>
<tr>
<th>NAME</th>
<th>EMAIL</th>
<th>AGE</th>
<th>GENDER</th>
<th>CITY</th>
<th></th>
<th></th>
</tr>
{this.renderEmployeesListItems()}
</tbody>
</table>
</div>
);
}
}
To do this kind of filtering when user inputs data, you should use React State, and the best way to integrate it today is using hooks.
In the state, you will not store employees as you did (which is only the result of a minimongo query to fetch the whole database), but employeesToDisplay. This will avoid the Html parsing in myFunction.
After some linting using state, your code should look like something like this :
//Gender Filter
myFunction() {
var filter = document.getElementById("genName").value;
// best is to get the input from context (look into `this` with console.log here)
// assuming that filter is exactly what you are looking for in your database :
this.setState({
employeesToDisplay: Employees.find({gender: filter}).fetch()
})
}
Since state is reactive, your list will be updated as the gender is selected, with a rendering of the database elements that correspond to the query.
To do a query on the age of employees, refer to the mongoDB documentation on queries to build a request like :
Employees.find({age: {$gte: minAge, $lte: maxAge}}).fetch() that you will use to store the data to display in the state

Check and uncheck checkbox with Angular

In my form I have a table with checkbox in all of these columns. I have 3 <tr> and each <tr> has its ng-repeate calling the webservice to display the clones (Json data).
When I click on a checkbox I generate a js array which records id using this code :
checkoptions (array, model) {
angular.forEach(array, (value, key) => {
if (array[key].checked) {
model.push(array[key].id)
}
})
And in HTML :
<tr ng-repeat="developer in $ctrl.developers">
<td>{{developer.label}}</td>
<td>
<input type="checkbox" id="{{developer.id}}"
ng-change="$ctrl.checkoptions($ctrl.developers,$ctrl.employees.developers)"
ng-model="developer.checked">
<label for="{{developer.id}}"></label>
</td>
It works, but the problem is that when I uncheck a checkbox it is not removed from the js array
I included an else part to remove from the array:
http://jsfiddle.net/x9m1nqvp/1/
$scope.checkoptions = function (array, model) {
angular.forEach(array, (value, key) => {
if (array[key].checked) {
var index = model.indexOf(array[key].id);
if(index == -1)
model.push(array[key].id)
}
else {
var index = model.indexOf(array[key].id);
if(index >=0)
model.splice(index, 1);
}
})
While Everton's answer gets the job done, it is a bit redundant checking every item in the array, every time a single checkbox changes state. You don't really have to update for every single item in the array.
Here is an example, where only the checkbox that's actually toggled, is added or removed from the employees.developers array (Note: no need for the redundant angular.forEach):
$scope.checkoption = function (developer){
if (developer.checked) {
var index = $scope.employees.developers.indexOf(developer.id);
if(index == -1)
$scope.employees.developers.push(developer.id)
} else {
var index = $scope.employees.developers.indexOf(developer.id);
if(index >=0)
$scope.employees.developers.splice(index, 1);
}
}
and how this is used in the html:
<tr ng-repeat="developer in developers">
<td>{{developer.label}}</td>
<td>
<input type="checkbox" id="{{developer.id}}"
ng-change="checkoption(developer)"
ng-model="developer.checked">
<label for="{{developer.id}}"></label>
</td>
</tr>

Remove selected rows on a button click in angular js

I have a table with check box for each row .
I need to remove the rows for the selected check boxes in the table on a button click. (this button is outside ng-repeat).
The index of the selected rows are populated to an array using ng-change function but i'm unable to remove the selected rows on a single button click
Here is the Fiddle
HTML
<div ng-app="approvalApp">
<div ng-controller="SimpleApprovalController" >
<table style="width:90%" border="5" >
<tr>
<th><input type="checkbox" ng-model="CheckAllData" ng- change="selectAll()" /></th>
<th>Date</th>
<th>AssociateID</th>
<th>Check-In</th>
<th>Checkout</th>
</tr>
<tr data-ng-repeat="approval in approvalitems">
<td><input type="checkbox" value="{{approval.ReqId}}" data-ng-model="approval.selected" data-ng-change="SelectDeselect($index)"/></td>
<td>{{approval.Date}}</td>
<td>{{approval.AssociateID}}</td>
<td>{{approval.CheckIn}}</td>
<td>{{approval.Checkout}}</td>
</tr>
</table>
<input type="button" value="Approve" data-ng-model="ApproveIndex" data-ng-click="ApproveRequest()" />
Script
$scope.SelectDeselect=function(index)
{
$scope.getIndexvalues = [];
angular.forEach($scope.approvalitems, function (approval,index) {
if (!!approval.selected) {
$scope.getIndexvalues.push(index);
$scope.CheckAllData = false;
}
});
console.log($scope.getIndexvalues);
};
$scope.ApproveRequest = function () {
$scope.selectedIdsArray = [{}];
angular.forEach($scope.approvalitems, function (item) {
if (!!item.selected) {
$scope.selectedIdsArray.push({ Reqid: item.ReqId, Status: "Approved" });
$scope.CheckAllData = false;
}
});
};
};
So how to use getIndexvalues in approverequest function , or is there any better way to remove it using other angular directive.
I'm a newbie to angular js .
Fiddle: http://jsfiddle.net/jpk547zp/1/
$scope.ApproveRequest = function () {
$scope.selectedIdsArray = [{}];
$scope.approvalitemsNew = [];
angular.forEach($scope.approvalitems, function (item) {
if (!!item.selected) {
$scope.selectedIdsArray.push({ Reqid: item.Date, Status: "Approved" });
$scope.CheckAllData = false;
item.hideThis = true;
console.log($scope.selectedIdsArray);
} else {
$scope.approvalitemsNew.push(item);
}
});
$scope.approvalitems = $scope.approvalitemsNew;
$scope.getIndexvalues = [];
};
Hope this helps.
you can simply do
$scope.ApproveRequest = function () {
$scope.approvalitems = $scope.approvalitems.filter(function(i){
return !i.selected;
});
};

knockoutjs deselect/select all checkboxes when one or more items deselected

This is similar to, but different from other questions around this topic.
I have a table with a list of records, each having a select checkbox.
In the table header I have a "Select All" checkbox.
When the user checks/unchecks "Select All" the records are selected/unselected. This works fine.
However, I need to deselect my "Select All" checkbox when one or more of the records are deselected.
My markup:
<table>
<thead>
<tr>
<th>Name</th>
<th><input type="checkbox" data-bind="checked: SelectAll" /></th>
</tr>
</thead>
<tbody data-bind="foreach: $data.People">
<tr>
<td data-bind="text: Name"></td>
<td class="center"><input type="checkbox" data-bind="checked: Selected" /></td>
</tr>
</tbody>
</table>
My script (edited):
function MasterViewModel() {
var self = this;
self.People = ko.observableArray();
self.SelectAll = ko.observable(false);
self.SelectAll.subscribe(function (newValue) {
ko.utils.arrayForEach(self.People(), function (person) {
person.Selected(newValue);
});
});
}
my.Person = function (name, selected) {
var self = this;
self.Name = name;
self.Selected = ko.observable(false);
}
This works
http://jsfiddle.net/AneL9/
self.SelectAll = ko.computed({
read: function() {
var item = ko.utils.arrayFirst(self.People(), function(item) {
return !item.Selected();
});
return item == null;
},
write: function(value) {
ko.utils.arrayForEach(self.People(), function (person) {
person.Selected(value);
});
}
});
but will give you a ordo n ^ 2 problem when selecting deselecting all, you can use a pasuable computed to get around that
http://www.knockmeout.net/2011/04/pausing-notifications-in-knockoutjs.html
edit: You can also extend the computed with a throttle, this way you avoid the ordo n^2 problem
.extend({ throttle: 1 })
http://jsfiddle.net/AneL9/44/
You should make SelectAll computed observable like this:
self.SelectAll = ko.computed({
read: function() {
var persons = self.People();
for (var i = 0, l = persons.length; i < l; i++)
if (!persons[i].Selected()) return false;
return true;
},
write: function(value) {
ko.utils.arrayForEach(self.People(), function(person){
person.Selected(value);
});
}
});
and strip SelectAll.subscribe out.
http://jsfiddle.net/Yqj59/

Categories