Poor performance when adding large number of elements to page - javascript

I have produced a table but I am using <div>s instead of <tr>s and <td>s. here is an example:
<div class="tbl tbl1">
<div class="thead">
<div class="tr">
<div class="td colTitle" style="width: 120px"><span>Title</span></div>
<div class="td colLink" style="width: 190px"><span>Link</span></div>
<div class="td colSize numeric" style="width: 75px"><span>Size(MB)</span></div>
<div class="td colUploadDate" style="width: 75px"><span>UploadDate</span></div>
<div class="td colOpen" style="width: 50px; max-width: 50px;"><span>Show</span></div>
</div>
<div class="tr">
<div class="td colTitle">
<input type="text" class="Filter" />
</div>
<div class="td colLink">
<input type="text" class="Filter" />
</div>
<div class="td colSize">
<input type="text" class="Filter" />
</div>
<div class="td colUploadDate">
<input type="text" class="Filter" />
</div>
<div class="td colOpen">
</div>
</div>
</div>
<div class="tbody">
</div>
</div>
I will fill tbody with an ajax function. After getting all data from the database, I store it into an array in client-side. I use below codes to fill my table using array arr. But when I have a large number of rows it takes a lot of time to display rows in my table.
var res = "";
arr.forEach(function (row) {
res += "<div class='tr' idattachment='" + row["IdAttachment"] + "' >" +
"<div class='td colTitle'>" + row["Title"] + "</div>" +
"<div class='td colLink'>" + row["Name"] + "</div>" +
"<div class='td colSize'>" + (row["Size"] / (1024 * 1024)).toFixed(2) + "</div>" +
"<div class='td colUploadDate'>" + row["UploadDate"] + "</div>" +
"<div class='td colOpen'><a class='link' href='uploads/" + row["Name"] + "'>Open</a></div>" +
"</div>";
});
$(".tbody").html(res);
Is there any more efficient way to load data into table?

Use virtual rendering to only render the rows that are actually in view.
Example with Clusterize.js:
var rows = [];
for (var i = 0; i < 10000; i++) {
rows.push('<tr><td>Row ' + i + '</td></tr>');
}
var clusterize = new Clusterize({
rows: rows,
scrollId: 'scrollArea',
contentId: 'tbody'
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/clusterize.js/0.18.0/clusterize.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/clusterize.js/0.18.0/clusterize.min.css" rel="stylesheet"/>
<div class="clusterize-scroll" id="scrollArea">
<table>
<thead class="thead">
<tr class="tr">
<td>
<span>Title</span>
</td>
</tr>
</thead>
<tbody id="tbody" class="clusterize-content">
<tr class="clusterize-no-data">
<td>Loading data…</td>
</tr>
</tbody>
</table>
</div>

I'm going to do a performance analysis. I will go step by step so you can try and do the same if you like the pattern. Keep in mind some times this analysis will say there is nothing obvious you can do.
Performance Goals
When ever you are trying to archive a level performance the first question should be what are we trying to do.
The normal scale is as follows.
< 16ms for animations
< 100ms for immediate reactions
100-300ms Slight perceptible delay
300-1000ms A task being done.
> 1s loss of Focus.
> 10s Frustration and abandons task
It seems you are doing a task so <= 1s is the Goal.
When caring about these Goal follow the data.
JS
You are using forEach seems to be the best option.
https://jsperf.com/for-vs-foreach/75
String Plus vs template literal, does not seem to matter.
https://jsperf.com/template-literal-vs-string-plus/7
Dot Notation vs Square Bracket Notation, does not seem to matter.
https://jsperf.com/dot-notation-vs-square-bracket-notation/5
JS Conclusion
The data supports your Javascript is as optimized as it can be.
CSS
CSS not provided.
CSS Conclusion
Inconclusive
Rendering
jQuery append vs html vs innerHTML
https://jsperf.com/jquery-append-vs-html-list-performance/24
It seems using innerHTML would be a better choice.
Rendering Conclusion
Change .html to innerHTML
Perception
When I render this code without CSS in a as you did not provide it. I start hitting 1s render times when I render 10,000 items.
You can paginate the rendering but if you do this you will need to load chunks that take < 16ms to render. Even if you think you don't have animation scrolling is an animation.
When you do your testing on low end device and get how many items can be rendered under 1 second do the following. I'm using 10k as I hit 1s on my device. 10,000 / 1000 * 15 This will give you the number of items you can load in 1s without disrupting animation.
Add a Spinner or something that shows work is being done. So the user thinks in terms of something is working which gains time from < 100ms to 300-1000ms.
Perception Conclusion
Paginate
Add Spinner or show work is being done.
That's all I got. Good Luck.

Give the browser some breathing space while loading.
var nextRow = 0;
var handler;
//Process a row in every 250ms
var handler = setInterval(function() {
//Select next row
var row =arr[nextRow];
var res= "<div class='tr' idattachment='" + row["IdAttachment"] + "' >" +
"<div class='td colTitle'>" + row["Title"] + "</div>" +
"<div class='td colLink'>" + row["Name"] + "</div>" +
"<div class='td colSize'>" + (row["Size"] / (1024 * 1024)).toFixed(2) + "</div>" +
"<div class='td colUploadDate'>" + row["UploadDate"] + "</div>" +
"<div class='td colOpen'><a class='link' href='uploads/" + row["Name"] + "'>Open</a></div>" +
"</div>";
//Append
$(".tbody").appendChild(res);
//Move on
nextRow++;
//Exit at the end
if (arr.length === nextRow)
clearInterval(handler);
}, 250);
This will keep adding your data to the table until the end.
Also remember, string concatenation is expensive. You could even try the appendChild method without the interval.

Related

How to use JQuery .find() properly when using multiple divs

I have an HTML page where users can create a newsitem. Each news item is displayed in a new div, with multiple divs inside to divide content into multiple columns, to hide some content etc. The following code is a stripped down version of 1 newsitem:
<div class="news-item clearfix" id="c-b504b780-06a8-49bc-ba04-84d1fbba1a94">
<h2>This is a title</h2>
<div class="news-image div-right">
<a class="img-gallery" href="Images/Dynamic/700x700/NewsItem/44951/example.png" rel="lightbox-This is a title"><img class="img-responsive clear" src="Images/Dynamic/200x200c/NewsItem/44951/example.png" /></a><br />
</div>
<div class="preview one-image">
<p>Text here</p>
</div>
<div class="full div-hide">
<p>Text here</p> <img src="Images/Dynamic/full/NewsItem/44951/example.png" class="img-responsive" />
</div>
As you can see, each newsitem has a generated code attached to it which is used to identify unique newsitems, the code is also used in my javascript:
<script type="text/javascript">
(function () {
var newsdiv = $('#c-b504b780-06a8-49bc-ba04-84d1fbba1a94');
$('#c-b504b780-06a8-49bc-ba04-84d1fbba1a94 > .full').find('img').wrap(function () { return "<a href='" + $(this).attr('src') + "'></a>"; });
})();
</script>
This script is supposed to put an anchor tag around the image found in my full div-hide class, however this is not working properly. I assume my jquery selector is not selecting the right div, but I do not know what it should be. Do you have an idea how I can wrap my anchor tags around images inside the class="full" div?
You have two issue in document ready function:
1) You have not added jquery selector($) in front of ready function.
2) You dont need to call the document ready function.
$(function () {
/*^^missing selector here*/
var newsdiv = $('#c-b504b780-06a8-49bc-ba04-84d1fbba1a94');
$('#c-b504b780-06a8-49bc-ba04-84d1fbba1a94 > .full').find('img').wrap(function () { return "<a href='" + $(this).attr('src') + "'></a>"; });
});
/*^^ () not required here*/
and to make it work for all the divs:
$('.news-item .full img').each(function(){
$(this).wrap("<a href='" + $(this).attr('src') + "'></a>");
});
Working Demo
Try this:
$('#c-b504b780-06a8-49bc-ba04-84d1fbba1a94 > .full img').each(function() {
$(this).wrap(function () { return "<a href='" + $(this).attr('src') + "'></a>"; });
});
$('div.full').find('img').each(function() {
// your code here ..
});

Lots of empty lines in the DOM

When I read .html() at the end of this snippet, I get this result :
"
<div style="left: 0px;" class="text">Test1</div><div style="left: 1px;" class="text">Test2</div><div style="left: 2px;" class="text">Test3</div>"
Why all these empty lines?
Note: in the JS part, I detach all the .text elements, apply some modifications on them, and reappend them to the DOM. I need to do that in my real code, for some reasons that would be out of topic here.
$("#blah").append($(".text").detach().each(function(i) {
this.style.left = Math.random() * 10 + 'px'; this.style.top = i*10 + 'px';
}));
console.log($("#blah").html());
.text {position:absolute;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<body>
<div id="blah">
<div class="text" >Test1</div>
<div class="text" >Test2</div>
<div class="text" >Test3</div>
</div>
You do not need to the detach and append the elements to change their style. You are basically accumulating all the whitespace between elements at the top and moving all the elements below the whitespace.
If your HTML looked like this the problem would not have been visible:
<div id="blah"><div class="text" >Test1</div><div class="text" >Test2</div> <div class="text" >Test3</div></div>
JSFiddle: http://jsfiddle.net/TrueBlueAussie/not1yerc/
Basically do not detach and append. The changes occur on the same JS browser cycle so will not glitch:
$(".text").each(function(i) {
this.style.left = Math.random() * 10 + 'px'; this.style.top = i*10 + 'px';
});
console.log($("#blah").html());
The real problem:
As the real problem is using detach and append to avoid transition-delay styling from firing, the real solution is to remove a specific transition class from the elements, change them, then add that class back.
e.g. have a new transitions class with the transitioning styles:
.transitions{
transition-duration: 0.2s;
}
make sure all your text elements have that class as required. Then your code becomes:
$(".text").each(function(i) {
// Remove the transitions styling class
$(this).removeClass('transitions');
// Change the layout without transitions
this.style.left = Math.random() * 10 + 'px'; this.style.top = i*10 + 'px';
// Restore the transitions styling class
$(this).addClass('transitions');
});
This is much faster than detach and appending DOM elements and will not move the whitespace.
Why all these empty lines?
Because they were in your initial DOM before you moved around nodes. If you have a look at the html source of #blah, you'll find four linebreaks - which are represented in the DOM as whitespace text nodes. Since you didn't remove them, they still reside in the DOM - only with no more <div>s in between them, making the lines empty.
You have empty lines because of the whitespace in your markup. Also, as mentioned by others in comments, you don't need to detach. If you don't want to change your markup, then do a .trim().
Sample Snippet:
$("#blah").find(".text").each(function(i) {
this.style.left = Math.random() * 10 + 'px'; this.style.top = i*10 + 'px';
$(this).appendTo($("#blah"));
});
console.log($("#blah").html().trim());
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="blah">
<div class="text" >Test1</div>
<div class="text" >Test2</div>
<div class="text" >Test3</div>
</div>
Alternatively, just remove the whitepsace from your markup in this case. As #trueblueassie mentioned, .trim() could be slow for large chunk of HTML string. And, then you don't even need to .append() to move things around.
<div id="blah"><div class="text" >Test1</div><div class="text" >Test2</div><div class="text" >Test3</div></div>

jQuery - preserve html elements after change

I'm using Ruby on Rails to implement a simple edit/submit button to replace the content of h4 that has class named "special-content".
Here is my code for rhtml:
<div class="modal-body" style="height: 280px !important;">
<%= image_tag("special/turkey.png", :alt => "turkey", :class => "special-img") %><br>
<h4 class="special-content">#93 Turkey, Avocado & Cheese</h4><h4> with Small Sized Drink & Chip</h4>
</div>
and here is my code for jQuery, which is implemented right above the rhtml code:
<script type="text/javascript">
$(document).ready(function() {
$("body").on("click", "#edit", function() {
$('#edit').replaceWith('<a class="btn" id="submit">Submit</a>');
$('.special-content').replaceWith('<input class="special-content-edit" type="text" value="' + $('.special-content').html() + '">');
$('.special-img').replaceWith('<input class="special-img-edit" type="text" value="' + $('.special-img').attr('src') + '">');
});
$("body").on("click", "#submit", function() {
$('#submit').replaceWith('<a class="btn" id="edit">Edit</a>');
$('.special-img-edit').replaceWith('<img class="special-img" src="' + $('.special-img- edit').val() + '">');
$('.special-content-edit').replaceWith('<h4 class="special-content">' + $('.special-content-edit').val() + '</h4>');
});
});
</script>
The jQuery should allow users to replace the h4 content. Everything works fine. However, if I navigate to another link and come back to the page, the h4 content changes back to the original content ("#93 Turkey, Avocado & Cheese"). How can I preserve the changed element?

How to add 2 buttons to <li> via jquery

I am using http://jsfiddle.net/eLENj/493/ as a guide line to create 2 stacked buttons in an li element.
This is the code I am using
'<li><div class="ui-grid-a">' +
'<div class="ui-block-a" style="width: 75%;">' +
'<div data-role="fieldcontain">' +
'<h3>Address Details:</h3>' +
'<p>Address 1</p>' +
'</div>' +
'</div>' +
'<div class="ui-block-b" style="width: 25%; float: right;">' +
'<div data-role="controlgroup" data-type="horizontal" style="height: 20px;">' +
'Map' +
'Delete' +
'</div>' +
'</div>' +
'</div>' +
'</li>').listview('refresh');
But what I end up with are two "regular" hyperlinks which look like "MapDelete". Any ideas why the buttons are not being rendered correctly?
Method listview('refresh') will style ONLY a listview.
Because buttons are not part of a listview they will be disregarded.
You will need to style them separately like this:
$('[data-role="button"]').button();
Or you can use this method on your content DIV:
$('#contentDivID').trigger('create');
If you want to find more about this topic take a look at my other blog ARTICLE describing how to enhance dynamically added jQuery Mobile content.
EDIT :
Working example: http://jsfiddle.net/Gajotres/UDBCM/
You will need to position them by yourself + find some custom map icon.

formatting data caption in light box

how to format the data caption
I am planning to display three different names for a single image
after i click the cube light box opens and the caption comes with div tag
but i wanted to display it one by one on the light box
its working fine without light box
$data = '';
$('[data-caption]').each(function(){
$data += '<div>' + $(this).data('caption') + '</div>';
});
$('body').append('<div id="more-info">' + $data + '</div>');
http://jsfiddle.net/ZrpLT/52/
<body>
<div class="row">
<div class="span10">
<div class="melonhtml5_gallery">
<div data-caption="<div>Paul Scholes</div> <div>Wayne Rooney</div> <div>Sir Alex Ferguson</div>" data-image="http://www.defie.co/designerImages/thumbnails/inventory.png"></div>
</div>
</div>
</body>
http://jsfiddle.net/ZrpLT/63/
This requires updating the Gallery class to use .html instead of .text when the caption is appended specifically in the onClick function on the line:
var m = $("<div>").addClass("caption").html(g ? g : ""),
As long as you are able to do that, you can get this to work for you.

Categories