Searching a table with jQuery more quickly - javascript

I got this code
$(document).ready(function() {
var activeSystemClass = $('.list-group-item.active');
//something is entered in search form
$('#searchfor').keyup( function() {
var that = this;
// affect all table rows on in systems table
var tableBody = $('.table-list-search').find('tbody');
var tableRowsClass = $('.table-list-search tbody').find('tr');
$('.search-sf').remove();
tableRowsClass.each( function(i, val) {
//Lower text for case insensitive
var rowText = $(val).text().toLowerCase();
var inputText = $(that).val().toLowerCase();
if(inputText != '')
{
$('.search-query-sf').remove();
tableBody.prepend('<tr class="search-query-sf"><td colspan="6"><strong>Searching for: "'
+ $(that).val()
+ '"</strong></td></tr>');
}
else
{
$('.search-query-sf').remove();
}
if( rowText.indexOf( inputText ) == -1 )
{
//hide rows
tableRowsClass.eq(i).hide();
}
else
{
$('.search-sf').remove();
tableRowsClass.eq(i).show();
}
});
//all tr elements are hidden
if(tableRowsClass.children(':visible').length == 0)
{
tableBody.append('<tr class="search-sf"><td class="text-muted" colspan="6">No entries found.</td></tr>');
}
});
});
From http://bootsnipp.com/snippets/featured/js-table-filter-simple-insensitive . The purpose of the code is to scan a table and filtering ("searching") for the keyword entered.
I already followed a guide on how to speed the code up. But still for a table of >400 records the code freezes the browser.
Is there a way to rewrite this code so it will run quicker. I alreay tried to remove the each() but as I am not to good with JavaScript I failed terribly.

The issue in performance is no doubt caused by accessing the DOM so many times on every key up. If you must access the table data this way (rather than JSON, for example), your best bet would be indexing the table rows into an array first that can be searched more efficiently.
Before that, you will typically want to cache the DOM elements you're accessing outside of an event handler if possible, otherwise jQuery has to find these elements every time the event is triggered. Also take advantage of existing selectors (e.g. tableBody) for getting child elements.
First, we have:
var tableBody = $('.table-list-search').find('tbody');
var tableRowsClass = tableBody.find('tr');
We then index the table, adding the text from the rows into the tableData array one at a time. Outside of and before the keyup event you have this:
// store the table data in an array
var tableData = [];
tableRowsClass.each(function(i, val) {
tableData.push($(val).text().toLowerCase());
});
Finally, we dig through the table data array in the keyup event using each() to find multiple instances of a search term.
// scan the table data array on each keyup
$('#searchfor').keyup(function() {
var inputText = $(this).val().toLowerCase();
// use each instead of indexOf to get multiple instances
$(tableData).each(function(i, val){
if (val.indexOf(inputText) > -1) {
// the index of the data in the array will be the same
// as the corresponding table row
tableRowsClass.eq(i).show();
} else {
tableRowsClass.eq(i).hide();
}
});
});
This will no doubt result in significantly more efficient table searching. Accessing data stored in JavaScript will always be quicker than retrieving it from the DOM, simply due to less processing overhead.
See a working example using the code above here: http://jsfiddle.net/ah88w55k/

Related

JQuery: Finding a way to name cloned input fields

I'm not the best at using jQuery, but I do require it to be able to make my website user-friendly.
I have several tables involved in my website, and for each the user should be able to add/delete rows. I created a jquery function, with help from stackoverflow, and it successfully added/deleted rows. Now the only problem with this is the names for those input fields is slightly messed up. I would like each input field to be an array: so like name[0] for the first row, name[1] for the second row, etc. I have a bunch of tables all with different inputs, so how would I make jQuery adjust the names accordingly?
My function, doesn't work completely, but I do not know how to go about changing it.
My Jquery function looks like:
$(document).ready(function() {
$("body").on('click', '.add_row', function() {
var tr = $(this).closest('.row').prev('table').find('tr.ia_table:last');
var clone = tr.clone();
clone.find("input").val('');
clone.find("select").val('');
clone.find('input').each(function(i) {
$(this).attr('name', $(this).attr('name') + i);
});
clone.find('select').each(function(i) {
$(this).attr('name', $(this).attr('name') + i);
});
tr.after(clone);
});
$("body").on('click', '.delete_row', function() {
var rowCount = $(this).closest('.row').prev('table').find('tr.ia_table').length;
var tr = $(this).closest('.row').prev('table').find('tr.ia_table:last');
if (rowCount > 1) {
tr.remove();
};
});
});
I also created a jsFiddle here: https://jsfiddle.net/tareenmj/err73gLL/.
Any help is greatly appreciated.
UPDATE - Partial Working Solution
After help from a lot of users, I was able to create a function which does this:
$("body").on('click', '.add_row', function() {
var tr = $(this).closest('.row').prev('table').find('tr.ia_table:last');
var clone = tr.clone();
clone.find("input").val('');
clone.find("select").val('');
clone.find('input').each(function() {
var msg=$(this).attr('name');
var x=parseInt(msg.split('[').pop().split(']').shift());
var test=msg.substr(0,msg.indexOf('['))+"[";
x++;
x=x.toString();
test=test+x+"]";
$(this).attr('name', test);
});
clone.find('select').each(function() {
var msg1=$(this).attr('name');
var x1=parseInt(msg1.split('[').pop().split(']').shift());
var test1=msg1.substr(0,msg1.indexOf('['))+"[";
x1++;
x1=x1.toString();
test1=test1+x1+"]";
$(this).attr('name', test1);
});
tr.after(clone);
});
A working jsFiddle is here: https://jsfiddle.net/tareenmj/amojyjjn/2/
The only problem is that if I do not select any of the options in the select inputs, it doesn't provide me with a value of null, whereas it should. Any tips on fixing this issue?
I think I understand your problem. See if this fiddle works for you...
This is what I did, inside each of the clone.find() functions, I added the following logic...
clone.find('input').each(function(i) {
// extract the number part of the name
number = parseInt($(this).attr('name').substr($(this).attr('name').indexOf("_") + 1));
// increment the number
number += 1;
// extract the name itself (without the row index)
name = $(this).attr('name').substr(0, $(this).attr('name').indexOf('_'));
// add the row index to the string
$(this).attr('name', name + "_" + number);
});
In essence, I separate the name into 2 parts based on the _, the string and the row index. I increment the row index every time the add_row is called.
So each row will have something like the following structure when a row is added...
// row 1
sectionTB1_1
presentationTB1_1
percentageTB1_1
courseTB1_1
sessionTB1_1
reqElecTB1_1
// row 2
sectionTB1_2
presentationTB1_2
percentageTB1_2
courseTB1_2
sessionTB1_2
reqElecTB1_2
// etc.
Let me know if this is what you were looking for.
Full Working Solution for Anyone Who needs it
So after doing loads and loads of research, I found a very simple way on how to do this. Instead of manually adjusting the name of the array, I realised that the clone method will do it automatically for you if you supply an array as the name. So something like name="name[]" will end up working. The brackets without any text has to be there. Explanation can't possible describe the code fully, so here is the JQuery code required for this behaviour to work:
$(document).ready(function() {
$("body").on('click', '.add_row', function() {
var tr = $(this).closest('.row').prev('table').find('tr.ia_table:last');
var clone = tr.clone();
clone.find("input").val('');
tr.after(clone);
});
$("body").on('click', '.delete_row', function() {
var rowCount =
$(this).closest('.row').prev('table').find('tr.ia_table').length;
var tr = $(this).closest('.row').prev('table').find('tr.ia_table:last');
if (rowCount > 1) {
tr.remove();
};
});
});
A fully working JSfiddle is provided here: https://jsfiddle.net/tareenmj/amojyjjn/5/
Just a tip, that you have to be remove the disabled select since this will not pass a value of null.

Javascript - Dynamic Expand/Collapse All

I have a jQuery Tree Report that I am trying to create 'expand/collapse all' buttons for.
The following two pieces of code are fired when the corresponding buttons are pressed and work great:
for (i = 1; i < 100; i++) {
var el = $('#dtt_2597807651112537_table tbody tr')[i - 1];
// store current level
var level = Number($(el).attr('dtt_level'));
// change icon
$(el).find('span.dtt_icon').removeClass('dtt_collapsed_span');
$(el).find('span.dtt_icon').addClass('dtt_expanded_span');
while ($($(el).next()).attr('dtt_level') != null) {
var el = $(el).next();
if ($(el).attr('dtt_level') == (level + 1)) {
// change display
el.removeClass('dtt_collapsed_tr');
el.addClass('dtt_expanded_tr');
} else if ($(el).attr('dtt_level') == level) {
break;
}
}
}
for (i = 1; i < 100; i++) {
// get related table row
var el = $('#dtt_2597807651112537_table tbody tr')[i - 1];
// store current level
var level = Number($(el).attr('dtt_level'));
// change icon
$(el).find('span.dtt_icon').addClass('dtt_collapsed_span');
$(el).find('span.dtt_icon').removeClass('dtt_expanded_span');
while ($($(el).next()).attr('dtt_level') != null) {
var el = $(el).next();
if ($(el).attr('dtt_level') > level) {
// change display
el.addClass('dtt_collapsed_tr');
el.removeClass('dtt_expanded_tr');
// change icon
$(el).find('span.dtt_icon').addClass('dtt_collapsed_span');
$(el).find('span.dtt_icon').removeClass('dtt_expanded_span');
} else if ($(el).attr('dtt_level') == level) {
break;
}
}
};
However, I was wondering if anyone had a nice way to:
1) Get the number of rows that need to be looped through - I just put 100 as a large number to prove my code worked and I don't want to just increase this to an even larger number.
2) Get the class name from the page source - The large number in "dtt_2597807651112537_table" is a report ID generated by the application. This is static for now but I want to eliminate any problems if it changes.
Thanks.
This is all wrong. Well, it's working against how jQuery works, in any case.
jQuery's credo is:
Select elements
Do stuff to them
Drop your loops. You don't need them.
For example. To toggle the icon on all span.dtt_icon in your document, do
var collapsed = true;
$("#dtt_2597807651112537_table span.dtt_icon") // select elements
.toggleClass('dtt_collapsed_span', collapsed) // do stuff to them
.toggleClass('dtt_expanded_span', !collapsed);
or, as a function that can both collapse and expand:
function toggleTree(tree, collapsed) {
$(tree).find("span.dtt_icon")
.toggleClass('dtt_collapsed_span', collapsed)
.toggleClass('dtt_expanded_span', !collapsed);
}
To collapse only the currently expanded ones...
$("#dtt_2597807651112537_table span.dtt_icon.dtt_expanded_span")
.toggleClass('dtt_collapsed_span', true)
.toggleClass('dtt_expanded_span', false);
and so on.
You can boil down your entire code into a few lines that way, and you don't need to write a single loop: Use smart element selection (via jQuery selectors and any of jQuerys find, filter and traversal functions) to single out the elements you want to manipulate and then manipulate them all at once in a single step.
To your second question. There are many ways, pick one:
use known page structure to determine the right table (e.g. $("div.main > table:first") or something to that effect)
use known table contents to determine the right table (e.g. $("table:has(span.dtt_icon)"))
use the table's other classes ($("table.treeReport") maybe?) or for example the table's ID with and a "starts-with" selector ($("table[id^=dtt_]")).
Again it's all about selecting your elements smartly. A dive into the jQuery API documentation, in this case the part about selectors, is recommended.

Jquery - check column checkboxes then do something with those rows

I've been trying to find a good match to my question, but nothing really concrete. I'm still learning and don't know exactly what I'm missing.
So my code can be found here: Fiddle
This is a simplified version of what I'm working with. In the final version, I will upload a csv file to the html table you see there (id="dvCSV"). Upon uploading, the table will look like it is shown (with added dropdowns and a column of checkboxes). The checkboxes come "pre-chcecked" when I generate them but what I want is the user to be able to turn "off" the rows that I do not want to calculate on.
I'll run you through the process:
This function reads the columns that the user designates. I don't know which column they will upload the data into.
function CheckLocations() {
//Checks the uploaded data for the locations of the Lat/Lon Data based on user dropdowns
colLocs[0] = ($('#Value_0 :selected').text());
colLocs[1] = ($('#Value_1 :selected').text());
colLocs[2] = ($('#Value_2 :selected').text());
colLocs[3] = ($('#Value_3 :selected').text());
LatColumn = colLocs.indexOf("Lat");
LongColumn = colLocs.indexOf("Long");
}
function AllTheSame(array) { //if they do not designate the checkboxes, I prompt them to
var first = array[0];
return array.every(function (element) {
return element === first;
});
}
This function takes all of the data in the designated columns and places them into an array for calculation.
function data2Array() {
//gets the lat and long data from the assigned columns and transfers them to an array for calculation
$("#dvCSV tr td:nth-child(" + (LatColumn + 1) + ")").each(function () {
var tdNode = $("<td/>");
tdNode.html(this.innerHTML);
LatData.push(tdNode.text());
});
LatData.splice(0, 2);
LatData.unshift(1, 1);
$("#dvCSV tr td:nth-child(" + (LongColumn + 1) + ")").each(function () {
var tdNode = $("<td/>");
tdNode.html(this.innerHTML);
LongData.push(tdNode.text());
});
LongData.splice(0, 2); //these two lines remove the first two items then replace them with 0
LongData.unshift(1, 1);
}
The first of these functions removes the checkbox column after calculations are done then new calculated columns are appended at the end. The second one was my attempt to read the checkboxes into an array. Ideally I'd want an array of values true or false, then do the calculations and return the calculated values back to the dvCSV table. For the td's where no calculation was performed, the cell would be empty.
function removeChecks() {
$("#dvCSV th:last-child, #dvCSV td:last-child").remove();
}
function makeCheckArray() {
var searchIDs = $("#dvCSV tbody td:last() input:checkbox:checked").map(function () {
return $(this).val();
}).get();
alert(searchIDs);
}
Hopefully I made the problem clear. Any help would be appreciated.
Pass a class when your table is generated into the tr element. Then create an on change method for your checkboxes. Read more here: http://api.jquery.com/on/
Also if you cannot get the inserted rows id's from your table then start a counter outside of your js like this
counter = 0;
Then inside of your loop add counter++
SO..
<tr class="row-1">
<td>
</td>
</tr>
Then add this snippet outside all of your other JS
$( "tr" ).on( "change", function() {
//do something
$(this+'.row-'+(counter)).hide();
});
This should get you headed in the right direction.

creating a 2 column table dynamically using jquery

I am trying to generate a table dynamically using ajax call. To simplify things i have just added my code to js fiddle here -> http://jsfiddle.net/5yLrE/81/
As you click on the button "HI" first two columns are created properly.. but some how as the td length reaches 2 . its not creating another row. The reason is that when i do find on the table elements its actually retrieving the children table elements. Can some one pls help.
I want a two column table.. Thank you.
sample code:
var tr = $("#maintable tbody tr:first");
if(!tr.length || tr.find("td:first").length >= max) {
$("#maintable").append("<tr>");
}
if(count==0) {
$("#maintable tr:last").append("<td>hi"+content+"</td>");
}
Basically the matching of descendants was allowing for great great grandchildren etc. Just needed to make the matching more specific.
JSFiddle: http://jsfiddle.net/TrueBlueAussie/5yLrE/91/
max = 2
$("button").click(function () {
var content = $('#template').html();
var $table = $("#maintable");
var tr = $table.find(">tbody>tr:last");
if (!tr.length || tr.find(">td").length >= max) {
// Append a blank row
tr = $("<tr>");
$table.append(tr);
}
tr.append("<td>hi " + content + "</td>");
});
This one always targets the last row and adds a row if it does not exists at all (or there are too many divs already) which is what I gather you intended.
I also used the templating I suggested to separate messy HTML strings from the code.
You will want to check the length of the table cells before incrementing a new table row. After you have reached your max column length, reset the row and start over.
JSFiddle
max_columns = 2;
count=0;
$("button").click(function() {
var content='column';
if(count==max_columns||!$('#maintable tr').length){
$("#maintable").append("<tr>");
count=0;
}
if(count!=max_columns)
$("#maintable tr:last").append("<td>"+content+"</td>");
else
$("#maintable tr:first").append("<td>"+content+"</td>");
count++;
});

Knockout JS with Kendo UI Grid forgets user's selection

I have a Kendo UI Grid and I am populating and maintaining the data it displays using Knockout JS (with knockout-kendo.min.js and knockout.mapping-latest.js). When the underlying data updates, the grid also updates to reflect this change. It's working well apart from the problem outlined below.
Code presented in the following Fiddle: http://jsfiddle.net/rTtS9/
The problem is that when the grid updates, it 'forgets' the selected cell that the user has chosen, such that if they are trying to keep an eye on a certain cell value, they can't. If this data is updating sub second (which it could), this gets very silly. The are many other use-case problems too (this is a contrived one).
Is there a way to have the Kendo UI Grid avoid a complete redraw when new data arrives such that the user's selection does not get forgotten?
I thought that this issue might be because KO thought that the whole object had changed, so rather than updating an existing object, it removed and added new ones. To make sure that this isn't the case, I uniquely identify objects using “keys” via the Mapping plugin. In addition, the array remain the same length.
I think that the mapping plugin is working OK as I seem to get desired behavior with the foreach binding, whereby you can select and highlight the ID part of the list item and it won't drop your selection when the data updates. See the Fiddle to see what I mean.
In case this is helpful for anyone else, I have included my solution below which remembers which grid cells were selected before the grid is re-drawn/bound.
I have attached the following code to the Kendo Grid change and dataBound events, respectively. Note, naming conversions for my grid variables always lead with "grid" followed by "name", such as "gridName".
So for the change event:
function saveGridSelection (gridID) {
try {
var shortName = gridID.substring(4,gridID.length)
var idxGrid = ns.grids.map(function(e) {return e.name}).indexOf(shortName);
var gridID = "#grid" + shortName;
var pair=[];
var columnHeader=[];
ns.grids[idxGrid].selectedCells = [];
// Loop over selected realized volsz
$(gridID + " > .k-grid-content .k-state-selected").each(function (index, elem) {
var grid = $(gridID).data("kendoGrid");
var row = $(this).closest("tr");
var colIdx = $("td", row).index(this);
pair[index] = $('td:first', row).text();
columnHeader[index] = $(gridID).find('th').eq(colIdx).text();
if (colIdx != 0 && ns.grids[idxGrid].dataGrid.length > 0 ) { // Check if cell is permitted and has data
pairID = ns.grids[idxGrid].dataGrid.map(function(e) { return e.pair; }).indexOf(pair[index]); // Get the index for the appropriate Pair
ns.grids[idxGrid].selectedCells.push({pair: pairID, container: (colIdx - 1), pairTitle: pair[index], columnHeader: columnHeader[index] });
}
});
} catch (err) {
console.log(err);
}
}
And for the dataBound event:
function loadGridSelection (gridID) {
try {
var shortName = gridID.substring(4,gridID.length)
var idxGrid = ns.grids.map(function(e) {return e.name}).indexOf(shortName);
var gridID = "#grid" + shortName;
var grid = ns.grids[idxGrid];
var gridSelectedCells = grid.selectedCells;
var tempSelectedCells = gridSelectedCells.slice(0); // Create a temp. array to work with
$(gridID + " > div.k-grid-content > table > tbody > tr").each(function (i,e) {
var pair = $("td:nth-child(1)", this).text();
if (tempSelectedCells && typeof tempSelectedCells !== "undefined") {
var ii = tempSelectedCells.length;
while(ii--) { // Loop backwards through teh array so we can slice out the bits we're finished with.
if (pair == tempSelectedCells[ii].pairTitle) {
var row = i;
var column = tempSelectedCells[ii].container;
var noColumns = $(gridID + " > div.k-grid-content > table").find("tr:first td").length;
var cell = (row * noColumns) + 1 + column;
$(gridID).data("kendoGrid").select(gridID + " td:eq("+cell+")");
tempSelectedCells.splice(ii, 1)
}
}
}
});
} catch (err) {
console.log(err);
}
}
Improvements to code always welcome.

Categories