Prevent javascript search function from slowing down browser - javascript

I have a bit of javascript / jquery that upon pressing "enter" in a search box goes through all table rows and then hides those which are not applicable/ do not contain the string. This process is however is extremely taxing on slower systems and so I decided to implement a small loading gif so people know something is happening even though it seems the browser has frozen. The problem though is that the image never appears. I'm assuming it's because the browser freezes. So, now to my question. How can i either make the loop faster, use less computing power, and show the gif? Thank you very much
var $rows = $('tbody tr.visall');
$('#search').keydown(function(e) {
if (e.keyCode == 13){
$('.load').show();
var val = $.trim($(this).val()).replace(/ +/g, ' ').toLowerCase();
$rows.show().filter(function () {
var text = $(this).text().replace(/\s+/g, ' ').toLowerCase();
return !~text.indexOf(val);
}).hide();
};
$('.load').hide();
});
edit: this code goes through about 9000-10000 tr elements.

Hard to say if it will be enough for your data. But here is what you can do:
Reduce the amount of dom manipulations. You can add|remove class hidden to show hide rows.
If your text is static you can create text cache and do not extract it on every search.
You can use setTimeout to delay search execution to show loading gif. Not even sure you will need one. Searching in memory is quite fast.
Demo.
Code
$(function() {
var table = $('#mytable'), //your table
rows = table.find('tr').map(function(){ //all rows you need
return $(this);
}),
rowsCache = (function(from){ //text cache
return from.map(function(){
return this.text();
});
}(rows));
function delay(func) { //delayed function executor
setTimeout(func, 13);
}
var load = $('#load'); //your loader
$('#search').keydown(function(e){
var val;
if(e.keyCode === 13) {
val = $.trim($(this).val());
load.show();
table.hide(); //release dom
delay(function() {
//search in text cache
var toShow = rowsCache.map(function(_, row) {
return row.indexOf(val) > -1;
});
rows.each(function(i){
//simply toggle class let css work for you
this.toggleClass('hidden', !toShow[i]);
});
load.hide();
table.show();
});
}
});
});

Personally I would say drop the filter and use CSS selectors instead then it's just basic DOM manipulation which jQuery should be optimized for.
See http://api.jquery.com/contains-selector/ for documentation and here is a little fiddle http://jsfiddle.net/tgz5X/
Here is what I use in the fiddle as a simple example
function search(mySearchValue) {
$("tr > td:contains(" + mySearchValue + ")").show();
$("tr > td:not(:contains(" + mySearchValue + "))").hide();
}
You would need more but you should get the idea behind this approach.
Or use filter still but with selectors, also this may trigger hide on element which will have show after so more DOM manipualtion
$("tr td").hide().filter(":contains(" + mySearchValue + ")").show();

Related

Toggle function on html click event

I'm trying to add a simple "Read more" button to a page. I managed to prepare a working function that hides the maximum characters within an element. And to call the function I've prepared a button in the HTML with a "onclick" event to call the function.
Now a couple things happen that shouldn't. The button should reveal the hidden text but the opposite happens and everytime the button is clicked more characters are removed until I'm left with only the dots even though I have a var that limits the characters to 15. I have spent too much time trying to figure this out knowing this is probably basic jQuery. I apologize if that is the case I just hope someone can point out what I'm doing wrong here.
HTML:
<button class="content-toggle" onclick="textCollapse()">Read more</button>
jQuery:
function textCollapse() {
var limit = 15;
var chars = jQuery(".field-content p").text();
if (chars.length > limit) {
var visiblePart = jQuery("<span class='visiblepart'> "+ chars.substr(0, limit) +"</span>");
var dots = jQuery("<span class='dots'>... </span>");
jQuery(".field-content p").toggle()
.empty()
.append(visiblePart)
.append(dots)
}
};
I would save full text content into outer variable and keep it there. There is no need to requery text content every time a button is clicked. Also, it's better to have a variable that would store a state for the text - collapsed or not instead of comparing each time.
var fullText = jQuery(".field-content p").text();
var collapsed = false;
function textCollapse() {
var limit = 15;
if (collapsed) {
var visiblePart = jQuery("<span class='visiblepart'> " + fullText + "</span>");
jQuery(".field-content p")
.empty()
.append(visiblePart)
collapsed = false;
} else {
var visiblePart = jQuery("<span class='visiblepart'> " + fullText.substr(0, limit) + "</span>");
var dots = jQuery("<span class='dots'>... </span>");
jQuery(".field-content p")
.empty()
.append(visiblePart)
.append(dots)
collapsed = true;
}
}
Also, I don't think you need to call toggle() since all operations are performed synchronously and there's no need to hide p element before emptying and appending nodes.

performing sortable using JavaScript

i have a situation where i need to achieve this, in a table having n rows. if i click on a row in a table and then click on another row. the content in row of first click should go to content in row of second click and then each row should be shifted by on step backward or forward. this can be taken equivalent to JQUERY sortable.
Example:
|1|2|3|4| if i click on 1 and 4 then it should be |2|3|4|1|
how to record two clicks, and the contents of my rows are div elements containing many input elements. I thought of this idea and is this a good way to achieve sortable or is there a still better way, i want to do it using java script.
thanks in advance.
my script fucntion goes like this. I really should have thought for a little longer. i used flags here, i got two questions more, can i do that with out flags?, is there a better way?
<script>
var flag=0;
var id1;
var id2;
function Myfunction(id)
{
if(flag==0)
{
id1=id;
flag=1;
}
else
{
id2=id;
var x=document.getElementById(id1).innerHTML;
var num1=parseInt(id1);
var num2=parseInt(id2);
for(var i=num1;i<num2;i++)
{
var j=i.toString();
var k=(i+1).toString();
document.getElementById(j).innerHTML=document.getElementById(k).innerHTML;
}
document.getElementById(id2).innerHTML=x;
flag=0;
}
}
</script>
I made up a little http://jsfiddle.net/zxKeg/. Works with jQuery only, no additional plugins needed. Hope this will help you!
JS Code:
$(document).ready(function() {
var $table = $('table');
var $toCopy = null;
$table.on('click', 'tr', function() {
if($toCopy === null || $toCopy[0] == this) {
$toCopy = $(this);
}
else {
$toCopy.remove();
$(this).after($toCopy);
$toCopy = null;
}
});
});

High performance selectable cells in large table - IE6

I am working on an application with a firm business requirement to display an html table with up to 60 rows and up to 50 columns.
Ideally, users would be able to select individual table cells, or click and drag to select multiple cells.
My problem is that I'm limited to using IE6 at the moment, and I have been having real trouble finding (or coding) a way to allow for this kind of selection on this many cells without severe performance degradation.
My current method looks basically like this:
$(document).ready(function() {
var selecting = false;
var colStart, rowStart;
var tableContainer = $("#tableContainer")
tableContainer.delegate("td", "mousedown", function() {
//Clear Selection
tableContainer.find("td.selected").removeClass("selected");
$(this).addClass("selected");
colStart = $(this).index();
rowStart = $(this).parents("tr").index();
selecting = true;
}).delegate("td", "mouseover", function() {
if (selecting) {
//Clear Selection
tableContainer.find("td.selected").removeClass("selected");
var theCell = $(this);
// Get the row and column numbers of the current cell
var colEnd = theCell.index();
var rowEnd = theCell.parents("tr").index();
// Account for rowEnd being smaller than rowStart
var rowSliceStart = Math.min(rowStart, rowEnd);
var rowSliceEnd = Math.max(rowStart, rowEnd);
tableContainer.find("tr").slice(rowSliceStart, rowSliceEnd + 1).each(function() {
var colSliceStart = Math.min(colStart, colEnd);
var colSliceEnd = Math.max(colStart, colEnd);
// Add the required class to the children
$(this).children().slice(colSliceStart, colSliceEnd + 1).addClass("selected");
});
}
}).delegate("td", "mouseup", function() {
selecting = false;
});
});​
Does anybody have any suggestions for a method to improve the performance of this function? I believe the adding/removing of classes is taking up most of the performance overhead, so I'm especially hoping to find efficiencies there.
Tables are the overhead themselves, especially when they contain lots of stuff. Tables also render only when they are complete. Consider paginations if possible.
Constant DOM manipulations, repaints (change appearance) and reflows (change in dimensions) is also an overhead.
IE6 itself wasn't built to do heavy JS operations. IE6 is what? 10 years old? What was JS 10 years ago? validations and pop-ups right?
Repeated function calls. in jQuery, it's best to cache the value of function calls like the $(this) instead of calling it repeatedly.
As what I understand in your code, you are running $.each(), slice and some random math during mouseover. That's heavy.
consider using a newer jQuery
Also, I have cleaned a bit of your code:
$(function() {
var selecting = false,
tableContainer = $("#tableContainer"),
colStart, rowStart;
tableContainer.on("mousedown", 'td', function() {
var $this = $(this); //reference this
colStart = $this.index();
rowStart = $this.closest("tr").index(); //use closest instead of parents to avoid going up to root
$(".selected", tableContainer).removeClass("selected"); //context instead of find
$this.addClass("selected");
selecting = true;
}).on("mouseover", 'td', function() {
if (selecting) {
var theCell = $(this),
colEnd = theCell.index(),
rowEnd = theCell.closest("tr").index(), //use closest
rowSliceStart = Math.min(rowStart, rowEnd),
rowSliceEnd = Math.max(rowStart, rowEnd);
$(".selected", tableContainer).removeClass("selected");
$("tr", tableContainer).slice(rowSliceStart, rowSliceEnd + 1).each(function() {
var colSliceStart = Math.min(colStart, colEnd),
colSliceEnd = Math.max(colStart, colEnd);
$('> *', this).slice(colSliceStart, colSliceEnd + 1).addClass("selected"); //using selector to get children instead of $(this).children()
});
}
}).on("mouseup", 'td', function() {
selecting = false;
});
});​
It doesn't actually look too bad. The only thing I could think of off the top of my head would be to calculate just the deltas on mouseover. That is, store the previous start and end columns/rows and on the next mouseover event, update the classes of just the elements which have changed.
Other minor things:
cache $(this) in the mousedown handler
I'm not 100% sure about this one for IE6, but you could try changing the selector from .find('td.selected') to just .find('.selected'). The first has two conditions to check, vs just one. In modern browsers, the second would definitely be faster, since jQuery can leverage getElementsByClassName, but that doesn't exist in IE6, so who knows?
You could also experiment with making a more targetted selector, especially if the contents of the cells contain further DOM elements. .find('> tr > .selected')
throttle the mouseover handler.

jQuery 'not' function giving spurious results

Made a fiddle: http://jsfiddle.net/n6ub3/
I'm aware that the code has a LOT of repeating in it, its on the list to refactor once functionality is correct.
The behaviour i'm trying to achieve is if there is no selectedTab on page load, set the first tab in each group to selectedTab. If there is a selectedTab present, then use this as the default shown div.
However, as you can see from the fiddle its not working as planned!
If anyone has any ideas how to refactor this code down that'd be great also!
Change
if($('.tabs1 .tabTrigger:not(.selectedTab)')){
$('.tabs1 .tabTrigger:first').addClass('selectedTab');
}
to
if ( !$('.tabs1 .tabTrigger.selectedTab').length ) {
$('.tabs1 .tabTrigger:first').addClass('selectedTab');
}
Demo at http://jsfiddle.net/n6ub3/1/
They way you are doing it (the first code part) you are adding the .selectedTab class if there is at least one of the tabs in that group that is not selected at start .. (that means always)
Update
For a shortened version look at http://jsfiddle.net/n6ub3/7/
Your selector are doing exactly what you're writing them for.
$('.tabs3 .tabTrigger:not(.selectedTab)') is true has long as there is at least one tab that has not the selected tab (so always true in your test case).
So you should change the logic to !$('.tabs3 .tabTrigger.selectedTab').length which is true only if there are no selectedTab
WORKING DEMO with simplified code
$('.tabContent').hide();
$('.tabs').each(function(){
var search = $(this).find('div.selectedTab').length;
if( search === 0){
$(this).find('.tabTrigger').eq(0).addClass('selectedTab')
}
var selectedIndex = $(this).find('.selectedTab').index();
$(this).find('.tabContent').eq(selectedIndex).show();
});
$('.tabTrigger').click(function(){
var ind = $(this).index();
$(this).addClass('selectedTab').siblings().removeClass('selectedTab');
$(this).parent().find('.tabContent').eq(ind).fadeIn(700).siblings('.tabContent').hide();
});
That's all! You don't need all that ID's all around. Look at the demo!
With a couple of very minor changes you code can be reduced to:
$('.tabContent').hide();
$('.tabs').each(function(){
if($('.tabTrigger.selectedTab',$(this)).length < 1)
$('.tabTrigger:first').addClass('selectedTab');
});
$('.tabTrigger').click(function(){
var content = $(this).data('content');
$(this).parents('div').children('.tabContent').hide();
$(this).parents('div').children('.tabTrigger').removeClass('selectedTab');
$(this).addClass('selectedTab');
$('#' + content).show();
});
$('.tabTrigger.selectedTab').click();
Those changes are
Change the class on the surrounding div to just class="tabs.
Add a data-content attribute with the name of the associated content div
Live example: http://jsfiddle.net/gsTBQ/
Well, I'm a bit behind the times obviously; but, here's my updated version of your demo...
I have updated your fiddle as in the following fiddle: http://jsfiddle.net/4y3Xp/1/.
Basically I just tidied it up a bit, and to refactor I put everything in a separate function instead of having each of the cases in their own. This is basically just putting a new function in that does similar to what yours was doing (e.g. not modifying your HTML model), but I tried to clean it up a bit, and I also just made a function that took the tab number and did each of the items that way rather than needing a separate copy for each.
The main issue with the 'not' part of your query is that the function doesn't return a boolean; like all JQuery queries, it's returning all matching nodes. I just updated that part to return whether .selected was returning more than 0 results; if not, I go ahead and call the code to select the first panel.
Glad you got your problem resolved :)
$(document).ready(function(){
var HandleOne = function (i) {
var idxString = i.toString();
var tabName = '.tabs' + idxString;
var tabContent = tabName + ' .tabContent';
$(tabContent).hide();
var hasSelected = $(tabName + ' .tabTrigger.selectedTab').length > 0;
if (!hasSelected)
$(tabName + ' .tabTrigger:first').addClass('selectedTab');
var selectedTabId =
$(tabName + ' .tabTrigger.selectedTab').attr('id');
var selectedContentId = selectedTabId.replace('tab','content');
$('#' + selectedContentId).show();
$(tabName + ' .tabTrigger').click(function() {
$(tabName + ' .tabTrigger').removeClass('selectedTab');
$(tabName + ' .tabContent').hide();
$(this).addClass('selectedTab');
var newContentId = $(this).attr('id').replace('tab','content');
$('#' + newContentId).show();
});
}
HandleOne(1);
HandleOne(2);
HandleOne(3);
});

JQuery: nextUntil() - weird problem

I'm building a page that features a hierarchical tree-type structure. I've posted a simplified version of it at JSFiddle
It mostly works as I'd like but for one thing - on clicking closed a Brand-level row I would like, as well as the town and shoe rows to contract (which they do), for the anchors on the Town rows to change their text to '+'.
I've attempted to do so with
$(this).parent().parent().nextUntil(".TRBrand", ".TownToggle").text("+");
but try as I might it won't play nicely.
Can anyone point me in the right direction ...?
Nested lists are better for tree like structures. You can see the js is easier to write with this markup:
http://jsfiddle.net/RANmK/1/
There were several problems with your version:
The last <a> (for Reebok) had the wrong class : TRTown instead of TownToggle
Your nextUntil(...) for TownToggle was only stopping when it sees .TRTown, which means it hides too much when it is the last Town in the list and continues to hide the next brand as well. It should also stop on .TRBrasnd. You can specify both selectors by seperating them with a comma.
a.toggleTown was not targetted correctly when updating the text value to +
If I understand your requirements correctly, the following should do what you want: http://jsfiddle.net/Sx4qg/69/
$('.BrandToggle').click(function() {
var t = $(this);
var txt = t.text();
var tr = t.closest("tr");
if (txt == "+") {
tr.nextUntil(".TRBrand", ".TRTown").show();
} else {
tr.nextUntil(".TRBrand", ".TRTown, .TRShoes").hide();
tr.nextUntil(".TRBrand", ".TRTown").find("a.TownToggle").text("+");
}
t.text(txt == "+" ? "-" : "+");
});
$(".TownToggle").click(function() {
var t = $(this);
var txt = t.text();
var tr = t.closest("tr");
if (txt == "+") {
tr.nextUntil(".TRBrand,.TRTown", ".TRShoes").show();
} else {
tr.nextUntil(".TRBrand,.TRTown", ".TRShoes").hide();
}
t.text(txt == "+" ? "-" : "+");
});
Try this:
$(this).parent().parent().nextUntil(".TRBrand").find('.TownToggle').text("+");
http://jsfiddle.net/sangdol/Sx4qg/64/
Hope this fiddle will help
$(this).parent().parent().nextUntil("tr:not(.TRTown, .TRBrand)", ".TownToggle").text("+");

Categories

Resources