Textual filter very slow - javascript

I have a table with 10 to 1000 lines
I have a text field to filter the table.
I used this code to filter the display of each line depending on the filter:
$("#cs_name").keyup(function () {
$(".cs_line").each(function () {
if (!$(this).data('name').toLowerCase().includes($("#cs_name").val().toLowerCase())) {
$(this).hide("fast");
}
else {
$(this).show("fast");
}
});
});
It works fine if I have 10 or 20 lines, but when I have 1000 lines time between each letter is sooo long. up to 5 seconds sometimes.
Maybe there is a way to make it mor efficient.
For information, pagination is not an option, for some client reasons, I cannot have multiple pages.
Thanks a lot for your help

Start with caching $(this). The creation of a $(this) object requires time, so create one and cache it : let $this = $(this), and reuse $this.
Same thing for $(".cs_line") and $("#cs_name").val().toLowerCase(). Each of these operations require jQuery to access the DOM and create full jQuery objects, and you're doing that thousands of times.
Also, throttle your keyup, don't execute the filter every time a key is pressed.
Optimized code :
const $lines = $(".cs_line")
const name = $("#cs_name").val().toLowerCase()
let typeTimeout = null
const applyFilter = () => {
$lines.each(() => {
let $this = $(this);
if ($this.data('name').toLowerCase().includes(name)) {
$this.show("fast"); // Or just .show(), much faster
} else {
$this.hide("fast"); // Or just .hide(), much faster
}
});
}
const throttleFilter = () => {
clearTimeout(typeTimeout);
typeTimeout = setTimeout(applyfilter, 400) // Will apply the filter if no key is pressed for 400ms
}
$("#cs_name").keyup(throttleFilter);

I think the better option for you is to use Jquery Datatable. It offers really fast search on multiple columns and other ready-to-use functionality.
UPDATE:
Without using JQuery Datatable you can increase performance storing cs_line elements and not retrieving everytime with query selector.

This is how I would filter the data based on a data attribute so it runs quickly.
/* Just filling table with data */
var trs = $(Array.from({length: 1000}, () => Math.floor(Math.random() * 1000)).map( n => `<tr data-name="${n}"><td>${n}</td></tr>`).join(""))
var tbody = $("#tbod").append(trs)
/* end of filling table */
var lastFilter = "", //holds last typed search
rows = tbody.find("tr"); // table rows
/* Normalize the data to start so you are not doing it on every check */
rows.attr("data-name", function (index, value) {
return value.toLowerCase()
})
$("#filter").on("keyup", function () {
var currentFilter = this.value.toLowerCase(); //get what was typed and normalize it
//If the last filter does not match current than we need to reset table rows
if (!lastFilter || currentFilter.substring(0,currentFilter.length-1) !== lastFilter) {
rows.removeClass("hidden");
}
//store what the filter was for next iteration
lastFilter = currentFilter;
//If there is text, filter it
if (currentFilter) {
//Use CSS selector to find it ^ is start, use * for anywhere
trs.not('tr[data-name^="' + currentFilter + '"]').addClass("hidden")
}
});
.hidden { display:none}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="text" id="filter" />
<table>
<tbody id="tbod">
</tbody>
</table>

Related

How to give a unique id for each cell when adding custom columns?

I wrote following code to add a custom column to my table. but i want to add a unique id to each cell in those columns. the format should be a(column no)(cell no>)
ex :- for the column no 4 :- a41, a42, a43, ........
So please can anyone tell me how to do that. Thank You!
$(document).ready(function ()
{
var myform = $('#myform'),
iter = 4;
$('#btnAddCol').click(function () {
myform.find('tr').each(function(){
var trow = $(this);
var colName = $("#txtText").val();
if (colName!="")
{
if(trow.index() === 0){
//trow.append('<td>'+iter+'</td>');
$(this).find('td').eq(5).after('<td>'+colName+iter+'</td>');
}else{
//trow.append('<td><input type="text" name="al'+iter+'"/></td>');
$(this).find('td').eq(5).after('<td><input type="text" id="a'+iter+'" name="a'+iter+'"/></td>');
}
}
});
iter += 1;
});
});
You seem to have code that's modifying the contents of the table (adding cells), which argues fairly strongly against adding an id to every cell, or at least one based on its row/column position, as you have to change them when you add cells to the table.
But if you really want to do that, after your modifications, run a nested loop and assign the ids using the indexes passed into each, overwriting any previous id they may have had:
myform.find("tr").each(function(row) {
$(this).find("td").each(function(col) {
this.id = "a" + row + col;
});
});
(Note that this assumes no nested tables.)
try this
if(trow.index() === 0){
//trow.append('<td>'+iter+'</td>');
$(this).find('td').eq(5).after('<td id="a'+column_no+cell_no+'">'+colName+iter+'</td>');
}else{
//trow.append('<td><input type="text" name="al'+iter+'"/></td>');
$(this).find('td').eq(5).after('<td id="a'+column_no+cell_no+'"><input type="text" id="a'+iter+'" name="a'+iter+'"/></td>');
}
you just have to define and iterate the column_no and cell_no variable
When all other cells are numbered consistently (for example using a data-attribute with value rXcX), you could use something like:
function addColumn(){
$('table tr').each(
function(i, row) {
var nwcell = $('<td>'), previdx;
$(row).append(nwcell);
previdx = nwcell.prev('td').attr('data-cellindex');
nwcell.attr('data-cellindex',
previdx.substr(0,previdx.indexOf('c')+1)
+ (+previdx.substr(-previdx.indexOf('c'))+1));
});
}
Worked out in this jsFiddle

How to properly filter a list by multiple criteria with jQuery?

I would like to perform the following actions on my DOM:
Filter a list via multiple controls (combined):
Checkboxes
Select boxes
Free text input (like e.g http://vdw.github.io/HideSeek/)
So for example, the user can select a city from the select box, which filters the displayed items. When typing into the input field, the filter from the select is still in place, further filtering the displayed items by the entered text.
I normally write something by hand to combine multiple choices from different select boxes, but it's not ideal. Also, for a "free text" filter, I have always used jQuery-Plugins, and they tend to reset the selection when you start typing.
With tables, I use the datatables plugin, which brings along multiple filters. It's extremely feature-rich, but also quite heavy - and designed for tables, not for any type of lists.
What are the general recommendations / outlines on how to achieve this?
PS: Here's how I do it now. a) it's extremely proprietary and b) I haven't managed to combine it with a text filter yet:
function showItems(selectedCanton,showTypeOne,showTypeTwo){
var typeOneSelector = '';
var typeTwoSelector = '';
if (selectedCanton=='all'){
var cantonSelector = '';
}
else {
var cantonSelector = '.list-item[data-canton="'+selectedCanton+'"]';
}
if (showTypeOne){
if (showTypeTwo){
selector = cantonSelector;
//selector = cantonSelector+'[data-type="one"],'+cantonSelector+'[data-type="two"]';
}
else {
selector = cantonSelector+'[data-type="one"]';
}
}
else if (showTypeTwo){
selector = cantonSelector+'[data-type="two"]';
}
$('.list-item').hide();
console.log(selector);
$(selector).show();
}
$(document).ready(function($){
$(".filter-select").change(function() {
var selectedCanton = $("#canton").val();
var showTypeOne = $("#type-one").prop('checked');
var showTypeTwo = $("#type-two").prop('checked');
showItems(selectedCanton,showTypeOne,showTypeTwo);
});
});
you can use filter function of jquery.
try something like
$('.list-item').hide();
$('.list-item').filter(function (index, e) {
var condition = true;
var el = $(e);
if(showTypeOne)
{
condition = condition && (el.data("type") === "one");
}
if(showTypeTwo)
{
condition = condition && (el.data("type") === "two");
}
if(selectedCanton!='all')
{
condition = condition && (el.data("canton") === selectedCanton);
}
return condition;
})
.show();
you could add text filter easly that way..
working sample : http://jsfiddle.net/CufMp/1/

jQuery iteration though all items of a class regardless of their position in the DOM

i'm building a webpage where many spanĀ­ needs to be transitioned from one class to another to create a bg-color fadein effect. Distribution of elements of same classes is mixed through the page, but they are all grouped under common classes.
I want to create a behavior that does the following: when you click any elements of class-n, the other elements of that class transitions, with the clicked element acting as the starting point.
This is mostly figured out, thanks to some help on SO; see the jsfiddle.
$(".div").click(function () {
var itemClasses = this.classList;
var itemThread = itemClasses[1];
colorThread($(this), itemThread);
console.log(itemThread);
});
function colorThread($div, tId) {
tId = '.'+tId;
$div.toggleClass('div-clicked');
setTimeout(function () {
(function togglePrev($div) {
$div.toggleClass('div-clicked');
setTimeout(function () {
togglePrev($div.prev(tId));
}, 100);
})($div.prev(tId));
(function toggleNext($div) {
$div.toggleClass('div-clicked');
setTimeout(function () {
toggleNext($div.next(tId));
}, 100);
})($div.next(tId));
}, 100);
}
However, I am still struggling around a particular issue: I don't want the transition to stop if if encounter different class, I just want it not to toggle and keep iterating. If the jsfiddle, that would translate in all of the same color div to transition, regardless of their placement in the DOM tree.
In my togglePrev/toggleNext function, I have tried something along
if($div.hasClass(".classToTransition"))
{
$div.toggleClass(".div-clicked");
}
but couldn't get it to work properly (it doesn't ieterate to the next elements). There is something that I can't seem to grasp in the structure of that conditional. Anyone has a lead?
You really did manage to complicate something that should be pretty simple ?
$(".div").click(function () {
var coll = $('.'+this.className.replace(/(div-clicked|div)/g, '').trim()),
idx = coll.index($(this).toggleClass('div-clicked'));
$.each(coll, function(i) {
setTimeout(function() {
if (idx + i <= coll.length) coll.eq(idx + i).toggleClass('div-clicked');
if (idx - i >= 0) coll.eq(idx - i).toggleClass('div-clicked');
},i*200);
});
});
FIDDLE
It gets all the elements with the same class as the one currently clicked, and the index of the currently clicked, and then just adds and subtract 1 to the current index to get the next and previous elements. The checks are to make sure it stops when it reaches the end.
I don't want the transition to stop if if encounter different class, I just want it not to toggle and keep iterating
You might want to use nextAll(tId).first()/prevAll(tId).first() to select the next to-be-toggled element: http://jsfiddle.net/35uNW/4/. .next() does only look at the next sibling, and if that doesn't match your tId selector, no element will be selected.
If you want to iterate the different-classed elements so that you wait for each one, but don't want to toggle it, you can use your if-condition but you must remove the tId selector from the next()/prev() calls: http://jsfiddle.net/35uNW/3/.
This was a fun one. I did it a slightly different way, getting all of the matched elements and splitting them into before and after arrays.
var $allItems = $(".div");
$(".div").click(function () {
var itemClasses = this.classList;
var itemThread = itemClasses[1];
colorThread($(this), itemThread);
});
function colorThread($div, classname) {
var tId = '.'+classname,
$divs = $allItems.filter(tId),
index = $divs.index($div),
$before = $divs.slice(0, index),
before = $before.get().reverse(),
$after = $divs.slice(index+1);
$div.toggleClass('div-clicked');
$(before).each(function(i, item){
setTimeout(function () {
$(item).toggleClass('div-clicked');
}, i*100);
});
$($after).each(function(i, item){
setTimeout(function () {
$(item).toggleClass('div-clicked');
}, i*100);
});
}
Here's a working fiddle: http://jsfiddle.net/5sUr4/

Identify, and compare, two rows in two different tables

Please see this jsfiddle.
My setup has the following two tables: simTable = datatable-simf-sim and resTable = datatable-simf-res.
I am trying to add a row into resTable everytime the user selects (clicks) on a row in simTable, this works great. However when the user deselects the row, it should be removed from simTable.
resTable = $('#datatable-simf-res').dataTable({});
simTable = $('#datatable-simf-sim').dataTable({});
$('#datatable-simf-sim tr').click(function () {
if ($(this).hasClass('row_selected')) {
$(this).removeClass('row_selected');
var uniqueid = $(this).closest('tr').children('td:eq(0)').text();
$('#datatable-simf-res tr td').each(function () {
if ($(this).text() === uniqueid) {
var rowindex = $(this).closest('tr').index();
resTable.fnDeleteRow(rowindex);
}
});
} else {
$(this).addClass('row_selected');
var rows = [];
$(this).closest('tr').find("td").each(function (cell, v) {
rows.push($(this).text());
});
resTable.fnAddData(rows);
}
resTable.fnDraw();
});
When you play with the jsfiddle (just click a bunch of times on the rows) you will see that it will leave rows untouched, or removes the wrong rows. I am assuming is has to do with the way I identify a row in resTable, as this was the biggest bottleneck for me.
So how do I succesfully identify, and compare, two rows in two different tables?
Thank you very much.
Although the fiddle seems fine to me, a cleaner way to remove the row would be:
// iterate over rows, not all cells
$('#datatable-simf-res tr')
.filter(function () {
// check if the first column contains the unique ID
return $(this).find('td:first').text() === uniqueid
})
.each(function () {
// 'this' is the current DOM element, inside .each()
resTable.fnDeleteRow(this)
});
(The DataTables API says that fnDeleteRow accepts either an index or a <tr> element.)

jQuery traversing and finding textboxes

If I am looping through elements in a table - say a hidden field of class "pmtos" - how do I get a reference to the text field (input) within the same cell in the table?
jQuery is:
// Loop through each hidden field, which holds the outstanding amount
$(".pmtos").each(function () {
var os = $(this).val();
//
//find text box in same cell - and populate with some value
//
//
});
Thank you for any guidance in getting this working.
Mark
Here's a solution to the question before it was edited (as requested):
$('#allocate').click(function () {
var recd = parseFloat( $('#pmtRecd').val() );
$('input.pmtallocated').each(function() {
var value = parseFloat( $(this).parent().prev().text() );
this.value = (recd >= value) ? value : recd;
recd = recd - this.value;
if (recd == 0) {
return false;
}
});
});
Note: This doesn't rely on the hidden input. It takes the text from the td in the second column.
Here's the fiddle
To answer the question post-edit
You can use siblings('.pmtallocated') or prev('.pmtallocated') to get the input. Using siblings() would probably be the better of the two as it doesn't rely on pmtallocated coming directly before pmtos in the markup:
$(this).siblings('.pmtallocated').val()
Try
// Loop through each hidden field, which holds the outstanding amount
$(".pmtos").each(function () {
var os = $(this);
var cell = os.parent(); // gets the parent, i.e. the table cell
var input = cell.find('input')[0];
});
You could use $(this).closest('input')
Check this out. may works for you.
$(".pmtos").each(function () {
var os = $(this).val();
var input = $(this).closest('td').find('input[type=text]');
});

Categories

Resources