IE8 long running script error when using DataTables - javascript

I have an application that uses the DataTables jQuery library to render content in my target browser IE8. The problem is when I push a big array to be rendered, IE8 sometimes throws up the infamous long running script error.
After profiling the app it seems that the call to __fnAddData in the following code is causing the problem:
if (bUsePassedData) {
for (var i = 0, len = oInit.aaData.length; i < len; i++) {
_fnAddData(oSettings, oInit.aaData[i]);
}
} else if (oSettings.bDeferLoading ||
(oSettings.sAjaxSource === null && oSettings.ajax === null)) {
_fnAddTr(oSettings, $(oSettings.nTBody).children('tr'));
}
I was looking around for solutions and saw Nicholas Zakas' write up here and tons of other solutions that would work if the for loop wasn't inside of an if else if "block". When I tried, on my 1st attempt of many, to wrap it in a setTimeout function it of course didn't work because the 2nd part of the if else if resolves to true.
(oSettings.sAjaxSource === null && oSettings.ajax === null) // true
What is a good solution for this? Thanks in advance.

I think you might split up your function in 3 functions:
Before the if statement.
Processing the oInit.aaData
After the if statement
Here is the code split up in 3 functions:
function beforeIf(){
if (bUsePassedData) {
procesData(oSettings,oInit.aaData.concat());
} else if (oSettings.bDeferLoading ||
(oSettings.sAjaxSource === null && oSettings.ajax === null)) {
_fnAddTr(oSettings, $(oSettings.nTBody).children('tr'));
}
afterIF();
}
function processData(oSettings,arr){
//process in chuncks of 50;
// setTimeout takes a long time in IE
// it'll noticibly slow donw your script when
// only processing one item at the time
var tmp=arr.splice(0,50);
for (var i = 0, len = tmp.length; i < len; i++) {
_fnAddData(oSettings, tmp[i]);
}
if(arr.length!==0){
setTimeout(function(){
processData(oSettings,arr);
},0);
return;
}
afterIf();
}
function afterIf(){
//continue processing
}

Thanks #HMR. You helped to bring me closer to my goal. To solve the problem I worked my code down to this IIFE:
(function processData(oSettings, arr) {
var tmp = arr.splice(0, 50);
tickApp.$orders.dataTable().fnAddData(tmp);
if (arr.length !== 0) {
setTimeout(function () {
processData(oSettings, arr);
}, 0);
}
}(oSettings, oInit.aaData.concat()));
Instead of using the private _fnAddData function I opted for the DataTables public fnAddData (http://datatables.net/ref#fnAddData) function. This way I am able to push 50 rows at a time into the table which is stored in the tickApp.$orders object which I just a reference to my jQuery object that stores the table in memory:
tickApp.$orders = $('#orders');
In another part of my code. They way you had it it was still pushing 1 row at a time instead of the whole 50.
Thanks again.

If you are using ajax to fetch your data, you can override "fnServerData" in your datatables config object. This will allow you to fetch the data to be loaded and then process it however you want.
In my case, I have a generic datatables config object that I use for all my datatables. I override the default fnServerData function with one that passes rows to the datatable in sets of 200 using fnAddData and setTimeout to call the function again until all the data has been processed, finally I call fnDraw to draw the table.
var DEFAULT_CHUNK_SIZE = 200;
function feedDataToDataTableInChunks(startIndex, data, oSettings) {
var chunk = data.slice(startIndex, DEFAULT_CHUNK_SIZE);
oSettings.oInstance.fnAddData(chunk, false);
if((startIndex += DEFAULT_CHUNK_SIZE) < data.length) {
setTimeout(function () {
feedDataToDataTableInChunks(startIndex, data, oSettings);
});
} else {
oSettings.oApi._fnInitComplete(oSettings, data);
oSettings.oInstance.fnDraw();
}
}
var config = {fnServerData: function(){
oSettings.jqXHR = $.getJSON(sSource, aoData)
.done(function (result) {
feedDataToDataTableInChunks(0, result || [], oSettings);
});
}}
I am using datatables version 1.9.4

Related

How do I use data out of chrome.storage.get function?

I'm trying to grab data from chrome extension storage, but I can use them only in this function.
var help = new Array();
chrome.storage.local.get(null, function(storage){
//get data from extension storage
help = storage;
console.log(storage);
});
console.log(help); // empty
Result in console:
content.js:1 content script running
content.js:11 []
content.js:8 {/in/%E5%BF%97%E9%B9%8F-%E6%99%8F-013799151/: "link", /in/adam-
isaacs-690506ab/: "link", /in/alex-campbell-brown-832a09a0/: "link",
/in/alex-davies-41513a90/: "link", /in/alex-dunne-688a71a8/: "link", …}
Async function has won. I wrote my code again and now function is called hundreds time, i can not do this in dirrefent way
code:
console.log("content script running");
var cards = document.getElementsByClassName("org-alumni-profile-card");
var searchText = "Connect";
function check(exi, cards) {
chrome.storage.local.get(null, function(storage) {
for (var key in storage) {
if (storage[key] == "link" && key == exi) {
cards.style.opacity = "0.3";
}
}
});
}
for (var i = 0; i < cards.length; i++) {
var ctd = cards[i].getElementsByClassName(
"org-alumni-profile-card__link-text"
);
var msg = cards[i].getElementsByClassName(
"org-alumni-profile-card__messaging-button-shrunk"
);
if (ctd.length < 1 || msg.length > 0) {
cards[i].style.display = "none";
} else {
var exi = cards[i]
.getElementsByClassName("org-alumni-profile-card__full-name-link")[0]
.getAttribute("href");
check(exi, cards[i]);
}
}
SOLUTION of my problem
I wanted to delete this topic, but I can not, so instead of doing that, I'll put here what I've done finally.
The code above is wrong becouse, it was taking a list of links from website and for each from them script was grabbing a data from a storage... Which was stupid of course. I didn't see a solution which was so easy:
Put all your file's code in this function - it grabs data from storage just once.
I'm so sorry for messing up this wonderfull forum with topic like this.
Hope u'll forgive.
help will return undefined because it is referencing a asynchronous function and not the return value of that function. The content from storage looks to be printed on content.js:8, i.e. line 8.

Javascript function always returns 0

I am writing a function which searches for a value in my IndexedDB and if it finds one, then it should return 1, else it should return 0. The problem is that it always returns 0 though the value exists in a database (variable arr is incremented, but 0 is returned as a result). The code is as follows:
searchAllValues: function(store, type)
{
var arr = 0;
AAA.initDb(function()
{
var obj = {};
AAA.aaaDb.transaction(store).objectStore(store).openCursor().onsuccess = function(store)
{
var storeresult = store.target.result;
if(storeresult.value.value == type ){
arr++;
}else{console.log('value NOT found');}
storeresult ? (obj[storeresult.key] = storeresult.value.value, storeresult["continue"]()) : callback(obj)
}
});if(arr!=0){return 1}else{return 0}
}
EDIT_1:
Ok, I have refactored the code as follows:
addInfo: function(store, type, info)
{
var arr = [];
P4S.p4sPushDb.transaction(store).objectStore(store).openCursor().onsuccess = function(store)
{
var storeresult = store.target.result;
console.log('value of storeresult==>'+storeresult.value.value);
if(storeresult.value.value == info)
{
arr.push(storeresult.value.values);return;//If it finds something it should stop here, no more search or anything to be done
}else
{
console.log('continuing..');
storeresult['continue']();
}
console.log('arr length==> '+arr.length);//If it finds nothing after the looping the whole DB, I want it to print this statement, only once (to send it to my DB actually but sending code is omitted for simplicity).
}
}
Instead I get console.log('arr length==>') statement executed 2 times, for every key in my object store (there are 2 of them actually). So it is doing the code when it finds nothing AND when it finds the value in the DB. Any ideas how to fix it?
Any ideas would be welcome, Thank You
Because by the time the line if(arr!=0){return 1}else{return 0} is executed the db transaction is not complete and value of arr is 0. Though never used indexedDb, but webSql do take some extra miliseconds to read from DB.
Try to put your return logic inside the onsuccess function where you incrementing the arr. You can simply test it by printing value of arr just before your return logic
You need to learn about how to write asynchronous javascript. There are several other indexedDB questions where there are explanations as to why this happens.
For example: Uncaught TypeError: Cannot read property 'transaction' of null with an indexeddb
function addInfo(store, type, info, next)
{
var arr = [];
P4S.p4sPushDb.transaction(store).objectStore(store).openCursor().onsuccess = function(store)
{
var storeresult = store.target.result;
console.log('value of storeresult==>'+storeresult.value.value);
if(storeresult.value.value == info)
{
arr.push(storeresult.value.values);
next(arr);//If it finds something it should stop here, no more search or anything to be done
}else
{
console.log('continuing..');
storeresult.continue();
}
console.log('arr length==> '+arr.length);//If it finds nothing after the looping the whole DB, I want it to print this statement, only once (to send it to my DB actually but sending code is omitted for simplicity).
}
}
Added an extra parameter called 'next' to your the addInfo function.
'next' param is the very last function called if the condition (storeresult.value.value == info) is true.
The next function which you create, will use the 'arr' variable and do whatever with it
your 'return statement' doesnt work the sameway with asynchronous functions, would highly advice you search up asynchronous functions to get a gist of how its different to regular functions
This is how you would call your newly edited function:
addInfo(store,type,info,function(arr){
//do something with arr
})
Note that you have a potential state which would break your code
what if the cursor reaches the end of its iterations and never meets that condition (storeresult.value.value == info). storeresult would be null, and the check for the condition (null.value.value == info) will throw an exception
correction:
function addInfo(store, type, info, next)
{
var arr = [];
P4S.p4sPushDb.transaction(store).objectStore(store).openCursor().onsuccess = function(store){
var storeresult = store.target.result;
if(storeresult){
if(storeresult.value.value == info){
arr.push(storeresult.value.values);
next(arr);
}else storeresult.continue();
}else next();
}
}
And when you call it you handle the scenario whereby arr == null
addInfo(store,type,info,function(arr){
if(arr){
//do something with arr
}else{
//do somethingelse when arr == null
}
})

Next iteration of $.each when received AJAX-content

The question has been asked before, but it is almost four years ago and maybe there is a better solution.
I have a $.each-loop where sometimes additional data is being fetched via ajax.
I am bulding an object with the fetched data, after the loop there is a function that generates HTML from the object. The problem is that the loop finishes before the ajax data arrives. If I place an alert in the HTML-generating-function the content is loading properly.
I am searching for a solution that calls the HTML-generator-function only when the loop and all ajax calls are finished. Maybe it is a solution to count the started Ajax requests and wait if all of them are finished?
I believe jQuery deferred is the right solution for me but I do find only examples where everything stays inside the loop. Can someone help?
I have stripped down my code to the most important things:
//goes through each testplace -->main loop
$.each(jsobject, function(key, value)
{
//build object together...
for (var i = 0, numComputer = jenkinsComputer.contents.computer.length; i < numComputer; i++)
{
//If the testplace is in both objects then fire AJAX request
if (jenkinsComputer.contents.computer[i].displayName == key) //<<<This can happen only once per $.each loop, but it does not happen every time
{
//next $.each-iteration should only happen when received the JSON
var testplaceurl = jenkinsComputer.contents.computer[i].executors[0].currentExecutable.url;
$.when($.getJSON("php/ba-simple-proxy.php?url=" + encodeURI(testplaceurl) + "api/json?depth=1&pretty=1")).done(function(jenkinsUser)
{
//build object together...
});
}
}
}); //End of main Loop ($.each)
generateHTML(builtObject);
It would be great if someone could give me an advice how to do it.
I would do something like this:
var thingstodo = $(jsobject).length;
var notfired = true;
$.each(jsobject, function(key, value)
{
//build object together...
for (var i = 0, numComputer = jenkinsComputer.contents.computer.length; i < numComputer; i++)
{
//If the testplace is in both objects then fire AJAX request
if (jenkinsComputer.contents.computer[i].displayName == key) //<<<This can happen only once per $.each loop, but it does not happen every time
{
//next $.each-iteration should only happen when received the JSON
var testplaceurl = jenkinsComputer.contents.computer[i].executors[0].currentExecutable.url;
$.when($.getJSON("php/ba-simple-proxy.php?url=" + encodeURI(testplaceurl) + "api/json?depth=1&pretty=1")).done(function(jenkinsUser)
{
//build object together...
thingstodo--;
if(thingstodo === 0 && notfired){
notfired = false;
generateHTML(buildObject);
}
});
}else{
thingstodo--;
}
}
}); //End of main Loop ($.each)
if(thingstodo === 0 && notfired){
generateHTML(buildObject);
}
This is short untested example about the solution. I hope this to give you idea.
// I guess that jsobject is array ..
// if it is not object you can use something like:
// var keys = Object.getOwnPropertyNames(jsobject)
(function () {
var dfd = $.Deferred();
function is_not_finished() {
return jsobject.length > 0 && jenkinsComputer.contents.computer.length > 0;
}
(function _handleObject() {
var key = jsobject.shift();
var displayName = jenkinsComputer.contents.computer.shift().displayName;
if (displayName == key) //<<<This can happen only once per $.each loop, but it does not happen every time
{
//next $.each-iteration should only happen when received the JSON
var testplaceurl = jenkinsComputer.contents.computer[i].executors[0].currentExecutable.url;
$.getJSON("php/ba-simple-proxy.php?url=" + encodeURI(testplaceurl) + "api/json?depth=1&pretty=1").done(function(jenkinsUser)
{
//build object together...
if(is_not_finished()) {
setTimeout(_handleObject,0);
} else {
dfd.resolve();
}
});
} else if (is_not_finished()) {
setTimeout(_handleObject,0);
} else {
dfd.resolve();
}
}());
return dfd.promise();
}()).done(function () {
generateHTML(builtObject);
});

using slice in angularjs array

In my angularjs app, i need to manually remove or add old/new data from a data array (service is executed in a loop). For remove, i use slice(); but there is a problem: the item is correctly removed but execVerif_distant(); is not executed for the next item. With my actual code, execVerif_distant(); is executed for each item only half a time. For example, if i need to remove entire array, only half is removed.
// start the loop, search in local datas
angular.forEach($scope.seaDocument.datas.cages, function(itemLocalCages) {
execVerif_local(itemLocalCages.url);
});
function execVerif_local(identifiant) {
var iterSearch_local = 0;
angular.forEach(responseZS, function(itemDistantCages) {
if (itemDistantCages.url == identifiant) {
iterSearch_local++;
}
});
// if we not find the local datas in distant datas
if (iterSearch_local == 0) {
// verifItem(); call
verifItem('remove', identifiant);
}
}
// verifItem();
function verifItem(action, url) {
if (action == 'remove') {
var iIndex = -1;
angular.forEach($scope.seaDocument.datas.cages, function(itemLocalCages) {
iIndex++;
if (itemLocalCages.url == url) {
$scope.seaDocument.datas.cages.splice(iIndex,1);
}
});
} else {
// do nothing
}
}
what's wrong ?
The problem is that the foreach is iterating over the same object you are removing things from. To avoid this behavior clone the object you are iterating before the loop and work with them as separate:
// ... code
var arrCopy = $scope.seaDocument.datas.cages.slice(); //this will create a deep copy.
angular.forEach(arrCopy, function(itemLocalCages) {
iIndex++;
if (itemLocalCages.url == url) {
$scope.seaDocument.datas.cages.splice(iIndex,1);
}
});
//... more code

Load dictionary file with ajax and don't crash iPhone Mobile Safari

I have a web application where I load (via ajax) a dictionary file (1MB) into the javascript array. I found the reason why the Mobile Safari crashes after 10 seconds. But now what I'm wondering is how do I get around this issue?
On the link above the answer suggest using setInterval, but this would mean I would have to have a dictionary file chunked into pieces and have them loaded one by one. This surely could be done, but I would have to make a lot of chunks taking into account the internet speed and too many requests would take forever for the page to load (and if I make the chunks too big it could happen that some mobile users wouldn't be able to download the chunk in a given 10second period).
So, my question is: has anyone encountered this kind of problem and how did you go about it? A general push in the right direction is appreciated.
edit:
This is the js code which I use to load the dictionary:
var dict = new Trie();
$.ajax({
url: 'data/dictionary_342k_uppercase.txt',
async: true,
success: function (data) {
var words = data.split('\n');
for (var i = words.length - 1; i >= 0; i--) {
dict.insert(words[i]);
}
},
error: function(){
$('#loading-message').text("Problem s rječnikom");
}
});
Trie.js:
function Trie () {
var ALPHABET_SIZE = 30;
var ASCII_OFFSET = 'A'.charCodeAt();
this.children = null;
this.isEndOfWord = false;
this.contains = function (str) {
var curNode = this;
for (var i = 0; i < str.length; i++) {
var idx = str.charCodeAt(i) - ASCII_OFFSET;
if (curNode.children && curNode.children[idx]) {
curNode = curNode.children[idx];
} else {
return false;
}
}
return curNode.isEndOfWord;
}
this.has = function (ch) {
if (this.children) {
return this.children[ch.charCodeAt() - ASCII_OFFSET] != undefined;
}
return false;
}
this.next = function (ch) {
if (this.children) {
return this.children[ch.charCodeAt() - ASCII_OFFSET];
}
return undefined;
}
this.insert = function (str) {
var curNode = this;
for (var i = 0; i < str.length; i++) {
var idx = str.charCodeAt(i) - ASCII_OFFSET;
if (curNode.children == null) {
curNode.children = new Array(ALPHABET_SIZE);
curNode = curNode.children[idx] = new Trie();
} else if (curNode.children[idx]) {
curNode = curNode.children[idx];
} else {
curNode = curNode.children[idx] = new Trie();
}
}
curNode.isEndOfWord = true;
return curNode;
}
}
This is a very common issue once you start doing processing in JS. If the Mobile Safari issue is the cause then what you want to do is figure out where the CPU time is going here.
I'm assuming it's the dict.insert() loop and not the data.split() call (that would be a bit more difficult to manage).
The idea here is to split up the dict.insert() loop into functional blocks that can be called asynchronously in a sequenced loop (which is what the setupBuildActions function does). After the first block each subsequent block is called via setTimeout, which effectively resets the function-time counter in the JS runtime (which seems to be what's killing your process).
Using the Sequencer function means you also keep control of the order in which the functions are run (they always run in the sequence they are generated in here and no two or more functions are scheduled for execution at the same time). This is much more effective than firing off thousands of setTimeout calls without callbacks. Your code retains control over the order of execution (which also means you can make changes during execution) and the JS runtime isn't overloaded with scheduled execution requests.
You might also want to check the node project at https://github.com/michiel/sequencer-js for more sequencing examples and http://ejohn.org/blog/how-javascript-timers-work/ for an explanation on setTimeout on different platforms.
var dict = new Trie();
// These vars are accessible from all the other functions we're setting up and
// running here
var BLOCKSIZE = 500;
var words = [];
var buildActions = [];
function Sequencer(funcs) {
(function() {
if (funcs.length !== 0) {
funcs.shift()(arguments.callee);
}
})();
}
// Build an Array with functions that can be called async (using setTimeout)
function setupBuildActions() {
for (var offset=0; offset<words.length; offset+= BLOCKSIZE) {
buildActions.push((function(offset) {
return function(callback) {
for (var i=offset; i < offset + BLOCKSIZE ; i++) {
if (words[i] !== null) { // ugly check for code brevity
dict.insert(words[i]);
}
}
// This releases control before running the next dict.insert loop
setTimeout(callback, 0);
};
})(offset));
}
}
$.ajax({
url: 'data/dictionary_342k_uppercase.txt',
async: true,
success: function (data) {
// You might want to split and setup these calls
// in a setTimeout if the problem persists and you need to narrow it down
words = data.split('\n');
setupBuildActions();
new Sequencer(buildActions);
},
error: function(){
$('#loading-message').text("Problem s rječnikom");
}
});
Here's an example using setTimeout to defer the actual insertion of words into your trie. It breaks up the original string into batches, and uses setTimeout to defer processing of inserting each batch of words. The batch size in my example is 5 words.
The actual batch insertion happens as subsequent event handlers in the browser.
It's possible that just breaking the words up into batches might take too long. If you hit this problem, remember you can chain setTimeout() calls, eg iterating for a while then using setTimeout to schedule another event to iterate over some more, then setTimeout again, etc.
function addBatch(batch)
{
console.log("Processing batch:");
for (var i = 0; i < batch.length; i++)
console.log(batch[i]);
console.log("Return from processing batch");
}
var str = "alpha\nbravo\ncharlie\ndelta\necho\nfoxtrot\n" +
"golf\nhotel\nindia\njuliet\nkilo\nlima\n" +
"mike\nnovember\noscar\npapa\nquebec\n" +
"romeo\nsierra\ntango\nuniform\n" +
"victor\nwhiskey\nxray\nyankee\nzulu";
var batch = []
var wordend;
for (var wordstart = 0; wordstart < str.length; wordstart = wordend+1)
{
wordend = str.indexOf("\n", wordstart);
if (wordend < 0)
wordend = str.length;
var word = str.substring(wordstart, wordend);
batch.push(word);
if (batch.length > 5)
{
setTimeout(addBatch, 0, batch);
batch = [ ];
}
}
setTimeout(addBatch, 0, batch);
batch = [ ];

Categories

Resources