How to do Threading in Javascript - javascript

So I have a large JSON object i'm returning from the server, then building a datatable from it and displaying it on the form. This usually takes a few seconds.. so I was thinking of a loading bar.
I have the logic behind the loading bar, however the loop that builds the hmtl data is locking down the browser and I cannot call out to the element i need to update.
Here is my function to do this:
function buildDataTable(db_table, container_id) {
var $pb = $("<div id=\"progress-bar\"></div>");
$(container_id).html($pb);
$pb.progressbar({
value: 0
});
$.post("post location", {
view: "all"
}, function (data) {
var headers = "";
var contents = "";
var jsonObject = $.parseJSON(data);
var tik = Math.round(jsonObject.length / 100);
for (key in jsonObject[0]) {
headers += "<th>" + key.replace(" ", " ") + "</th>";
}
for (i in jsonObject) {
contents += "<tr>";
for (j in jsonObject[i]) {
contents += "<td class=\"border-right\">" + jsonObject[i][j] + "</td>";
}
contents += "</tr>";
if(Math.round(i/tik) == i/tik) {
/* if I run the alert (between popups) i can see the progressbar update, otherwise I see no update, the progressbar appears empty then the $(container_id) element is updated with the table i've generated */
alert('');
$pb.progressbar("value",i/tik);
}
}
var html = "<table cellpadding=\"5\" cellspacing=\"0\"><thead><tr>" + headers + "</tr></thead><tbody>" + contents + "</tbody></table>";
$(container_id).html(html);
$(container_id).children("table:first").dataTable({
"bJQueryUI": true,
"sScrollX": "100%"
});
});
}

[Added my comment as an answer]
JavaScript is single threaded. You'll have to break your work up into pieces and call them in sequence using "setTimeout" to allow the GUI to update during processing (in between your calls) but even then the browser will still seem somewhat unresponsive.

You can try using WebWorker: https://developer.mozilla.org/en/DOM/Worker
Thus worker are executed in parallel of the main thread, you cannot exactly achieve multi-threading using workers: you cannot modify the UI from a worker.
You can maybe create your grid as a string in a worker and when the worker finish, append it where you want.

If you do all the building of the Database in a setTimeout, the rest of the page should be responsive.
You can construct the html elements in that function, and when it is ready, attach it to the DOM. You will also have to call functions or send events to update the display of the progress bar.
Edit after comment:
This will run in background and won't affect responsiveness of the page:
window.setTimeout(function() {buildDataTable(db_table, container_id)}, 0);
You already update your progressbar from your function, so this should do it.
However, you might want to decouple the code generating the datatable from the code updating the progressbar.

So it appears the only clean way to do this in my application is to process the json on the server and build the html there. Then return the html to the browser via $.post()
The progress bar will be lost. however I can use a infinite loading gif...

Related

NVD3 chart in React app draws html that do not ever then disappear from the page

I implemented an NVD3 scatter chart in a React app and when I am hovering over it the tooltip is shown which sometimes does not go away:
Basically the cursor was already moved away from the chart, but these two labels just got stuck there and now if I navigate to the other page they stay there:
The bug is definitely related to the chart being re-drawn sometimes. I suppose that the previously drawn chart is re-drawn by the new one and so loses the handle to the tooltip.
I tried controlling the bug with shouldComponentUpdate() function but was just able to reduce the occurrence of the bug, but it is still happening.
Then, I tried to find the html for the drawn tooltips, so that I could write code to just find them and delete from the page, but I was not able to with Google Chrome tools. Somehow they are no more in html but are shown on the page...
The tooltip overriding code looks like that:
var that = this;
chart.tooltip.contentGenerator(function (d) {
//var html = "<div>";
var html = "";
d.series.forEach(function(elem){
Object.keys(data_obj).forEach(function(key_1) {
var outer_obj = data_obj[key_1];
if (outer_obj["key"] === elem.key) {
if (that.props.type === "main") {
that.showBarChart(elem.key);
html = "";
html += "<p>cluster " + elem.key + "</p>";
------> } else if (that.props.type === "marker") {
html = "";
if (elem.type === "main") {
------> html += "<p>cluster " + elem.key + "</p>";
}
}
/*var expr = outer_obj["values"][0]["expr"];
html += "<p>" + elem.key + "</p>";
html += "<p>x = " + d.value + ", y = " + elem.value + "</p>";*/
}
});
})
//html += "</div>";
return html;
});
I pointed with arrows the code that draws the tooltips. The code above is located within the function createScatterChart() which is called from componentDidUpdate() and componentWillReceiveProps() if certain requirements are met (I am trying to minimize the updates since both it leads to less efficient app which redraws my chart many times and to more of the bug in question)
The code for the scatter chart itself is pretty identical to the one that is here: https://github.com/novus/nvd3/blob/master/examples/scatterChart.html
Maybe it is possible to somehow optimize this code, so that the first time the html for the chart is created, and then it is just updated with the data? Is it not already working like that if I call createScatterChart()? As I understand it is done by the code nv.addGraph, so is there a way to something like nv.findAndUpdateGraph?
Any suggestions would be greatly appreciated.
What I was doing wrong is calling all of the code that creates the chart every time any update happens. I fixed it by just updating the data without calling nv.addGraph() all the time:
d3.select(node)
.datum(data_func);
chart.update()
I made chart as a global variable, and updated it later on, after it was created with chart.update(). The code looks like that:
// If the chart has not yet been created, run `nv.addGraph()`
if(d3.select(".nvd3.nv-wrap.nv-scatterChart").empty()) {
...
...
} else {
d3.select(node)
.datum(data_func);
chart.update()
}

Why is this jQuery load()-command repeated?

I have gone through many stackoverflow posts and other websites to find a solution to this problem, but none of the solutions i could find either fit my problem or just straight up didn't work.
Im using javascript and jQuery (and some Java for background work) to build a page on a website. The page contains a table with data (the data is handled by Java) and i want to have that table refresh every 10 seconds. That works.
But now i also want to highlight all the cells that have changed values in them. For that, as you see in my code snippet, i just simply turn the background of those cells black. That works too.
My problem is that the color only changes for a split second before changing back to standard white. Using the console and playing around a bit i was able to find out that the table is actually reloaded twice, which to me must mean the load-command is executed twice.
window.setInterval(function () {
if (frozen == false) {
$('table').load(document.URL + ' table');
var elem = document.getElementById("freezebtn"); //This is a button generated by Java Code
elem.style.background = getRandomColor();
var tab = document.getElementsByTagName('table').item(1);
var l = tab.rows.length;
for (var i = 0; i<l; i++) {
var tr = tab.rows[i];
var cll = tr.cells[1];
var check = tr.cells[0];
if(check.innerText === "getAsk") {
var valAsk = cll.innerText;
var ask = Number(valAsk);
if (ask != loadPreviousAsk()) {
console.log("TELEFONMAST!");
cll.style.backgroundColor = "#000000";
}
}
//Do this for every other Updateable Cell
...
}
cachePreviousValues(someValues,thatINeed,To,BeCached);
}
My Question is, what is/could be causing the repeat of the load command (or why won't the cells stay black at all)?
Is there a better way of achieving what im trying to do?
Could my code sample be optimized in any other way?
$.fn.load() is async (by default). To handle any logic regarding loaded data, you can use the complete callback:
$('table').load(document.URL + ' table', function(){/* your logic regarding loaded data here*/);

Apply Masonry on content from Facebook API

I have been trying to implement Masonry into this project for ages now, and I would be totally happy to have someone who can help me out. The internet is full of similar problems with masonry, but none of the given solutions could help me fix this.
Basically, I am pulling reviews from my Facebook page via the Open Graph API with the following code. The Facebook response data is converted from json to an array and then stored in the fb_reviews variable for later use. This one works as desired:
[...]
function (response) {
if (response && !response.error) {
//convert to array
fb_reviews = Object.keys(response).map(function (_) {
return response[_];
});
//only store needed data of fb_reviews[0]
fb_reviews = fb_reviews[0];
reviews();
}
;
});
[...]
This works perfectly. As you can see, the following reviews() function is called next to display the Facebook data, and this is where I would like masonry to kick in:
// DISPLAY REVIEWS
var print2 = "";
var reviews = function() {
var z = fb_reviews.length - 1;
var print2 = "";
for (i = 0; i < z; i++) {
//put together div container for every review object
print2 += '<div class="review" id="review-' + i + '"><div class="meta">' + fb_reviews[i].reviewer.name + ' rated ' + fb_reviews[i].rating + '/5</div><blockquote>' + fb_reviews[i].review_text + '</blockquote></div>';
}
print2 = $(print2);
// still works!!!
$("#reviews").append(print2);
//until here, everything still works, but the following does no longer work:
$("#reviews").masonry({
itemSelector: '.review'
});
}
;
The content is appended as desired etc., but I have literally tried any possible way to initialize masonry, including using all snippets I could find for appended content, e. g.:
$("#reviews").append( print2).masonry( 'appended', print2 );
There are many more, including reload and layout etc, but this does not make much sense to me, because there is no content in the #reviews-div-container until all Facebook reviews are loaded and appended.
Masonry ALWAYS seems to be called BEFORE the Facebook reviews are appended, no matter where I would position and spread the masonry code snippets.
So what is the best way to get Masonry initialized AFTER the Facebook data is stored and appended?
Thank you so much in advance for your help! Really appreciate your support
Simon

Create CSV from html using flash, javascript, classic asp

Let me start by stating that my end goal is to create an Export to Comma Separated File (.csv) or Export to Excel button on a page of our site. Our site currently has a search mechanism that returns the data in XML. That XML is loaded into a Microsoft.XMLDOM object.
Dim objXMLDom
Set objXMLDOM = Server.CreateObject("Microsoft.XMLDOM")
...
Function LoadXML(strXML)
objXMLDOM.loadXML(strXML)
end function
This XML data is parsed into a variable called myData and is displayed in an HTML Grid using different columns & rows. Like I said above, ideally I would like to add a button to the page that can export to csv or Excel. I've searched around and found that this is not an easy task when the code is in Classic ASP...so I found a semi-work around.
The workaround is to use Downloadify. It's a jquery/flash script that allows you to copy code into the HTML of a DOM element and then pull that data back out all on the client side. It works for the most part...the only problem I'm having is adding in a new line character. I think it gets removed going into HTML and back out again.
Here's the code for how I put data into the element
for (i = 0; i < <%=RowCount %>; i++) {
parent.document.getElementById('data').innerHTML += '\n';
for (j = 0; j < <%=columnCount%>; j++) {
parent.document.getElementById('data').innerHTML += myData[i][j];
parent.document.getElementById('data').innerHTML += ',';
}
};
Here's what I use to pull it back out:
function load() {
Downloadify.create('downloadify', {
filename: function() {
return document.getElementById('filename').value;
},
data: function() {
return document.getElementById('data').value;
},
onComplete: function() { alert('Your File Has Been Saved!'); },
onCancel: function() { alert('You have cancelled the saving of this file.'); },
onError: function() { alert('You must put something in the File Contents or there will be nothing to save!'); },
swf: 'media/downloadify.swf',
downloadImage: 'images/download.png',
width: 100,
height: 30,
transparent: true,
append: false
}
);
}
I really don't know very much Flash, but I've tried putting in different strings ('<br>', '
', even ";:;" ) and replacing them in the data : function part using Flash's replace method, but it seems to hang and never finish processing (i think this is because I needed to put the replace method in a while loop since the replace method only replaces one instance and I have many).
Any help will be appreciated. Thank you in advance.
why don't you have the ASP create a .csv file along with the html page? It seems like that would be a lot easier.
I had a problem like this awhile back when I was using Flash and ASP together.
I remember calling ASP from Flash and it hanging. I THINK the fix was to add a "response.end" to the end of the ASP page. That seemed to tell the server, "Done. Back to Flash now." Sorry I can't be more sure. It was a long time ago and I can't find the code.

Update DOM Elements Immediately in Javascript

For a simple project, I'm trying to display log output in a textarea. I have a log function, and would like the textarea to always be updated immediately whenever log is called. Unfortunately, it seems to only do the update once, and push everything at the same time (I'm using JQuery to set the value of the textarea, if that possibly matters). Here's a basic example:
for (i = 0; i <= 100; i++) {
$("textarea").html($("textarea").html() + "test" + i + "\n");
$("textarea").scrollTop(9999999)
};
This spits out all the text into the textarea at once (note: you can see the results of these examples at this jsfiddle). This basic example is easily remedied by creating a timeout and using recursive function calls:
f = function(i) {
if (i <= 100) {
$("textarea").html($("textarea").html() + "test" + i + "\n");
$("textarea").scrollTop(999999);
setTimeout(function() { f(i+1); }, 0);
}
};
f(1);
This version spits out the text into the textarea one line at a time, which is what I want. But using timeouts and callbacks in this manner does not seem practical in the setting of logging; every time I call log, I would have to provide a callback for all the functionality that I want to ever follow the log call.
Is there any way to achieve the desired effect without callbacks?
I think you might consider using :
$("textarea").val(function(index, value) {
return value + "test" + i + "\n"
});
instead of :
$("textarea").html($("textarea").html() + "test" + i + "\n");
or in general :
$("textarea").val(NEWVAL)
Also noted in the comments of your question, if you want to be able to notice "by eye" all the messages that arrives you'll have to save them in a buffer and have something like (not tested) :
var buffer = []
function log(text) { buffer.push(text) }
setInterval(function(){
if (len(buffer)>0) {
$("textarea").val(function(index, value) {
return value + "\n" + buffer.shift()
});
}
},500)
Browsers generally (Opera is an exception) do JS execution and rendering on the same thread, so while your JS is running there will be no rendering done by th browser, unless you explicitly yield control via a timeout or interval timer.

Categories

Resources