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
Related
I was trying to build my first search function for a phonelist. Unfortunately it looks like, my filter function loops only trough the last column of the table.
Did i miss something? Or do i have to use a different approach for this?
PS: Pardon for the possible duplicate. All examples that i've found has been for PHP.
Many thanks in advance!
const phonelist = document.querySelector('table');
const searchInput = document.querySelector('#search');
const searchResult = document.querySelector('#search-result');
const searchValue = document.querySelector('#search-value');
// EVENTS
function initEvents() {
searchInput.addEventListener('keyup', filter);
}
function filter(e) {
let text = e.target.value.toLowerCase();
console.log(text);
// SHOW SEARCH-RESULT DIV
if (text != '') {
searchValue.textContent = text;
searchResult.classList.remove('hidden');
} else {
searchResult.classList.add('hidden');
}
document.querySelectorAll('td').forEach((row) => {
let item = row.textContent.toLowerCase();
if (item.indexOf(text) != -1) {
row.parentElement.style.display = 'table-row';
console.log(row.parentElement);
} else {
row.parentElement.style.display = 'none';
}
})
}
// ASSIGN EVENTS
initEvents();
<input id="search" />
<div class="phonelist">
<div id="search-result" class="hidden">
<p>Search results for <b id="search-value"></b>:</p>
</div>
<table class="striped">
<thead>
<tr>
<th>Phone</th>
<th>Fax</th>
<th>Room</th>
<th>Name</th>
<th>Title</th>
</tr>
</thead>
<tbody>
<tr>
<td>165</td>
<td>516</td>
<td>1.47</td>
<td>Johnathan Doe</td>
<td>Sales</td>
</tr>
<tr>
<td>443</td>
<td>516</td>
<td>1.47</td>
<td>Jane Dow</td>
<td>Development</td>
</tr>
</tbody>
</table>
</div>
it looks like you are querying the wrong element
document.querySelectorAll('td').forEach((row) => {
I think you want to be querying the row
document.querySelectorAll('tr').forEach((row) => {
otherwise you are of overriding your class changes with whatever is the result of the last column
(and obviously apply the class on the tr and not the parent of the tr)
Your code is actually going through all the elements but the changes from last column are overriding changes from previous columns.
Let's say you searched for dow, 2nd row 4th column is matched and shows the parent but after that your loop goes to 2nd row 5th column which doesn't match and hides the parent row.
I have updated your code, as shown below you should loop through the rows, check if any of its columns are matching and update the row only once based on the result.
const phonelist = document.querySelector('table');
const searchInput = document.querySelector('#search');
const searchResult = document.querySelector('#search-result');
const searchValue = document.querySelector('#search-value');
// EVENTS
function initEvents() {
searchInput.addEventListener('keyup', filter);
}
function filter(e) {
let text = e.target.value.toLowerCase();
console.log(text);
// SHOW SEARCH-RESULT DIV
if (text != '') {
searchValue.textContent = text;
searchResult.classList.remove('hidden');
} else {
searchResult.classList.add('hidden');
}
document.querySelectorAll('tr').forEach(row => {
let foundMatch = false;
row.querySelectorAll('td').forEach(col => {
let item = col.textContent.toLowerCase();
foundMatch = foundMatch || item.indexOf(text) > -1;
});
if (foundMatch) {
row.style.display = 'table-row';
} else {
row.style.display = 'none';
}
});
}
// ASSIGN EVENTS
initEvents();
<input id="search" />
<div class="phonelist">
<div id="search-result" class="hidden">
<p>Search results for <b id="search-value"></b>:</p>
</div>
<table class="striped">
<thead>
<tr>
<th>Phone</th>
<th>Fax</th>
<th>Room</th>
<th>Name</th>
<th>Title</th>
</tr>
</thead>
<tbody>
<tr>
<td>165</td>
<td>516</td>
<td>1.47</td>
<td>Johnathan Doe</td>
<td>Sales</td>
</tr>
<tr>
<td>443</td>
<td>516</td>
<td>1.47</td>
<td>Jane Dow</td>
<td>Development</td>
</tr>
</tbody>
</table>
</div>
I have a table that display employees information. I want to add a search to display all employees between spicfic number that i put in min textbox and max textbox. Here is my table code.
constructor() {
super();
this.state = {
employees: []
};
}
componentDidMount() {
this.employeesTracker = Tracker.autorun(() => {
const employees = Employees.find().fetch();
this.setState({ employees });
});
}
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>
</tr>
);
});
}
here where I render my app:
render() {
return (
<div>
<input type="text" id="min" name="min"/>
<input type="text" id="max" name="max"/>
<button onClick={this.ageFilter.bind(this)}>filter</button>
<table id="myTable">
<tbody>
{this.renderEmployeesListItems()}
</tbody>
</table>
</div>
);
}
instead of || (OR) you need to use && (AND) operator
...
var age = parseInt(td.innerHTML)
if (age > filterMin && age < filterMax) {
...
} else {
...
}
Also don't why are you accessing data through document.. This is not the way to do document update in react.
You could have this filtered Data separately as a state variable, filteredEmployee or such. Then the filter function will be,
ageFilter = () => {
const { employees, min, max } = this.state;
const filteredEmployees = employees.filter(employee => min < employee.age && employee.age < max);
this.setState({ filteredEmployees })
}
you'll need to add logic to clear the filter too, if you need that. use this filteredEmployees in renderEmployeesListItems function instead of employees
I am developing a site that contains a live search. This live search is used to search for contacts in a contact list (An HTML table). The contact list is a table with 2 columns, with each column containing a contact. The search works but, it returns the whole row, not just the matching columns.
Meaning that if I search for A in a table like the one in the snippet below; the search returns the whole row ( A || B ), not just A. Is there any way I could refine my function to search through columns instead of rows?
Hope I explained myself clearly.
<table>
<tr>
<td>A</td>
<td>B</td>
</tr>
<tr>
<td>C</td>
<td>D</td>
</tr>
</table>
Function
<script>
function myFunction() {
//variables
var input, filter, table, tr, td, i;
input = document.getElementById("search");
filter = input.value.toUpperCase();
table = document.getElementById("table");
tr = table.getElementsByTagName("tr");
for (i = 0; i < tr.length; i++) {
td = tr[i].getElementsByTagName("td")[0];
if (td)
{
if (td.innerHTML.toUpperCase().indexOf(filter) > -1) {
tr[i].style.display = "";
} else {
tr[i].style.display = "none";
}
}
}
}
</script>
I've modified your code to iterate all the td elements in your table. instead of hiding the cells that don't contain the filter text I've opted to apply an opacity to them. It makes it clearer in the example what is happening.
When doing work on key down, don't forget to debounce the event. See this post for a good introduction: https://davidwalsh.name/javascript-debounce-function
function myFunction() {
//variables
var
input = document.getElementById("search"),
filter = input.value.toUpperCase(),
table = document.querySelector('table'),
cells = table.querySelectorAll('td');
for (var i = 0; i < cells.length; i++) {
var cell = cells[i];
if (cell.innerHTML.toUpperCase().indexOf(filter) > -1) {
cell.classList.remove('no-match');
} else {
cell.classList.add('no-match');
}
}
}
const
form = document.getElementById('form'),
input = document.getElementById("search");
form.addEventListener('submit', onFormSubmit);
input.addEventListener('keyup', onKeyUp);
function onFormSubmit(event) {
event.preventDefault();
myFunction();
}
function onKeyUp(event) {
// Debounce this event in your code or you will run into performance issues.
myFunction();
}
.no-match {
opacity: .2;
}
<form id="form">
<label>
Filter text
<input type="text" id="search"/>
</label>
<button>Filter</button>
</form>
<table>
<tr>
<td>A</td>
<td>B</td>
</tr>
<tr>
<td>C</td>
<td>D</td>
</tr>
</table>
I have a MVC web app in which I show a table.
Some of my rows can have a similar id, on which I need to show only one checkbox for all those rows, and individual checkboxes for the rows which don't have a matching id. Something like below:
row1 and row2 have the same id, hence the checkbox is in between them (denoted by red checkbox).
row3, row4 have different ids, hence they need to have their individual checkboxes (denoted by green).
I know I need to play on the rowspan property, but I am unable to visualize how to get on it.
Below is the sample code:
[Route("Search")]
[HttpGet]
public async Task<IActionResult> Search()
{
//Some API call
return View("Search", model);
}
View Code:
<table id="tblsearch">
#if (Model.HasRecords)
{
var counter = 0;
<tbody>
#foreach (var item in Model.SearchResults)
{
<tr>
<td>
<input type="checkbox" id="Dummy_#counter" name="chkSearch" data-id="#item.Id"/>
<label for="Dummy_#counter"></label>
</td>
<td>#item.FullAddress</td>
<td>#item.Price</td>
<td>#item.OfficeName</td>
}
else
{
<tr><td>Data Not Found</td></tr>
}
</table>
I am trying to first hide all the checkboxes, then trying to match the id's in each row, and then if the ids of 2 rows are same, I am trying to increase the rowspan by 2.
js code:
function pageLoad()
{
var rowCount = $('#tblSearch >tbody >tr').length;
for(var i=0;i<rowCount-1;i++)
{
$('#Dummy_' + i).hide();
}
var searchArray= [];
for (var i = 0; i < rowCount - 1; i++) {
searchArray[i]= $('#tblSearch >tbody >tr')[i].attr('data-id');
}
}
Please guide how to proceed.
You should control the layout of the page in this instance from your View, please forgive my syntax as I primarily work in vbhtml these days.
Important things are to order your search results (in case they aren't already)
Remember and update the last processed Id.
<table id="tblsearch">
#if (Model.HasRecords)
{
var counter = 0;
var lastId = -1;
<tbody>
#foreach (var item in Model.SearchResults.OrderBy(x=>x.Id))
{
<tr>
#if(lastId!= item.Id){
<td rowspan="#(Model.SearchResults.Count(x=>x.Id == item.Id) > 0 ? Model.SearchResults.Count(x=>x.Id == item.Id) : 1 )">
<input type="checkbox" id="Dummy_#counter" name="chkSearch" data-id="#item.Id"/>
<label for="Dummy_#counter"></label>
</td>
}
<td>#item.FullAddress</td>
<td>#item.Price</td>
<td>#item.OfficeName</td>
#lastId = item.Id;
//I assume there was code snipped here...
}
else
{
<tr><td>Data Not Found</td></tr>
}
</table>
There is no need for any javascript. You can simply group your items by the Id property and conditionally render the checkbox column with a rowspan attribute if its the first item in the group.
<tbody>
#foreach (var group in Model.SearchResults.GroupBy(x => x.Id))
{
bool isFirstRow = true;
foreach (var item in group)
{
<tr>
#if (isFirstRow)
{
<td rowspan="#group.Count()">
#Html.CheckBox("chkSearch")
</td>
isFirstRow = false;
}
<td>#item.FullAddress</td>
<td>#item.Price</td>
<td>#item.OfficeName</td>
</tr>
}
}
</tbody>
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