I have to show a progressbar/status indicator using pure JavaScript, no jQuery please.
My code is:
<script type="text/javascript">
function processObjects()
{
var selectedRows = {}; // array of selected rows from table
var count = selectedRows.length; // count value exceeds 100
var myDiv = document.getElementById("myDiv");
for(var i=0; i < count; i++)
{
myDiv.innerHTML = (i+1)+"/"+count;
// Process each object from array
// no Ajax call
// takes almost 0.1 sec for each object <- this is not an issue
}
}
</script>
<div id="myDiv"></div>
<input type="button" onclick="processObjects()" value="Process Objects" />
<table>
<!-- Table with lots of rows with checkboxs -->
</table>
Problem:
When I run this script in any Browser, the page becomes unresponsive and does not update the status in using innerHTML as 1/100...2/100...3/100 as so on.
what could be the possible solution to stop browser from becoming unresponsive?
JS is single threaded and it has to take the full attention of the browser while being inside a function.
You need to call long processes through setTimeout() function if you need to give the browser a chance to breath while processing something long.
See how I do this in the following example:
function doProgress(count) {
if (count == 100)
return;
document.getElementById("myDiv").innerHTML = count;
count++;
setTimeout(doProgress, 0, count); //<- calling the same function with new count here. "0" is the milliseconds to call it after. "count" is the argument to pass
}
It only demonstrate this technique and there are lot of best practices to follow once you master it.
Javascript locks the view while code is executing (unless you are using a canvas) so you must end the execution of your code before being able to see results in your DOM.
Even if this article is about angular, the intro explains quite well how javascript works and why it freezes a browser http://jimhoskins.com/2012/12/17/angularjs-and-apply.html
if you want to keep it simple you can do this:
<script type="text/javascript">
var start=0;
var selectedRows = {}; // array of selected rows from table
var count = selectedRows.length; // count value exceeds 100 value
var myDiv = document.getElementById("myDiv");
function processObject(){
myDiv.innerHTML = (++start)+"/"+count;
// Process one object from array using "start" as index
if(start<count){
setTimeout(processObject, 100);
}
}
function processObjects(){
//eventually update values
selectedRows=[] //adds items to array
count = selectedRows.length;
myDiv = document.getElementById("myDiv");
processObject();
}
</script>
<div id="myDiv"></div>
<input type="button" onclick="processObjects()" value="Process Objects" />
<table>
<!-- Table with lots of rows with checkboxs -->
</table>
if you don't want to use global variables you can do this:
function processObject(){
processObject.myDiv.innerHTML = (++processObject.start)+"/"+processObject.count;
// Process one object from array using "start" as index
if(processObject.start<processObject.count){
setTimeout(processObject, 100);
}
}
function processObjects(){
processObject.selectedRows=[]; //array with rows to process
processObject.count=processObject.selectedRows.length
processObject.start=0;
processObject.myDiv=document.getElementById("myDiv");
processObject();
}
Related
I have a function that is adding new strings to an array at random intervals. How can I display with javascript and/or jquery each new string on my page every time the length of the array increases?
You can set a recursive timer function that will updated your array display container every time it is called (adapted from Javascript dynamic array of strings):
<html>
<body>
<script type="text/javascript">
var arr = [];
function add() {
arr.push("String " + Math.random());
}
function show() {
var html = '';
for (var i = 0; i < arr.length; i++) {
html += '<div>' + arr[i] + '</div>';
}
var con = document.getElementById('container');
con.innerHTML = html;
}
function start() {
setTimeout(function() {
add();
// Note: you can call show in an independent timeout
show();
start();
}, 1000);
}
</script>
<input type="button" onclick="start();" value="start" />
<br />
<div id="container"></div>
</body>
</html>
Or you can make it smarter and update the container only if the array's length changed.
Yet another way is to pass a display container update callback to your array update function, so that whenever you update your array - you just go and re-display your array.
<html>
<body>
<script type="text/javascript">
var arr = [];
var lastDisplayed = 0;
function add() {
arr.push("String #" + lastDisplayed + ": " + Math.random());
show(); // Update display container
};
function show() {
var node;
var textnode;
var container = document.getElementById('container'); // Get parent container
for (; lastDisplayed < arr.length; lastDisplayed++) {
node = document.createElement("li"); // Create a <li> node
textnode = document.createTextNode(arr[lastDisplayed]); // Create a text node
node.appendChild(textnode);
container.appendChild(node);
}
};
function start() {
setTimeout(function() {
add();
start();
}, 1000);
};
</script>
<input type="button" onclick="start();" value="start" />
<br />
<ul id="container"></ul>
</body>
</html>
Internally, Angular and other frameworks implement a combination of these approaches.
Important note: depending on your application, you might want to explore different approaches to updating your page to preserve responsiveness and performance of your interface. For example, you might want to space your GUI updates in time if array elements are added too often. You may also want to keep adding elements to your DOM model (see the second example) instead of rewriting it (like in the first example) if the existing elements of your array remain unchanged. Similar issues might need to be considered if using a dedicated framework like Angular.
I would recommend using a library that handle property subscriptions like knockout or angular, but since that wasnt mentioned in the question I will give this example.
var someArray = [];
var standardPush = someArray.push;
someArray.push = function(){
// depending on your requirements you can switch these next two lines around
for (var i = 0; i < arguments.length; i++) {
updateUI(arguments[i]); // call code to update UI
standardPush(arguments[i]); // actually ad record to array
}
}
function updateUI(record){
// Some code that updates your UI
// example
$("#container").append($("<div>").text(record));
}
then just call it like a normal array.push
someArray.push(someRecord);
// or
someArray(record1, record2, record3....);
This code is more fun than practical, I again would recommend a library that handles property subscriptions.
I'm trying to understand why it takes so long to rebuild a table using javascript on Firefox 43.0.2
A simplified version of my code is below. Both the "real" code and the simple version use "publishTable() to add rows to a table. PublishTable deletes a table body element if it exists, creates a new one, adds about 9000 rows to it, and attaches the completed table body to the table.
PublishTable runs on load, and when the user clicks a "go" button. Therefore, I expect performance to be similar on load and rebuild.
When the "real" page first loads, Firefox takes about 300ms to construct the table [according to performance.now()]. When the alert() announcing this result is closed, i can immediately scroll the page up and down.
But if i click the "go" button to rebuild the table, Firefox spins its wheels for tens of seconds (or more) after I close the alert() dialog. A "stop script?" dialog can appear more than once. The simple version behaves similarly.
So: Why is the performance so radically different between initial build, and rebuild? It seems clearly possible to build the table in 300ms! Is there anything I can do about it?
Some further observations:
IE's performance is much worse on initial load, and as bad on rebuild. Chrome's performance is pretty good: 2 seconds to build or rebuild. If I use innerHTML, rather than insertRow, appendChild, etc., results are similar.
If i remove the line attaching the table body to the table, the wheel-spinning symptom does not occur.
In the "waterfall" chart (in the Firefox performance tool), the the "DOM event" takes up much more time than the "event handler" (which I think covers the run-time of my code), and I don't know what that means. What is happening between the time the js stops running, and the DOM event ends, that doesn't fall in one of the other waterfall categories?
The DOM event is followed by a brief time to recalculate style, a time to paint, and then a sequence of many "cycle collection" periods, then "incremental gc", "cc graph reduction", "cycle collection", "graph reduction", and so on, for tens of seconds. In one case, the performance call-tree allocated 49 seconds to "Gecko" (which seems to be idle time) and another 25 seconds to "graphics" (and within that, a mere 1 second is allocated to publishTable()). Is there something here I can act on?
I'm out of reasonable ideas for further analysis, or how I might modify the js. I don't understand enough about the performance information to act on it. (And now, after timing with IE and Chrome, I'm not even sure to whom the question should be addressed.)
Is there a fundamental error in the code? A better table construction strategy? A way to use the performance tool to understand the problem? A bug in Firefox? (And now I'm going to do the thing on the server side. But I'm still curious about what's going on.)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<div id='a'>
<button type="button" disabled id="btnGo">Go</button><br />
<button type="button" id="btnQ">?</button><br />
<table id="tideTable" style="Width:40%;margin-left:auto;margin-right:auto;">
</table>
</div>
<div id="b">
hello
</div>
<script>
(function() {
var mmm = ['one', 'two', 'three', "four", "five", "six", "seven"];
function publishTable() {
// The user may run this several times, varying some parameters each time.
var tStart = performance.now();
var table = document.getElementById('tideTable');
// var tableBody = table.getElementsByTagName('tbody')[0];
var tableBody = table.tBodies[0];
if (tableBody) {
tableBody.parentNode.removeChild(tableBody);
}
showHeight();
tableBody = document.createElement('tbody');
for (var i=0; i < 8500; i++) {
appendTableRow(tableBody, mmm);
}
table.appendChild(tableBody);
document.getElementById("btnGo").disabled = false;
alert("Time: " + (performance.now() - tStart) + "ms");
showHeight();
}
function appendTableRow(tableBody, columns) {
var cell;
var textNode;
var row = tableBody.insertRow(-1);
for (var i = 0; i < columns.length; i++) {
cell = row.insertCell(i);
textNode = document.createTextNode(columns[i]);
cell.appendChild(textNode);
}
}
function showHeight(){
var el = document.getElementById('b');
var topPos = el.offsetTop;
alert("position: " + topPos);
}
document.getElementById("btnGo").addEventListener("click", publishTable);
document.getElementById("btnQ").addEventListener("click", showHeight);
publishTable();
})();
</script>
</body>
</html>
I guess, it could be because of the removal of existing items, before inserting the new ones. You could try the following:
meassure what happens to the performance, if you just extend the table, without deletion
build the table before inserting it, e.g. make a variable tableContent put the rows in it, and then add tableContent to the table, that should be faster, because your browser has to rerender the page on every insert.
And I would advice you to consider the use of angularJS, if you plan to make the table dynamic
I tried swapping out the line:
var tableBody = table.getElementsByTagName('tbody')[0];
with the built-in getter:
var tableBody = table.tBodies[0];
and this seems to stabilize the build time. Still a bit slow, but near-consistent times reported for initial build and rebuilds.
This could be coincidental, but something you may want to mess around with.
Your JS is minified and in a CloudFront CDN.
The first demo is async and the second demo is defer
Async
https://jsfiddle.net/zer00ne/6m9f24j5/
Defer
https://jsfiddle.net/zer00ne/fcpy9z0c/
Results
Same times.
142ms on Firefox loading.
Avg of 230ms on each click event.
846ms on Chrome loading.
Avg of 930ms on each click event.
Put your <script> tags before the closing </body> tag
https://jsfiddle.net/zer00ne/y7mguyry/
(function() {
var mmm = ['one', 'two', 'three', "four", "five", "six", "seven"];
function publishTable() {
// The user may run this several times, varying some parameters each time.
var tStart = performance.now();
var table = document.getElementById('tideTable');
var tableBody = table.getElementsByTagName('tbody')[0];
if (tableBody) {
tableBody.parentNode.removeChild(tableBody);
}
tableBody = document.createElement('tbody');
for (var i = 0; i < 8500; i++) {
appendTableRow(tableBody, mmm);
}
table.appendChild(tableBody);
document.getElementById("btnGo").disabled = false;
alert("Time: " + (performance.now() - tStart) + "ms");
}
function appendTableRow(tableBody, columns) {
var cell;
var textNode;
var row = tableBody.insertRow(-1);
for (var i = 0; i < columns.length; i++) {
cell = row.insertCell(i);
textNode = document.createTextNode(columns[i]);
cell.appendChild(textNode);
}
}
document.getElementById("btnGo").addEventListener("click", publishTable);
publishTable();
})();
<button type="button" disabled id="btnGo">
Go</button>
<br />
<table id="tideTable" style="Width:40%;margin-left:auto;margin-right:auto;">
</table>
I'm writing a small progam wherein I'm getting data using $.get then display the data so far so good and then there's this part then when I click a certain link it refresh the page but it has this blinking effect. Is there a way on how to reload the content get the new updated content then replace the previously loaded data.
NOTE: I didn't use setInterval or setTimeout function because it slows down the process of my website. any answer that does not include those functions are really appreciated.
Here's the code
function EmployeeIssues(){
$('#initial_left').css({'display' : 'none'});
var table = $('#table_er');
$.get('admin/emp_with_issues', function(result){
var record = $.parseJSON(result);
var data = record.data,
employees = data.employees,
pages = data.pages;
if(employees){
$('#er_tab_label').html('<b>Employees with Issues</b>');
for (var i = 0; i < employees.length; i++) {
$('#table_er').fadeIn('slow');
table.append(write_link(employees[i])); // function that displays the data
}
if(pages){
$('#pagination').html(pages);
}
}else{
$('#er_tab_label').html('<b>No employees with issues yet.</b>');
}
});
table.html('');
}
then this part calls the function and display another updated content
$('#refresh_btn').on('click', function(e){
e.preventDefault();
var tab = $('#tab').val();
if(tab == 'er'){
EmployeeIssues();
}
});
What should I do to display the content without any blinking effect?
thanks :-)
This section might be the issue :
if(employees){
$('#er_tab_label').html('<b>Employees with Issues</b>');
for (var i = 0; i < employees.length; i++) {
$('#table_er').fadeIn('slow');
table.append(write_link(employees[i])); // function that displays the data
}
if(pages){
$('#pagination').html(pages);
}
} else ...
It seems you're asking table_er to fade in once per run of the loop whereas s there can only be one such table, you only need to do it once ?
first try re-arringing it like this:
if(employees){
$('#er_tab_label').html('<b>Employees with Issues</b>');
$('#table_er').hide(); // hide it while we add the html
for (var i = 0; i < employees.length; i++) {
table.append(write_link(employees[i])); // function that displays the data
}
$('#table_er').fadeIn('slow'); // only do this after the table has all its html
if(pages){
$('#pagination').html(pages);
}
} else ....
Another possibility is that you're running through a loop and asking jquery to do stuff while the loop is running. It might be better to work out the whole HTML for the new page data in a string and then get the screen to render it in one line. I cna't do this for you as I don't know what's in write_link etc but something like this ..
if(employees){
$('#er_tab_label').html('<b>Employees with Issues</b>');
var sHTML ="";
$('#table_er').hide(); // hide it while we add the html
for (var i = 0; i < employees.length; i++) {
sHTML+=write_link(employees[i]); // maybe this is right ? if write_link returns an HTML string ?
}
table.append(sHTML); // add the HTML from the string in one go - stops the page rendering while the code is running
$('#table_er').fadeIn('slow'); // now show the table.
if(pages){
$('#pagination').html(pages);
}
} else ...
I found this JS-Countdown Script at JSFiddle.
EDIT:
I'm using the code of rafaelcastrocouto now, which is nearly perfect. I wanted a 10-seconds JQuery Countdown-Script with an interval that resets the countdown timer at 5 seconds and starts over again and again, but only for a specific class with a specific id on the whole HTML page. If it drops to 0, the countdown should stop. Also I want to reset specific counters to 10.
It's about a WebSocket that refreshes every second and depending on the data I get for specific counters I want to reset them or they should count down to zero.
New JSFiddle: http://jsfiddle.net/alexiovay/azkdry0w/4/
This is how I solved with jquery and native setInterval...
var setup = function(){
$('.count').each(eachSetup);
};
var eachSetup = function(){
var count = $(this);
var sec = count.data('seconds') ;
count.data('count', sec);
};
var everySecond = function(){
$('.count').each(eachCount);
};
var eachCount = function(){
var count = $(this);
var s = count.data('count');
count.text(s);
s--;
if(s < 0) {
s = count.data('seconds');
}
count.data('count', s);
};
setup();
setInterval(everySecond, 1000);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<p class="count" data-seconds="5"></p>
<p class="count" data-seconds="10"></p>
<p class="count" data-seconds="15"></p>
You have sever errors in code, e.g.
setTimeout(cd_go(id), 1000); - should point to function reference not to function execution. setTimeout also returns the timeout id. You must past that id to clearTimeout
clearTimeout(this); it should take id instead of global object (window) or undefined if you are working in strict mode
loop = setInterval(function(id) { … } - id points to undefinded as you are not passing any value for it
I'm attempting to run multiple animations (slideshows of sorts) on one page, but the code is only working for one of the (in my case) 3 slideshows that are actually present.
The issue is not with the animation but with the actual initialisation and running of functions (explained better below by looking at the code):
The HTML:
<div class="someclass1" rel="slideshow" type="fade" duration=8500>
<div class="wrapper">...</div>
<div class="wrapper">...</div>
</div>
<div class="someclass2" rel="slideshow" type="slide" duration=4000>
<div class="wrapper">...</div>
<div class="wrapper">...</div>
</div>
<div class="someclass3" rel="slideshow" type="fade" duration=5000>
<div class="wrapper">...</div>
<div class="wrapper">...</div>
</div>
jQuery:
$(function() {
var plays = [];
var duration = 0;
var targets = [];
var t = "";
var $obs = $('div[rel="slideshow"]')
for(var x = 0; x < $obs.length; x++){
$obs.eq(x).children('.wrapper').eq(0).addClass('active');
$obs.eq(x).children('.wrapper').css({opacity: 0.0});
$obs.eq(x).children('.active').css({opacity: 1.0});
$obs.eq(x).children('.navigation a.slide-buttons').eq(0).addClass('current');
// Set duration
duration = $obs.eq(x).attr('duration');
// Set target
targets = $obs.eq(x).attr('class').split(' ');
t = '';
for(var i=0; i<targets.length; i++){
t += '.' + targets[i];
}
if($obs.eq(x).attr('type')==='fade'){
plays[x] = setInterval(function(){fadeSwitch(t);}, duration);
}
else if($obs.eq(x).attr('type')==='slide'){
plays[x] = setInterval(function(){slideSwitch(t);}, duration);
}
}
});
Through testing, I have shown that the loop runs successfully and passes the appropriate target and duration to either fadeSwitch or slideSwitch for all 3 runs of the loop.
fadeSwitch and slideSwitch are identical except for the animation part, for example:
function fadeSwitch(target) {
var $active = $(target+' .active');
if ( $active.length === 0 ){ $active = $(target+' .wrapper:first');}
var $next = $active.next('.wrapper').length ? $active.next('.wrapper')
: $(target+' .wrapper:first');
// FADE ANIMATIONS
$active.animate({opacity : 0.0}, 500, function() {
$active.addClass('last-active');
});
$next.animate({opacity: 1.0}, 500, function() {
$active.removeClass('active last-active');
$next.addClass('active');
});
}
However this function will run only using the last found target (i.e t = '.someClass3'). Even though by placing console.log alerts in the setInterval functions I know that it is applying the correct variables.
e.g.
plays[0] = setInterval(function(){fadeSwitch('.someclass1');}, 8500);
plays[1] = setInterval(function(){fadeSwitch('.someclass2');}, 4000);
plays[2] = setInterval(function(){fadeSwitch('.someclass3');}, 5000);
Yet as I have tried to (badly) explain, if I place a console.log inside of fadeSwitch to test what is being passed as the target when it runs (remember it is set to run after an interval, so by the time the .someClass1 function runs for the first time, the plays[] array is full and finished) the log shows that the target is always .someClass3 and it never succesfully runs for anything else but that last entered target.
Any suggestions or help is greatly appreciated.
Thank you.
The value of t is being "closed over" by your anonymous functions when you call setInterval. For every iteration of your loop you create a new anonymous function, and like you said, at the time t has the right value.
The problem is that by the time each function executes t's value has changed (it will hold the last value of the loops), and all three anonymous functions refer to the same t variable (that is the nature of a closure and the lexical scoping of javascript). The quick fix is to give each anonymous function the right value and not a reference to t:
Change this:
plays[x] = setInterval(function(){fadeSwitch(t);}, duration);
to this:
plays[x] = setInterval((function(t2){ return function(){ fadeSwitch(t2); }; })(t), duration);
And obviously the same for the same line with slideSwitch.
Another thing I felt I should point out: You're using invalid attributes in your html, consider finding an alternative, like hidden embedded markup (e.g. <div class="duration" style="display:none">5000</div>), or class names, or html5 data attributes, instead of <div duration=5000>