simple jquery each loop making browser hang - javascript

so i'm calling a function in jquery that's looping over a table and determining whether to hide a row based on a hidden form element within each row.
when i run this script, toggling the rows either way, the browser hangs for at least 5 seconds even though there are fewer than 100 rows.
the js looks like this:
$('input.vv').each(function(index) {
var chk = $(this).val();
if (chk == "0") $(this).parents("tr").slideToggle(function() {
tableRows();
});
});
and a sample row from the html looks like this:
<tr class="sortable part item" id="row803">
<td class="col-check">Interior Fixed Dome Camera Surface Mounted<br />(Panasonic Part No. WV-CW484AS/29)
<input type="hidden" class="vv" value="50" id="v803" /></td>
<td class="col-equip cen" id="q803">70</td>
<td class="col-equip cen" id="s803">50</td>
<td class="col-equip cen"><div id="bom803|092311-001|15" />50</div></td>
<td class="col-equip cen" id="b803"><span class="shipped">20</span></td>
</tr>
the line of jquery.js that firebug refers to is 8449
return isNaN( parsed = parseFloat( r ) ) ? !r || r === "auto" ? 0 : r : parsed;
i'm stuck (can't link to the live site sorry). firebug may give me a way out of this but i'm unsure how to use it well enough. thoughts anyone? thanks!

$('input.vv') creates a loop which goes through all input elements, and checks whether they're a part of the vv class.
.parents("tr") loops through all parent nodes, and selects only the <tr> elements.
Then, you call .slideToggle, which creates an effect which requires a significant amount of computing power (at small intervals, CSS style adjustments through JQuery, CSS style parsing by browser). likely to be the main cause
Finally, you're calling tableRows();, which you haven't defined yet.
These operations, on "fewer than 100 rows" requires much computing power.

Try being a little more specific:
$('input.vv').each(function(index) {
if ($(this).value == "0") $(this).parent().parent().slideToggle(function() {
tableRows();
});
});

Related

How to hide multiple (thousands) rows in the html table

I have html page with many rows (is about 40000)
<html><body>
<table id="t1">
<tr id="r1" name="1"><td>row 1</td></tr>
<tr id="r2" name="1"><td>row 2</td></tr>
....
<tr id="r50000" name="3"><td>row 30000</td></tr>
</table></body></html>
I need a fast way to hide/show set of rows (10 000 or 20 000) with the specified name. Platform requirements: IE8-9 and Mozila Firefox. I tray many methods: using tbody, block tags, hiding rows, and stop at one: loop trow the rows and hide/show it:
curLevel=root.getAttribute("Name");
var nextElement=curElement.nextElementSibling;
while(nextElement!=null)
{
curElement=nextElement;
nextElement=curElement.nextElementSibling;
if(curElement.tagName=="TR")
{
i++;
childLevel=curElement.getAttribute("Name");
if(childLevel<=curLevel)
break;
curElement.style.display = blockStyle;
}
}
But this method is very slow!! Takes is about 2 minutes...
Loop goes fast, the slowest part is curElement.style.display = blockStyle; it repaints document every time.
Could I change display style for selection rows and then show changes?
P.S. without jQuery
Probably the fastest way is to use a CSS rule, either by adding and removing a rule, or modifying one. Since the rows you wish to hide have a common name, you can use the equivalent of the following to hide the rows with a name of "1":
tr[name="1"]{
display: none;
}
and remove the rule to show them. The following shows how to do that.
// Object to hold functions for adding and removeing style rules
var myStyles = (function() {
// Use the first style sheet for convenience
var sheet = document.styleSheets[0];
// Delete a rule from sheet based on the selector
function deleteRule(selector) {
// Get rules
var rules = sheet.rules || sheet.cssRules; // Cover W3C and IE models
// Search for rule and delete if found
for (var i=0, iLen=rules.length; i<iLen; i++) {
if (selector == rules[i].selectorText) {
sheet.deleteRule(i);
}
}
}
// Add a rule to sheet given a selector and CSS text
function addRule(selector, text) {
// First delete the rule if it exists
deleteRule(selector);
// Then add it
sheet.insertRule(selector + text);
}
// Return object with methods
return {
'addRule': addRule,
'deleteRule': deleteRule
};
}());
// Convenience functions to hide and show rows
function hideRows(){
myStyles.addRule('tr[name="1"]','{display: none}');
}
function showRows(){
myStyles.deleteRule('tr[name="1"]');
}
Alternative behaviours for the addRule function if a rule with the selector already exists are:
do nothing, or
add the new CSS text to the existing rule
Some play HTML:
<table>
<tr name="1"><td>name is 1
<tr name="1"><td>name is 1
<tr name="1"><td>name is 1
<tr name="1"><td>name is 1
<tr name="2"><td>name is 2
<tr name="2"><td>name is 2
<tr name="2"><td>name is 2
<tr name="2"><td>name is 2
</table>
<button onclick="hideRows()">Hide rows named 1</button>
<button onclick="showRows()">Show rows named 1</button>
Clicking on the first button hides all rows with a name of "1" by adding a CSS rule, clicking the other button shows them by removing the rule.
Of course you can make it much more sophisticated, the above just shows the method.
a table with 40000 rows is not the best for a webpage....
like pradipgarala say you should do it from server side.
or at list use "divs" to separate multiple tables with less rows..
<div id="table_1_1000">
<table>
...rows from 1 to 1000
</table>
</div>
like this you can show-hide only the divs you need... and the loop would be faster...
but still not the best solution....
My first idea would be to do something like this:
var start = 20000; // hide 10k rows
var end = 30001; // rows from 20k to 30k
while(end!=start) {
end--;
var x = 'r' + end;
document.getElementById(x).style.display = "none";
}
Basically, I would use IDs instead going trough DOM Nodes, if possible. It's "cheaper".
Since performance is an issue, you should note that is faster to decrement than to increment.
Note: Since I don't have enough rep, I can't comment on pradipgaralas answer so I'll note it here... Can you do something like IF "request is to hide/show over 10k(or whatever number your benchmark show you) rows" SEND REQUEST TO SERVER ELSE DO YOUR THING ON CLIENT SIDE?

Weird behaviour of part of jQuery code

Ok, lets begin.
this code works fine in console, doing what is required from it.
$('#tasks-table input').parent().html($('#tasks-table input').siblings('textarea').val())
but in such a call it doesn't even execute. console is silent. all other function code is executed properly
updateTask.call(this, 131)
any ideas?
update
this
$('#tasks-table input').parent().html($('#tasks-table input').siblings('textarea').val())
is part of this
updateTask.call(this, 131)
and gets ignored in updateTask
this instruction works from console just fine, but does not when it's a part of updateTask
html is a 4-row php-generated table with a row typical to this
<tr>
<td><?php echo $task->id ?></td>
<td onclick='projects.editTask.call(this, <?php echo $task->id ?>)'><?php echo $task->text ?> </td>
<td><?php echo $task->status ?></td>
<td><?php echo $task->controls ?></td>
</tr>
editTask is a simple replace function(contents of td turned into textarea)
if(block || !($(this).attr('incomplete'))) return; //only one is allowed
block = true;
if(!$(this).children('textarea').length) {
$(this).html('<textarea style="width: 100%; height: 110px;">'+$(this).html()+'</textarea><br/><input type="button" value="Сохранить" onclick="projects.updateTask.call(this, '+parseInt(id)+')" />');
}
the goal of updateTask is to replace anything within td back to contents of textarea and send an update request back to server using $.post
contents of updateTask
var text = $(this).siblings('textarea').val();
//does not work only here, but works anywhere else
$('#tasks-table input').parent().text($('#tasks-table input').siblings('textarea').val())
$.post('/projects/ajax/', {
ajax: true,
command: 'update',
id: id,
text: text }
, function() {
block = false;
});
When I do it more complicated way, it works. more simple way like this one:
$(this).parent().html(text)
didn't work too
Edit:
Revisiting your question, it looks like the point I focused on may not be exactly the problem. I won't delete it because it is a valid issue with the code you posted. The issue with the textarea value not being queried or modified is most likely because you're adding the textarea after the DOM has loaded. You should be able to use a jquery.live() event on your table or the delegate event at the end of this post. Both are valid for any current or future DOM elements. These are a life saver when performing any DOM manipulation or ajax-based HTML updates. This would also explain why you're able to perform the operation in the console and not in the code.
Original answer:
I'm not sure if this will help fully, but I think what is preventing your code from executing is:
if(block || !($(this).attr('incomplete'))) return;
When your table cells don't have that attribute, you're actually executing:
if( block || !(undefined) ) return;
Which will always be true because !(undefined) is true. You most likely want to be forcing a boolean value:
if( block || !!(undefined) ) return;
This is fine if you are only expecting undefined and any text value. A better way to modify this condition is to explicitly check the value you're expected (and I'm assuming you expect to be "true"):
if(block || ($(this).attr('incomplete') === "true") ) return;
Suggestion:
One suggestion I would make is instead of inlining the onclick function, use HTML5 data-* attributes to store your data and perform the code in one place. This helps keep JavaScript in a single place and doesn't mix it throughout the HTML.
For example, you can generate a data-id attribute for each table cell and mark your table row editable with data-edit-cell="true"
<table>
<tr data-edit-cell="true">
<td data-id="1">cell one</td>
<td data-id="2">cell two</td>
<td data-id="3">cell three</td>
<td data-id="4">cell four</td>
</tr>
</table>​
Then, a second suggestion, instead of binding a click event to each cell , you can bind an event to the row using jquery.delegate(), which watches for bubbling events (click) of a child defined by a selector (td):
var block = false;
$('tr[data-edit-cell=true]').delegate('td', 'click', function() {
if (block || $(this).attr('incomplete') === "true" ) return; //only one is allowed
block = true;
console.log('clicked: ' + $(this).attr('data-id'));
});​
I've created a jsfiddle here so you can check this code out.

Optimizing jquery selector for Chrome

I have the following jquery code to loop over 525 (I know, alot!) checkboxes:
var elements = $("#profile-list table tr input[type=checkbox].email-checkout:not(:checked)");
$.each(elements, function(i) {
$(elements[i]).attr('checked', 'checked');
});
UPDATE The html looks like this:
<table>
<tr>
<th>Name</th>
<th>Title</th>
<th>E-mail</th>
<th>Telephone</th>
<th id="email_check"><img src="check_black.png"/></th>
</tr>
<?php foreach ($this->profiles as $profile): ?>
<tr>
<?php echo $this->presentProfile($profile, 'list') ?>
</tr>
<?php endforeach; ?>
This basically loops over all profiles in the database and creates a table row for each profile, where the last table data includes a checkbox, which one can select to send email to. If the user clicks the table header with the id of "email_check" then the javascript code should kick in, and that's where Chrome fails.
I attach the event with the following code:
$("#email_check img").live('click', function() {
//my code
}
When I run this code in Firefox (mac), it goes smoothly but when I run it in Chrome (mac) it takes forever and ends up giving me the window where chrome offers me the option of killing the window, so basically it never completes this loop.
I've been trying to optimize this selector as much as I can, and since jquery 1.3, I understand that they switched from left to right to right to left selector, which basically means that I should try to make my right most selector as specific as I can. Can it be any more specific than I currently have?
Or is it the loop that just takes so long? I have tried switching from $.each to just a regular for() without a positive result.
Any tips or ideas how I can fix this?
Ingiber
I really don't think this is a selector issue at all.
Your selector is a valid selector for querySelectorAll, which means it will be extremely fast.
I tested the exact selector in Chrome on Mac against a table with 250 rows, and the result was instantaneous.
I'd guess that there's something else going on.
Try removing the table tr part of the selector. It isn't adding anything.
Try this:
// console.time("test");
var elements = $("#profile-list input[type=checkbox].email-checkout").get();
var len = elements.length;
for (var i = 0; i < len; i += 1) {
elements[i].checked = true;
}
// console.timeEnd("test");
(So, first we select all check-boxes that are of the class "email-checkout" and are inside the #profile-list element. Then we just set their checked property to true. I assume, this is as fast as it can be.)
You could always give each check box a select/deselect event that will add/remove a class from the checkbox, then use the class as the selector.
You can use a .each() on the set to use the elements directly, like this:
$("#profile-list table tr input[type=checkbox].email-checkout").each(function() {
this.checked = true;
});
Also note the removal of :not(:checked) above...if you're going to check them all, that selector is more expensive that actually checking them anyway. More importantly is that this.checked = true; is tremendously cheaper than $(elements[i]).attr('checked', 'checked'); which happens every time.
Did you profile this? What is taking too long, getting the elements or looping over them? The only way to really speed up code is to profile and fix the slow parts.
FWIW, I would try
var elements = $("#profile-list").find("input[type=checkbox].email-checkout").get();
...
and see what happens.
Add an onlclick on the checkbox
$("#profile-list input[type=checkbox].email-checkout").click(function() {
var obj = $(this);
obj.hasClass("checked") ? obj.removeClass("checked") : obj.addClass("checked");
});
s = $("input[type=checkbox].email-checkout.checked");

visibility property not working as expected in javascript function

I have a table:
<table>
<tr>
<td colspan="2"><h2>Order Awards here:</h2></td>
</tr>
<tr>
<td class="aocBlack">Delivery:</td>
<td>
<select style="width: 200px;" id="deliveryMethod" name="deliveryMethod" size="1" onchange="showMailing()">
<option value="print">I will print it myself.</option>
<option value="mail">Please mail it to me.</option></select>
</td>
</tr>
<tr id="messageText" style="">
<td class="aocBlack" colspan="2">Message to appear on card:</td>
</tr>
<tr id="messageText2" style="">
<td colspan="2"><textarea id="certMessage" name="certMessage" rows="5" cols="10" style="width: 284px;"></textarea></td>
</tr>
</table>
When the select box called deliveryMethod is set to "print", the following two table rows (id messageText and messageText2) should be visible. When it's set to "mail", they should be hidden. I have some javascript that's worked before with no problem, but the id's I was targeting before were always divs. I don't know if table rows behave differently, but I'm getting some strange results. Here's the JS:
function showMailing(){
e = document.getElementById("deliveryMethod");
eVal = e.options[e.selectedIndex].value;
if (eVal == "mail"){
document.getElementById("messageText").style.display="none";
document.getElementById("messageText2").style.display="none";
}else{
document.getElementById("messageText").style.display="inline";
document.getElementById("messageText2").style.display="inline";
}
}
The results are somewhat strange, to my (admittedly javascript/css-rusty) eyes. For example, when the page initially loads, everything displays as it's supposed to: the dropdown's default value is "print", and so the two table rows in question display. When you change the dropdown to "mail", they both disappear. But when you change it back, the fields are all pushed over out of where they're supposed to be. These results are consistent across FF and Chrome (strangely it works correctly in IE) so I have to assume I'm doing something wrong, but I don't know what.
Here are some screenshots (note there are a few fields displayed in the screenshot that I've stripped out of the code shown here just for clarity.) Can anyone help me out here?
On initial load:
After changing from print to mail:
After changing back from mail to print:
The default display value for a table row is table-row(*). If you set it to inline instead you'll be asking the browser to draw table cells inside inline text instead of a row, which will confuse it and give unspecified results.
(*: except on IE<8, which don't support the table-related display values, instead setting them all to block and giving the elements themselves magic layout powers.)
The better way do show/hide, where you don't have to worry about what the default display value might be, is to define a class:
.hidden { display: none; }
and then toggle that class on and off the element.
document.getElementById('deliveryMethod').onchange= function() {
var cls= this.value==='mail'? 'hidden' : '';
document.getElementById('messageText').className= cls;
document.getElementById('messageText2').className= cls;
};
Assigning the handler from script allows you to drop the onchange inline attribute. You also don't need size="1" (that goes without saying for a single-select), or the style="".
The business with reading the select's value using this.options[this.selectedIndex].value you probably don't need any more, unless you're dealing with ancient browsers.
function showMailing(){
e = document.getElementById("deliveryMethod");
eVal = e.options[e.selectedIndex].value;
if (eVal == "mail"){
document.getElementById("messageText").style.display="none";
document.getElementById("messageText2").style.display="none";
}else{
document.getElementById("messageText").style.display="table-row";
document.getElementById("messageText2").style.display="table-row";
}
}
I'd recommend avoiding "eVal" as a variable name, because there's a native JS method called eval(). The capitalization you used is different ("eVal" not "eval"), so it probably won't break things. But it could easily be confusing to a human reader. Also, if you accidentally forget to capitalize the "V" and then need to use the eval() method, it could break your script.
So just in general, avoid using variable names that are similar to the names of existing methods. May I suggest "selectedOption" or "sel" or something?

IE Javascript Clicking Issue

First off, I'm working on an app that's written such that some of your typical debugging tools can't be used (or at least I can't figure out how :).
JavaScript, html, etc are all "cooked" and encoded (I think; I'm a little fuzzy on how the process works) before being deployed, so I can't attach VS 2005 to ie, and firebug lite doesn't work well. Also, the interface is in frames (yuck), so some other tools don't work as well.
Firebug works great in Firefox, which isn't having this problem (nor is Safari), so I'm hoping someone might spot something "obviously" wrong with the way my code will play with IE. There's more information that can be given about its quirkiness, but let's start with this.
Basically, I have a function that "collapses" tables into their headers by making normal table rows not visible. I have "onclick='toggleDisplay("theTableElement", "theCollapseImageElement")'" in the <tr> tags, and tables start off with "class='closed'".
Single clicks collapse and expand tables in FF & Safari, but IE tables require multiple clicks (a seemingly arbitrary number between 1 and 5) to expand. Sometimes after initially getting "opened", the tables will expand and collapse with a single click for a little while, only to eventually revert to requiring multiple clicks. I can tell from what little I can see in Visual Studio that the function is actually being reached each time. Thanks in advance for any advice!
Here's the JS code:
bURL_RM_RID="some image prefix";
CLOSED_TBL="closed";
OPEN_TBL="open";
CLOSED_IMG= bURL_RM_RID+'166';
OPENED_IMG= bURL_RM_RID+'167';
//collapses/expands tbl (a table) and swaps out the image tblimg
function toggleDisplay(tbl, tblimg) {
var rowVisible;
var tblclass = tbl.getAttribute("class");
var tblRows = tbl.rows;
var img = tblimg;
//Are we expanding or collapsing the table?
if (tblclass == CLOSED_TBL) rowVisible = false;
else rowVisible = true;
for (i = 0; i < tblRows.length; i++) {
if (tblRows[i].className != "headerRow") {
tblRows[i].style.display = (rowVisible) ? "none" : "";
}
}
//set the collapse images to the correct state and swap the class name
rowVisible = !rowVisible;
if (rowVisible) {
img.setAttribute("src", CLOSED_IMG);
tbl.setAttribute("class",OPEN_TBL);
}
else {
img.setAttribute("src", OPENED_IMG);
tbl.setAttribute("class",CLOSED_TBL);
}
}
­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­
Have you tried changing this line
tblRows[i].style.display = (rowVisible) ? "none" : "";
to something like
tblRows[i].style.display = (rowVisible) ? "none" : "table-row";
or
tblRows[i].style.display = (rowVisible) ? "none" : "auto";
setAttribute is unreliable in IE. It treats attribute access and object property access as the same thing, so because the DOM property for the 'class' attribute is called 'className', you would have to use that instead on IE.
This bug is fixed in the new IE8 beta, but it is easier simply to use the DOM Level 1 HTML property directly:
img.src= CLOSED_IMAGE;
tbl.className= OPEN_TBL;
You can also do the table folding in the stylesheet, which will be faster and will save you the bother of having to loop over the table rows in script:
table.closed tr { display: none; }
You might want to place your onclick call on the actual <tr> tag rather than the individual <th> tags. This way you have less JS in your HTML which will make it more maintainable.
If you enable script debugging in IE (Tools->Internet Options->Advanced) and put a 'debugger;' statement in the code, IE will automatically bring up Visual Studio when it hits the debugger statement.
I have had issues with this in IE. If I remember correctly, I needed to put an initial value for the "display" style, directly on the HTML as it was initially generated. For example:
<table>
<tr style="display:none"> ... </tr>
<tr style="display:"> ... </tr>
</table>
Then I could use JavaScript to change the style, the way you're doing it.
I always use style.display = "block" and style.display = "none"

Categories