optimize jquery loop to set widths - javascript

I'm trying to stop strings in a div expanding beyond the size of their variable-sized parent divs using the tactic of setting an overflow and fixing the width. I have about 4400 dom elements on the page (can't be decreased and typically can be more), but only about 100-300 need to be changed. Of course, not a problem in FF/Webkit which can do that in less than a second, but IE is extraordinarily slow at over 7 seconds.
I've already eliminated any dom traversing by using an array of pre-determined id elements to address the tags in question. Is there something I'm missing or some alternative trick to do this in a shorter time for IE?
for (id in ids) {
jq("#" + ids[id] + "_name").css({"overflow": "hidden",
'width': jq("#" + ids[id]).innerWidth() - 1
});
}

Well, at the point of being right down to the metal of the DOM and still not eliminating any speed, I've gone for the alternative which is to mitigate the problem so it's less of a problem for the user (maybe IE9 will save MS from this sort of embarrasment!). I looked at this blog entry by Nick Fitzgerald which showed a technique for overcoming the issue. So here, using Nick's pattern, is my solution in the end (just the part for handling IE, I left the non-IE version as is):
yieldingEach(ids, function(namebox) {
var elemName = document.getElementById(namebox + '_name');
if (elemName) {
var elem = document.getElementById(namebox);
elemName.style.width = (elem.scrollWidth - 4) + 'px';
}
});

This is a non-jQuery version...verify it works in IE, but it would be slightly faster since you're not having jQuery do it for you.
for (id in ids) {
var elemName = document.getElementById(ids[id] + '_name'),
elem = document.getElementById(ids[id]);
elemName.setAttribute('overflow', 'hidden');
elemName.setAttribute('width', elem.innerWidth - 1);
}

Related

How can I optimize the following JavaScript which sets the CSS properties of Elements

I have a page with JavaScript which takes a very long time to run. I have profiled the code using Firefox and following is the output.
As you can see I have moved the time consuming lines to a method _doStuff, which seems to do a lot of Graphic related things. Following is the content of the _doStuff method.
_doStuff:function(tds,colHeaderTds,mainAreaTds ){
for (i = 1; i < tds.length; i++) {
if (colHeaderTds[i].offsetWidth <= tds[i].offsetWidth) {
colHeaderTds[i].style.width = tds[i].offsetWidth + "px";
}
mainAreaTds[i].style.width = colHeaderTds[i].offsetWidth + "px";
}
},
I am assuming that the time consuming Graphics sections are due to setting the widths of the elements. Is this observation correct? And how should I go about optimizing the code so that it would take less time to load the page?
Every iteration of your loop JS changes your DOM tree and forces browser to repaint it.
The good practice is to make a copy of your element, modify it in the loop, and after loop change the .innerHTML of the former element.
More reading about repaints on the topic here

IE Standard vs Quirks Handling of offsetLeft

We are modifying an older pre-existing web app and as part of that have begun viewing it using IE10. This app has a third party menu control (menu9_com.js?) and among the numerous issues we are noticing, is the positioning of this menu on IE7+ in Standards mode. In FF, Chrome, or any version of IE with Quirks - it is positioned correctly. In Standards mode, however, it is shoved far off to the right.
I've identified the function below as a possible source for the issue. Running in any mode, the value of StartLeft begins about the same. In the working modes it finishes at a value which - by definition - works. In the broken modes, it is much much higher.
Though it's not fully clear, I believe the function is walking up the DOM from a given target location and adding values on to calculate a "total" offset for the menu element it is adding. And I think the issue comes down to the different ways that offsetLeft (and maybe offsetParent?) are handled. So I'm trying to find the best way to get consistent behavior from this function but just not familiar enough with the intention of the function, nor with the behavior of offsetLeft etc in the various modes.
Here's the function:
function ClcTrgt() {
var TLoc=Nav4?FLoc.document.layers[TargetLoc]:DomYesFLoc.document.getElementById(TargetLoc):FLoc.document.all[TargetLoc];
if (DomYes) {
while (TLoc) {
StartTop += TLoc.offsetTop;
StartLeft += TLoc.offsetLeft;
TLoc = TLoc.offsetParent;
}
}
else {
StartTop+=Nav4?TLoc.pageY:TLoc.offsetTop;
StartLeft+=Nav4?TLoc.pageX:TLoc.offsetLeft;
}
}
Any suggestions? For example, I'd convert this function to use jQuery, if I knew how.
UPDATE:
I've posted the script on pastebin.
My current direction, in the absence of an actual fix to the script (which may not be worth the work), is adding this function to run after the script itself. I added some markup to facilitate it, and it just takes the menu, and puts it where it should be (which it right-aligned with another element I've identified). This is for from optimal, but it works.
function fixMenu9() {
var pTD = $('#pgMenuDivTD');
var pMN = $('#pgMenuDiv');
var additionalOffset = ExpYes ? 3 : 0;
var leftVal = (parseInt(pTD.offset().left) + parseInt(pTD.css('width'))) - (parseInt(pMN.css('width')) + additionalOffset);
$('#pgMenuDiv').css('left', leftVal);
}

Speeding up jQuery empty() or replaceWith() Functions When Dealing with Large DOM Elements

Let me start off by apologizing for not giving a code snippet. The project I'm working on is proprietary and I'm afraid I can't show exactly what I'm working on. However, I'll do my best to be descriptive.
Here's a breakdown of what goes on in my application:
User clicks a button
Server retrieves a list of images in the form of a data-table
Each row in the table contains 8 data-cells that in turn each contain one hyperlink
Each request by the user can contain up to 50 rows (I can change this number if need be)
That means the table contains upwards of 800 individual DOM elements
My analysis shows that jQuery("#dataTable").empty() and jQuery("#dataTable).replaceWith(tableCloneObject) take up 97% of my overall processing time and take on average 4 - 6 seconds to complete.
I'm looking for a way to speed up either of the above mentioned jQuery functions when dealing with massive DOM elements that need to be removed / replaced. I hope my explanation helps.
jQuery empty() is taking a long time on your table because it does a truly monumental amount of work with the contents of the emptied element in the interest of preventing memory leaks. If you can live with that risk, you can skip the logic involved and just do the part that gets rid of the table contents like so:
while ( table.firstChild )
table.removeChild( table.firstChild );
or
table.children().remove();
I recently had very large data-tables that would eat up 15 seconds to a minute of processing when making changes due to all the DOM manipulation being performed. I got it down to <1 second in all browsers but IE (it takes 5-10 seconds in IE8).
The largest speed gain I found was to remove the parent element I was working with from the DOM, performing my changes to it, then reinserting it back into the DOM (in my case the tbody).
Here you can see the two relevant lines of code which gave me huge performance increases (using Mootools, but can be ported to jQuery).
update_table : function(rows) {
var self = this;
this.body = this.body.dispose(); //<------REMOVED HERE
rows.each(function(row) {
var active = row.retrieve('active');
self.options.data_classes.each(function(hide, name) {
if (row.retrieve(name) == true && hide == true) {
active = false;
}
});
row.setStyle('display', (active ? '' : 'none'));
row.store('active', active);
row.inject(self.body); //<--------CHANGES TO TBODY DONE HERE
})
this.body.inject(this.table); //<-----RE-INSERTED HERE
this.rows = rows;
this.zebra();
this.cells = this._update_cells();
this.fireEvent('update');
},
Do you have to repopulate all at once, or can you do it by chunks on a setTimeout()? I realize it'll probably take even longer in chunks, but it's worth a lot for the user to see something happening rather than an apparent lockup.
What worked for me is $("#myTable").detach(). I had to clear a table that has 1000s of rows. I tried $("#myTable").children().remove(). It was an improvement over $("myTable").empty(), but still very slow. $("#myTable").detach() takes 1 second or less.
Works fine in FF and Chrome. IE is still slow.

Improving Efficiency in jQuery function

The while statement in this function runs too slow (prevents page load for 4-5 seconds) in IE/firefox, but fast in safari...
It's measuring pixel width of text on a page and truncating until text reaches ideal width:
function constrain(text, ideal_width){
$('.temp_item').html(text);
var item_width = $('span.temp_item').width();
var ideal = parseInt(ideal_width);
var smaller_text = text;
var original = text.length;
while (item_width > ideal) {
smaller_text = smaller_text.substr(0, (smaller_text.length-1));
$('.temp_item').html(smaller_text);
item_width = $('span.temp_item').width();
}
var final_length = smaller_text.length;
if (final_length != original) {
return (smaller_text + '…');
} else {
return text;
}
}
Any way to improve performance? How would I convert this to a bubble-sort function?
Thanks!
move the calls to $() outside of the loop, and store its result in a temporary variable. Running that function is going to be the slowest thing in your code, aside from the call to .html().
They work very very hard on making the selector engines in libraries fast, but it's still dog slow compared to normal javascript operations (like looking up a variable in the local scope) because it has to interact with the dom. Especially if you're using a class selector like that, jquery has to loop through basically every element in the document looking at each class attribute and running a regex on it. Every go round the loop! Get as much of that stuff out of your tight loops as you can. Webkit runs it fast because it has .getElementsByClassName while the other browsers don't. (yet).
Instead of removing one character at time until you find the ideal width, you could use a binary search.
I see that the problem is that you are constantly modifying the DOM in the loop, by setting the html of the temp_item, and then re reading the width.
I don't know the context of your problem, but trying to adjust the layout by measuring the rendered elements is not a good practice from my point of view.
Maybe you could approach the problem from a different angle. Truncating to a fixed width is common.
Other possibility (hack?) if dont have choices, could be to use the overflow css property of the container element and put the … in other element next to the text. Though i recommend you to rethink the need of solving the problem the way you are intending.
Hugo
Other than the suggestion by Breton, another possibility to speed up your algorithm would be to use a binary search on the text length. Currently you are decrementing the length by one character at a time - this is O(N) in the length of the string. Instead, use a search which will be O(log(N)).
Roughly speaking, something like this:
function constrain(text, ideal_width){
...
var temp_item = $('.temp_item');
var span_temp_item = $('span.temp_item');
var text_len_lower = 0;
var text_len_higher = smaller_text.length;
while (true) {
if (item_width > ideal)
{
// make smaller to the mean of "lower" and this
text_len_higher = smaller_text.length;
smaller_text = text.substr(0,
((smaller_text.length + text_len_lower)/2));
}
else
{
if (smaller_text.length>=text_len_higher) break;
// make larger to the mean of "higher" and this
text_len_lower = smaller_text.length;
smaller_text = text.substr(0,
((smaller_text.length + text_len_higher)/2));
}
temp_item.html(smaller_text);
item_width = span_temp_item.width();
}
...
}
One thing to note is that each time you add something to the DOM, or change the html in a node, the page has to redraw itself, which is an expensive operation. Moving any HTML updates outside of a loop might help speed things up quite a bit.
As other have mentioned, you could move the calls to $() to outside the loop. You can create a reference to the element, then just call the methods on it within the loop as 1800 INFORMATION mentioned.
If you use Firefox with the Firebug plugin, there's a great way of profiling the code to see what's taking the longest time. Just click profile under the first tab, do your action, then click profile again. It'll show a table with the time it took for each part of your code. Chances are you'll see a lot of things in the list that are in your js framework library; but you can isolate that as well with a little trial and error.

clientWidth Performance in IE8

I have some legacy javascript that freezes the tfoot/thead of a table and lets the body scroll, it works fine except in IE8 its very slow.
I traced the problem to reading the clientWidth property of a cell in the tfoot/thead... in ie6/7 and FireFox 1.5-3 it takes around 3ms to read the clientWidth property... in IE8 it takes over 200ms and longer when the number of cells in the table is increased.
Is this a known bug ? is there any work around or solution ?
I've solved this problem if you are still interested. The solution is quite complex. Basically, you need to attach a simple HTC to the element and cache its clientWidth/Height.
The simple HTC looks like this:
<component lightweight="true">
<script>
window.clientWidth2[uniqueID]=clientWidth;
window.clientHeight2[uniqueID]=clientHeight;
</script>
</component>
You need to attach the HTC using CSS:
.my-table td {behavior: url(simple.htc);}
Remember that you only need to attach the behavior for IE8!
You then use some JavaScript to create getters for the cached values:
var WIDTH = "clientWidth",
HEIGHT = "clientHeight";
if (8 == document.documentMode) {
window.clientWidth2 = {};
Object.defineProperty(Element.prototype, "clientWidth2", {
get: function() {
return window.clientWidth2[this.uniqueID] || this.clientWidth;
}
});
window.clientHeight2 = {};
Object.defineProperty(Element.prototype, "clientHeight2", {
get: function() {
return window.clientHeight2[this.uniqueID] || this.clientHeight;
}
});
WIDTH = "clientWidth2";
HEIGHT = "clientHeight2";
}
Notice that I created the constants WIDTH/HEIGHT. You should use these to get the width/height of your elements:
var width = element[WIDTH];
It's complicated but it works. I had the same problem as you, accessing clientWidth was incredibly slow. This solves the problem very well. It is still not as fast IE7 but it is back to being usable again.
I was unable to find any documentation that this is a known bug. To improve performance, why not cache the clientWidth property and update the cache periodically? I.E if you code was:
var someValue = someElement.clientWidth + somethingElse;
Change that to:
// Note the following 3 lines use prototype
// To do this without prototype, create the function,
// create a closure out of it, and have the function
// repeatedly call itself using setTimeout() with a timeout of 1000
// milliseconds (or more/less depending on performance you need)
var updateCache = function() {
this. clientWidthCache = $('someElement').clientWidth;
};
new PeriodicalExecuter(updateCache.bind(this),1);
var someValue = this.clientWidthCache + somethingElse
Your problem may be related to something else (and not only the clientwidth call): are your updating/resizing anyhting in your DOM while calling this function?
Your browser could be busy doing reflow on IE8, thus making clientwidth slower?
IE 8 has the ability to switch between IE versions and also there is a compatibility mode.
Have you tried switching to Compatibility Mode? Does that make any difference?
I though I had noticed a slow performance also when reading the width properties. And there may very well be.
However, I discovered that the main impact to performance in our app was that the function which was attached to the window's on resize event was itself somehow causing another resize which caused a cascading effect, though not an infinite loop. I realized this when i saw the call count for the function was orders of magnitude larger in IE8 than in IE7 (love the IE Developer Tool). I think the reason is that some activities on elements, like setting element widths perhaps, now cause a reflow in IE8 that did not do so in IE7.
I fixed it by setting the window's resize event to: resize="return myfunction();" instead of just resize="myfunction();" and making sure myfunction returned false;
I realize the original question is several months old but I figured I'd post my findings in case someone else can benefit.

Categories