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>
Related
I am trying to make a filter/search table and I am using the JavaScript from this site.
Here's the script:
function myFunction() {
// Declare variables
var input, filter, table, tr, td, i, txtValue;
input = document.getElementById("myInput");
filter = input.value.toUpperCase();
table = document.getElementById("myTable");
tr = table.getElementsByTagName("tr");
// Loop through all table rows, and hide those who don't match the search query
for (i = 0; i < tr.length; i++) {
td = tr[i].getElementsByTagName("td")[0];
if (td) {
txtValue = td.textContent || td.innerText;
if (txtValue.toUpperCase().indexOf(filter) > -1) {
tr[i].style.display = "";
} else {
tr[i].style.display = "none";
}
}
}
}
You can change which column to be used as the filter by changing the index in "td = tr[i].getElementsByTagName("td")[0];"
0 means the first column will be used, 1 means the second column, etc.
Using the same script, how can I use multiple columns to be the filter?
EDIT:
This is the tableinside my 'exampletable.php':
<input type="text" id="myInput" onKeyUp="myFunction()" placeholder="Search"
<table class="table table-bordered" id="dataTable" width="100%" cellspacing="0">
<?php
include 'connection.php'; //to connect to my database, "database"
$data = mysqli_query($con, "SELECT*FROM database;");
while($d = mysqli_fetch_array($data)){
?>
<table class="table table-bordered" id="myTable" width="100%" cellspacing="0">
<thead>
<tr>
<th class="text-center">Name</th>
<th class="text-center">Age</th>
<th class="text-center">Hobby</th>
</tr>
</thead>
<tr>
<td class="text-center"><?php echo $d['name']; ?></td>
<td><?php echo $d['age']; ?></td>
<td><?php echo $d['hobby']; ?></td>
</tr>
</table>
<?php } ?>
As you can see, I have the column "Name, Age, and Hobby" using the function inside the JavaScript, if the index is 0 (the index for the filter column) then the first column (Name) will be used as the filter.
For example, when I type "Catherine" in the search bar, then the page will only show the data with "Catherine" inside the Name column.
What I wanted to do is to use say both Name and Hobby column as the filter. So, say when I type "Cat" in the search bar, then the page will only show the data with "Cat" inside the Name and also Hobby column.
Adding another for loop to evaluate all tds (columns) will help you with that.
This might be helpful:
// This variable switch if the filter catch any result
var isset = false;
//This loop evalaute all rows
for (i = 0; i < tr.length; i++) {
//Here we search for all columns in this row
//note that we get rid of [0] at the end because we're looking for all
//tds, not just the first one
let tds = tr[i].getElementsByTagName("td");
//Here we evaluate each column for this row
for (let td of tds) {
//If td has a value
if (td) {
txtValue = td.textContent || td.innerText;
if (txtValue.toUpperCase().indexOf(filter) > -1) {
//If the filter catch any result, we show the row
isset = true;
}
}
}
//The decision of show or hide the row is made here because
//the loop has to evaluate all columns before decide if show or hide the entire row
if (isset) {
//If the filter catch any result, we show the row
tr[i].style.display = "";
} else {
//If the filter catch no result, we hide the row
tr[i].style.display = "none";
}
//This is to reset the variable for the next loop
isset = false;
}
Your entire function will be something like this:
function myFunction() {
// Declare variables
var input, filter, table, tr, td, i, txtValue;
// This variable switch if the filter catch any result
var isset = false;
input = document.getElementById("myInput");
filter = input.value.toUpperCase();
table = document.getElementById("myTable");
tr = table.getElementsByTagName("tr");
// This variable switch if the filter catch any result
var isset = false;
//This loop evalaute all rows
for (i = 0; i < tr.length; i++) {
//Here we search for all columns in this row
//note that we get rid of [0] at the end because we're looking for all
//tds, not just the first one
let tds = tr[i].getElementsByTagName("td");
//Here we evaluate each column for this row
for (let td of tds) {
//If td has a value
if (td) {
txtValue = td.textContent || td.innerText;
if (txtValue.toUpperCase().indexOf(filter) > -1) {
//If the filter catch any result, we show the row
isset = true;
}
}
}
//The decision of show or hide the row is made here because
//the loop has to evaluate all columns before decide if show or hide the entire row
if (isset) {
//If the filter catch any result, we show the row
tr[i].style.display = "";
} else {
//If the filter catch no result, we hide the row
tr[i].style.display = "none";
}
//This is to reset the variable for the next loop
isset = false;
}
}
I have tested the code with this script
<input type="text" id="myInput" onKeyUp="myFunction()" placeholder="Search">
<table class="table table-bordered" width="100%" cellspacing="0">
<thead>
<tr>
<th class="text-center">Name</th>
<th class="text-center">Age</th>
<th class="text-center">Hobby</th>
</tr>
</thead>
<tbody id="myTable">
<tr>
<td class="text-center">Alejandro</td>
<td>12</td>
<td>Football</td>
</tr>
<tr>
<td class="text-center">Peralta</td>
<td>12</td>
<td>Alejandro</td>
</tr>
<tr>
<td class="text-center">Casemiro</td>
<td>12</td>
<td>Soccer</td>
</tr>
<tr>
<td class="text-center">Moreno</td>
<td>12</td>
<td>Tenis</td>
</tr>
<tr>
<td class="text-center">Alejandro</td>
<td>12</td>
<td>Moreno</td>
</tr>
</tbody>
</table>
<script>
function myFunction() {
// Declare variables
var input, filter, table, tr, td, i, txtValue;
// This variable switch if the filter catch any result
var isset = false;
input = document.getElementById("myInput");
filter = input.value.toUpperCase();
table = document.getElementById("myTable");
tr = table.getElementsByTagName("tr");
// This variable switch if the filter catch any result
var isset = false;
//This loop evalaute all rows
for (i = 0; i < tr.length; i++) {
//Here we search for all columns in this row
//note that we get rid of [0] at the end because we're looking for all
//tds, not just the first one
let tds = tr[i].getElementsByTagName("td");
//Here we evaluate each column for this row
for (let td of tds) {
//If td has a value
if (td) {
txtValue = td.textContent || td.innerText;
if (txtValue.toUpperCase().indexOf(filter) > -1) {
//If the filter catch any result, we show the row
isset = true;
}
}
}
//The decision of show or hide the row is made here because
//the loop has to evaluate all columns before decide if show or hide the entire row
if (isset) {
//If the filter catch any result, we show the row
tr[i].style.display = "";
} else {
//If the filter catch no result, we hide the row
tr[i].style.display = "none";
}
//This is to reset the variable for the next loop
isset = false;
}
}
</script>
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>
HTML
<input id="myInput" type="text" onkeyup="ContactsearchFX()"
placeholder="Search Titles">
*</p>
<table id="myTable" style="width: 100%" class="style1">
JAVASCRIPT
window.onload = function() {
var rows = document.querySelectorAll('tr');
for (var i = 0; i < rows.length; i++) {
rows[i].style.display = 'none';
}
}
function ContactsearchFX() {
var input, filter, table, tr, td, i;
input = document.getElementById("myInput");
filter = input.value.toUpperCase();
table = document.getElementById("myTable");
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";
}
}
}
var rows = document.querySelectorAll('tr');
if (input.value.length == 0) {
for (var i = 0; i < rows.length; i++) {
rows[i].style.display = 'none';
}
}
}
Have been trying to implement https://markjs.io/ among other various highlight solutions with no fix or solution.
Hope there is enough info to go on. Please do ask if you need more details.
Setup
Judging by the code you've shared, you're probably not using es6 or a js bundler to setup your project. This means no imports and the library <scrpit>s go inside your index.html
Steps
Verify you are loading the script correctly
Clean up the code
separate it to chunks to better understand what's failing
Update the code with the highlight functionality described by the "getting started" of the package
Notes
With the mark.js library it would be best to clean up the mark (.unmark()) before applying it again. because it will add more elements on each change. That's why we keep a single instance of mark per context (context is the table element) instead of creating a new instance inside the highlightChanges function
The snippet below is not the optimal implementation of this functionality but it can serve for a starting point. It was based on the original code
Snippet with added highlight
var input, table, rows, markInstance;
window.onload = function init() {
input = document.getElementById("myInput");
table = document.getElementById('myTable');
rows = table.querySelectorAll('tr');
markInstance = new Mark(table);
clear();
}
function ContactsearchFX() {
clear();
if (input.value.length == 0) return;
filterRows(input.value);
highlightMatches(input.value);
}
function clear() {
markInstance.unmark();
for (var i = 0; i < rows.length; i++) {
rows[i].style.display = 'none';
}
}
function filterRows(text) {
var part = text.toUpperCase();
for (i = 0; i < rows.length; i++) {
var row = rows[i];
var td = row.getElementsByTagName("td")[0];
if (td) {
// part = GI
// innerHtml = <MARK DATA-MARKJS="TRUE">G</MARK>ITHUB (wont match)
// innerText = GITHUB (will match)
var content = td.innerText.toUpperCase();
if (content.includes(part)) {
row.style.display = "";
}
}
}
}
function highlightMatches(text) {
markInstance.mark(text);
}
.input-wrap {
margin-bottom: 12px;
}
.hints {
display: none;
margin-top: 12px;
}
#myInput:invalid~.hints {
display: block;
}
mark {
background: orange;
font-weight: bold;
color: black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/mark.js/8.11.1/mark.js"></script>
<div class="input-wrap">
<label>
Search Titles:
<input id="myInput" type="text" required
onkeyup="ContactsearchFX()"
placeholder="Search Titles" />
<span class="hints">
Hints: type "git", "bit", "forge" ...
</span>
</label>
</div>
<table id="myTable" style="width: 100%" class="style1">
<tr>
<th>Title</th>
<th>Description</th>
</tr>
<tr>
<td>Github</td>
<td>Is the HUB</td>
</tr>
<tr>
<td>Gitlab</td>
<td>Have nice CI</td>
</tr>
<tr>
<td>Bitbucket</td>
<td>Has Jira integration (Duh)</td>
</tr>
<tr>
<td>SourceForge</td>
<td>Open source projects live here. It is said</td>
</tr>
</table>
And a more appropriate version for production
Quick look through the examples and the API for the options parameter of the mark() function review that we can substitute most of the logic with the hooks from the options
The input event handler is debounceed (function from from lodash _.debounce) this way the filtering will start when the user stops typing (for more than 250ms)
Content is again .unmark()ed at the start - the different part is that we use the options parameter with a done callback to continue with highlightMatches when unmark finishes
The options parameter have an each prop which is a callback for each element that matches. Using this callback we can search for it's parent that we know must be a row, and we add a class show so the row can become visible.
There's also a noMatch prop which will be called (back) if the texts matches nothing so we can show some hint to maybe try a different text
The options parameter hold other useful props that can configure the matching like, wildcards, case sensitive matching and so on...
var input, table, rows, noMatches, markInstance;
window.onload = function init() {
input = document.getElementById('myInput');
noMatches = document.getElementById('noMatches');
table = document.getElementById('myTable');
rows = table.querySelectorAll('tr');
markInstance = new Mark(table);
input.addEventListener('keyup', _.debounce(ContactsearchFX, 250));
}
function ContactsearchFX() {
resetContent();
markInstance.unmark({
done: highlightMatches
});
}
function resetContent() {
noMatches.textContent = '';
rows.forEach(function(row) {
row.classList.remove('show');
});
}
function highlightMatches() {
markInstance.mark(input.value, {
each: showParantRow,
noMatch: onNoMatches,
})
}
function showParantRow(element) {
var row = element.closest('tr');
row.classList.add('show');
}
function onNoMatches(text) {
noMatches.textContent = 'No records match: "' + text + '"';
};
.input-wrap {
margin-bottom: 12px;
}
#myInput:invalid~.hints {
display: block;
}
#noMatches:empty, #noMatches:empty + .hints {
display: none;
}
.filterTable tr {
display: none;
}
.filterTable .show {
display: table-row;
}
mark {
background: orange;
font-weight: bold;
color: black;
}
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.11/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mark.js/8.11.1/mark.min.js"></script>
<div class="input-wrap">
<label>
Search Titles:
<input id="myInput" type="text" required
placeholder="Search Titles" />
</label>
</div>
<div class="hintsWrap">
<p id="noMatches"></p>
<p class="hints">
Hints: type "git", "bit", "forge" ...
</p>
</div>
<table id="myTable" style="width: 100%" class="filterTable">
<tr>
<td>Github</td>
<td>Is the HUB</td>
</tr>
<tr>
<td>Gitlab</td>
<td>Have nice CI</td>
</tr>
<tr>
<td>Bitbucket</td>
<td>Has Jira integration (Duh)</td>
</tr>
<tr>
<td>SourceForge</td>
<td>Open source projects live here. It is said</td>
</tr>
</table>
Hi I am currently working on datatables and I wrote a code when a table row has been click there is a specific function that will be called.
The problem is when I click the table row for the table header it also does the function (IT MUST NOT).
Here is a snippet of my code when I click the table row there will be a function called.
$('#response-contact-container').on('click', 'tr', function(){
var table = $('#response-contact-container').DataTable();
var data = table.row(this).data();
$('#edit-contact-settings').modal('hide');
$('.modal-backdrop').remove();
var community_contacts = ['c_id','firstname','lastname','prefix','office','sitename','number','rel'];
var employee_contacts = ['eid','firstname','lastname','nickname','birthday','email','numbers','grouptags'];
var counter = 1;
var container = document.getElementById("contact-settings-wrapper");
while (container.hasChildNodes()) {
container.removeChild(container.lastChild);
}
for(var i=1; i< data.length; i++) {
var label = document.createElement("label");
var input = document.createElement("input");
if (data[0].charAt(0)=="c") {
var t = document.createTextNode(community_contacts[i].capitalize());
label.appendChild(t);
container.appendChild(label);
input.type = "text";
input.name = community_contacts[i];
input.className = "form-control";
input.value = data[i];
container.appendChild(input);
container.appendChild(document.createElement("br"));
} else {
var t = document.createTextNode(employee_contacts[i].capitalize());
label.appendChild(t);
container.appendChild(label);
input.type = "text";
input.name = community_contacts[i];
input.className = "form-control";
input.value = data[i];
container.appendChild(input);
container.appendChild(document.createElement("br"));
}
console.log(data[i]);
counter++;
}
$('#edit-contact').modal('show');
});
I want the header be able to not perform the function under the onclick in jQuery, but I don't want it to disable because I want to use the datatables built in feature that sort the cells.
Thanks
I interpret your question to be the following: when I click on a table row, can I call a click handler if and only if that row has <td> elements, not <th> elements, i.e. only when it is a non-header row?
To accomplish this, use the jQuery .has() selector. Specifically, in your code, instead of
...on('click', 'tr', ...
use
...on('click', 'tr:has(td)', ...
In the example below, the selected element's currentTarget result shows that it is indeed the row, and not the cell, that is the click's current target. Moreover, it calls the handler function (or not) as follows:
when you click on a normal data row, i.e. a row with all <td> cells
not when you click on a normal header row, i.e. a row with all <th> cells
when you click on a mal-formed row that (inappropriately) contains both <th> and <td> rows
You should never form a table row in the last way. However, that example show how the selector is working. Specifically, even if you click on the <th> cell in that row, the click handler will still fire because you are clicking on a cell in a row that contains at least one <td> cell. That should never be what you want, but it illustrates what's going on in terms of what is actually being selected.
var counter = 0;
$('#myTable').on('click', 'tr:has(td)', function(e) {
console.log(e.currentTarget);
});
$('#myTable').on('click', 'tr', function(e) {
counter += 1;
console.log(counter + ' clicks');
});
table {
border-collapse: collapse;
}
td, th {
border: black solid 1px;
padding: 0.5em;
text-align: left;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table id="myTable">
<tr>
<th>normal header row - cell #1 - <th></th>
<th>normal header row - cell #2 - <th></th>
</tr>
<tr>
<th>malformed (mixed) row - cell #1 - <th></th>
<td>malformed (mixed) row - cell #2 - <td></td>
</tr>
<tr>
<td>normal data row - cell #1 - <td></td>
<td>normal data row - cell #2 - <td></td>
</tr>
</table>
Am working on the table, my aim is to display rows orderly based on its sum of columns. I mean, highest valued rows should be displayed first and then, second highest values, then, go on.. Have tried and could not make it. Just give me an idea, and that is good enough for me. Am not able to come to any idea on how to reshuffle the rows based on its sum values. Any idea on this?
<div id="na_1" style="border: 1px solid gray;width: 450px;padding:10px;">
<form>
<input type="radio" value="All" onclick="Turnthis();"/>All
<input type="radio" value="Top15" onclick="TurnOutthis();"/>Top5
</form>
<table id="bt_01" border="1" width="100%">
<thead>
<tr><td>head1</td><td>head2</td><td>head3</td><td>head4</td><td>head5</td><td>head6</td><td>head7</td></tr>
</thead>
<tbody>
<tr><td>Subject1</td><td>501</td><td>501</td><td>501</td><td>550</td><td>560</td><td>570</td></tr>
<tr><td>Subject2</td><td>620</td><td>640</td><td>605</td><td>650</td><td>600</td><td>604</td></tr>
<tr><td>Subject3</td><td>730</td><td>730</td><td>740</td><td>750</td><td>760</td><td>790</td></tr>
<tr><td>Subject4</td><td>700</td><td>701</td><td>700</td><td>702</td><td>700</td><td>703</td></tr>
<tr><td>Subject5</td><td>220</td><td>201</td><td>202</td><td>222</td><td>210</td><td>203</td></tr>
<tr><td>Subject6</td><td>200</td><td>201</td><td>200</td><td>202</td><td>200</td><td>203</td></tr>
<tr><td>Subject7</td><td>200</td><td>201</td><td>200</td><td>202</td><td>200</td><td>203</td></tr>
<tr><td>Subject8</td><td>200</td><td>201</td><td>200</td><td>202</td><td>200</td><td>203</td></tr>
<tr><td>Total</td><td>202</td><td>201</td><td>200</td><td>202</td><td>200</td><td>203</td></tr>
</tbody>
</table>
</div>
After I pressed the 'Top5' button, then, the table should be displayed as follows[sample]
<div id="na_1" style="border: 1px solid gray;width: 450px;padding:10px;">
<form>
<input type="radio" value="All" onclick="Turnthis();"/>All
<input type="radio" value="Top15" onclick="TurnOutthis();"/>Top5
</form>
<table id="bt_01" border="1" width="100%">
<thead>
<tr><td>head1</td><td>head2</td><td>head3</td><td>head4</td><td>head5</td><td>head6</td><td>head7</td></tr>
</thead>
<tbody>
<tr><td>Subject3</td><td>730</td><td>730</td><td>740</td><td>750</td><td>760</td><td>790</td></tr>
<tr><td>Subject4</td><td>700</td><td>701</td><td>700</td><td>702</td><td>700</td><td>703</td></tr>
<tr><td>Subject2</td><td>620</td><td>640</td><td>605</td><td>650</td><td>600</td><td>604</td></tr>
<tr><td>Subject1</td><td>501</td><td>501</td><td>501</td><td>550</td><td>560</td><td>570</td></tr>
<tr><td>Subject5</td><td>220</td><td>201</td><td>202</td><td>222</td><td>210</td><td>203</td></tr>
</tbody>
</table></div>
Hope this makes sense. For your kind information, i except code only in javascript not in jquery.
The trick is to get the rows you want and put them into an array, then sort the array and put the rows into the table in the right order. The following will sort the rows based on the cell values, so if the first cell value is equal, it uses the second, and so on. The sort may not be stable since it just uses the built–in Array.prototype.sort. If you want to to be stable, that is pretty simple (but generally not required).
Firstly, get the relevant tBody element and rows:
var tBody = document.getElementById('bt_01').tBodies[0];
var rows = tBody.rows;
rows is a live collection so you want to build an array form it (also helps with sorting). The following works fine in modern browsers:
var rowArr = Array.prototype.slice.call(rows);
But will not work in IE 8 and lower, you'll need to use a for loop (just one extra line of code):
var rowArr = [];
for (var i=0, iLen=rows.length; i<iLen; i++) rowArr[i] = rows[i];
To keep the last row as the last, just keep a reference:
var lastRow = rowArr[rowArr.length - 1];
Now sort based on the cell values from the second cell onward:
rowArr.sort(function(a, b) {
var aVal, bVal;
for (var i = 1, iLen = a.cells.length; i<iLen; i++) {
aVal = a.cells[i].textContent || a.cells[i].innerText;
bVal = b.cells[i].textContent || b.cells[i].innerText;
if (aVal != bVal) return aVal - bVal;
}
return 0;
});
Now put the rows into order:
for (var j=0, jLen=rowArr.length; j<jLen; j++) {
tBody.appendChild(rowArr[j]);
}
and finally, put the bottom row back at the bottom:
tBody.appendChild(lastRow);
and you're done. And it's shorter than the offered jQuery alternative (and likely a lot faster). ;-)
It would be best to put footer row in separate tFoot section as you've done with the header.
Edit
If you want to sort based on the sum of the values in each row, the sort part becomes:
rowArr.sort(function(a, b) {
var aSum = 0, bSum = 0;
for (var i = 1, iLen = a.cells.length; i<iLen; i++) {
aSum += parseFloat(a.cells[i].textContent || a.cells[i].innerText);
bSum += parseFloat(b.cells[i].textContent || b.cells[i].innerText);
}
return aSum - bSum;
});
I assume you want the "total" row at the end of the table after sort.Otherwise code can be 10-12 lines shorter.
i come up with this solution.I've commented out every portion for convenience .With a little help form getElementsByTagName,removeChild,createElement,and appendChild methods you can achieve what you are trying to do.It is really simple.I advice you go through it.The main thing is create an array of objects which hold three properties.And the properties are the actual tr element,its sum and the first td element which holds subject1,2,3....and Total string.Then sort them down with Array.prototype.sort() then add them serially to your tbody element
var radio=document.getElementById('radio');
if(radio.checked == true){ //check for radio element is checked or not
radio.checked=false;
}
var table=document.getElementById('bt_01');
var serialTdArr=[]; //array which will hold the array of objects
var storageForTotal='';// store the tr element that has 'Total' as value of its first td element
function Turnthis(e){
var tbody=document.getElementsByTagName('tbody')[0]; //get the current tbody element
var tr=tbody.getElementsByTagName('tr'); //get all tr elements inside tbody
for(i=0;i<tr.length;i++){ // loop over the tr elements
var sum=0;
var td=tr[i].getElementsByTagName('td'); //get all td element inside every tr element
for(j=1;j<td.length;j++){
sum+=parseInt(td[j].innerHTML); // sum them up
}
var firstTd=td[0].innerHTML; //save every subject1,subject2,3,...and Total string to a seperate variable
serialTdArr.push({index:tr[i],sum:sum,pointer:firstTd}); // an object which holds the tr element,its sum and its first td element value is pushed inside serialTdArr
}
var sortedArr=serialTdArr.sort(function(a,b){ // sort them down from higher to lower
if(a.sum < b.sum){
return 1;
}
if(a.sum > b.sum){
return -1;
}
return 0;
});
table.removeChild(tbody); // remove the current tbody
var newTbody=document.createElement('tbody'); // create a new tbody
table.appendChild(newTbody); // append it to table
for(i=0;i<sortedArr.length;i++){
if((sortedArr[i].pointer) != 'Total'){ // if it has no "Total" string then add them to tbody
newTbody.appendChild(sortedArr[i].index); // add to tbody
}else{
storageForTotal=sortedArr[i].index; // 'Total' row will be seperated and stored in a variable to be added later at the end of the table
}
}
newTbody.appendChild(storageForTotal); // add the tr which 'Total' to the last of td element
}
<div id="na_1" style="border: 1px solid gray;width: 450px;padding:10px;">
<form>
<input type="radio" id='radio' value="Sort Them All" onchange="Turnthis();"/>Sort Them All
</form>
<table id="bt_01" border="1" width="100%">
<thead>
<tr><td>head1</td><td>head2</td><td>head3</td><td>head4</td><td>head5</td><td>head6</td><td>head7</td></tr>
</thead>
<tbody>
<tr><td>Subject1</td><td>501</td><td>501</td><td>501</td><td>550</td><td>560</td><td>570</td></tr>
<tr><td>Subject2</td><td>620</td><td>640</td><td>605</td><td>650</td><td>600</td><td>604</td></tr>
<tr><td>Subject3</td><td>730</td><td>730</td><td>740</td><td>750</td><td>760</td><td>790</td></tr>
<tr><td>Subject4</td><td>700</td><td>701</td><td>700</td><td>702</td><td>700</td><td>703</td></tr>
<tr><td>Subject5</td><td>220</td><td>201</td><td>202</td><td>222</td><td>210</td><td>203</td></tr>
<tr><td>Subject6</td><td>200</td><td>201</td><td>200</td><td>202</td><td>200</td><td>203</td></tr>
<tr><td>Subject7</td><td>200</td><td>201</td><td>200</td><td>202</td><td>200</td><td>203</td></tr>
<tr><td>Subject8</td><td>200</td><td>201</td><td>200</td><td>202</td><td>200</td><td>203</td></tr>
<tr><td>Total</td><td>202</td><td>201</td><td>200</td><td>202</td><td>200</td><td>203</td></tr>
</tbody>
This code works and tested on Chrome 30
Non-jQuery version (updated question):
<html>
<body>
<div id="na_1" style="border: 1px solid gray;width: 450px;padding:10px;">
<table id="bt_01" border="1" width="100%">
<thead>
<tr><td>head1</td><td>head2</td><td>head3</td><td>head4</td><td>head5</td><td>head6</td><td>head7</td></tr>
</thead>
<tbody>
<tr><td>Subject1</td><td>501</td><td>501</td><td>501</td><td>550</td><td>560</td><td>570</td></tr>
<tr><td>Subject2</td><td>620</td><td>640</td><td>605</td><td>650</td><td>600</td><td>604</td></tr>
<tr><td>Subject3</td><td>730</td><td>730</td><td>740</td><td>750</td><td>760</td><td>790</td></tr>
<tr><td>Subject4</td><td>700</td><td>701</td><td>700</td><td>702</td><td>700</td><td>703</td></tr>
<tr><td>Subject5</td><td>220</td><td>201</td><td>202</td><td>222</td><td>210</td><td>203</td></tr>
<tr><td>Subject6</td><td>200</td><td>201</td><td>200</td><td>202</td><td>200</td><td>203</td></tr>
<tr><td>Subject7</td><td>200</td><td>201</td><td>200</td><td>202</td><td>200</td><td>203</td></tr>
<tr><td>Subject8</td><td>200</td><td>201</td><td>200</td><td>202</td><td>200</td><td>203</td></tr>
</tbody>
<tfoot>
<tr><td>Total</td><td>202</td><td>201</td><td>200</td><td>202</td><td>200</td><td>203</td></tr>
</tfoot>
</table>
</div>
<input type='button' onclick='sort()' value='sort by sum' />
<script>
function sort() {
var t = document.getElementById('bt_01').children[1]; // tbody
var arr = [];
// find all tr
var trs = t.children;
for(;trs.length;) {
var tr = trs[0];
// find all td and calculate the sum
var sum = 0;
var tds = tr.children;
for(var y in tds) {
var td = tds[y];
var v = +td.textContent;
if (!isNaN(v)) sum += v;
};
// move them to arr
tr = tr.parentNode.removeChild(tr)
arr.push({sum: sum,tr: tr});
};
// sort it
arr.sort(function(a, b) {
if (a.sum < b.sum) return 1;
if (a.sum > b.sum) return -1;
return 0;
});
// attach back the tr in order
for (var z in arr) t.appendChild(arr[z].tr)
}
</script>
</body>
</html>
jQuery version:
<html>
<body>
<script src="http://code.jquery.com/jquery-1.11.2.min.js"></script>
<div id="na_1" style="border: 1px solid gray;width: 450px;padding:10px;">
<table id="bt_01" border="1" width="100%">
<thead>
<tr><td>head1</td><td>head2</td><td>head3</td><td>head4</td><td>head5</td><td>head6</td><td>head7</td></tr>
</thead>
<tbody>
<tr><td>Subject1</td><td>501</td><td>501</td><td>501</td><td>550</td><td>560</td><td>570</td></tr>
<tr><td>Subject2</td><td>620</td><td>640</td><td>605</td><td>650</td><td>600</td><td>604</td></tr>
<tr><td>Subject3</td><td>730</td><td>730</td><td>740</td><td>750</td><td>760</td><td>790</td></tr>
<tr><td>Subject4</td><td>700</td><td>701</td><td>700</td><td>702</td><td>700</td><td>703</td></tr>
<tr><td>Subject5</td><td>220</td><td>201</td><td>202</td><td>222</td><td>210</td><td>203</td></tr>
<tr><td>Subject6</td><td>200</td><td>201</td><td>200</td><td>202</td><td>200</td><td>203</td></tr>
<tr><td>Subject7</td><td>200</td><td>201</td><td>200</td><td>202</td><td>200</td><td>203</td></tr>
<tr><td>Subject8</td><td>200</td><td>201</td><td>200</td><td>202</td><td>200</td><td>203</td></tr>
</tbody>
<tfoot>
<tr><td>Total</td><td>202</td><td>201</td><td>200</td><td>202</td><td>200</td><td>203</td></tr>
</tfoot>
</table>
</div>
<input type='button' onclick='sort()' value='sort by sum' />
<script>
function sort() {
var t = $('#bt_01').find('tbody');
var arr = [];
// find all tr
t.find('tr').each(function(idx, tr) {
tr = $(tr);
// find all td and calculate the sum
var sum = 0;
tr.find('td').each(function(idx, td) {
td = $(td);
var v = +td.text();
if (!isNaN(v)) sum += v;
});
// move them to arr
arr.push({sum: sum,tr: tr.detach()});
});
// sort it
arr.sort(function(a, b) {
if (a.sum < b.sum) return 1;
if (a.sum > b.sum) return -1;
return 0;
});
// attach back the tr in order
for (var z in arr) t.append(arr[z].tr)
}
</script>
</body>
</html>