I have this Javascript class (cut down to necessary parts):
function DigitalChannel ($xmlDoc) {
var self = this;
// Parse definition from $xmlDoc
self.firstRender = function (rootElem) {
var html = ""; // Build HTML string to display object information
rootElem.html (rootElem.html () + html); // Make above HTML visible
self.paper = Raphael ("digDot" + self.numIndex, 54, 38);
self.svgImage = self.paper.circle (30, 20, 15).attr ({
"stroke-width": "2",
"stroke": "#000000",
"fill": "#00FF00"
});
}
self.updateStatus = function (newState) {
self.state = newState;
if (self.state !== self.normalState) {
self.svgImage.attr ("fill", "#FF0000");
}
else {
self.svgImage.attr ("fill", "#00FF00");
}
}
}
Now, please assume that all variables used in my methods were initialized properly from XML data (I have verified that this is the case with Firebug). A digital channel can either have a value of 0 or 1 and it can have a normal value of 0 or 1. If the current value matches the normal value, I want the circle that gets drawn to be green, otherwise red. Then I have this class:
function PageManager () {
var self = this;
self.base_url = "http://" + location.hostname + "...";
self.digital_channels = new Array ();
var fullPath = self.base_url + "...";
$.ajax (fullPath,
{
type: "post",
cache: true,
context: self,
success: function (data) {
$xmlDoc = $($.parseXML (data));
$xmlDoc.find ("channel").each (function () {
self.digital_channels.push (new DigitalChannel ($(this)));
});
}
});
self.firstRender = function () {
for (i in self.digital_channels) {
self.digital_channels[i].firstRender ($("#rootElem"));
}
}
self.updateDigitals = function () {
var fullPath = self.base_url + "...";
$.ajax (fullPath,
{
type: "post",
cache: true,
context: self,
success = function (data) {
var digital_idx = 0;
var mask = 0x0001;
$xmlDoc = $($.parseXML (data));
$xmlDoc.find ("digital_inputs").each (function () {
var bits = parseInt ($.trim ($(this).text ()));
for (i = 0; i < 32; i++) {
self.digital_channels[digital_idx].updateState (bits & mask);
bits = bits >>> 1;
digital_idx += 1;
}
});
}
});
}
}
So at this point, I have defined everything I need, and then I do this:
var pageManager = new PageManager ();
pageManager.firstRender ();
pageManager.updateDigitals ();
Again, assume that everything happens in the proper order. The actual classes are more complicated and nothing will get called before the right time. I have used Firebug to step through everything and what I have right now is a page of 64 green dots after pageManager.firstRender ();, which is what I want. When I call pageManager.updateDigitals ();, I should see the first 32 dots stay green and the next 32 dots turn red. I have seen everything code-wise internally go OK, even to the point where DigitalChannel.updateState (newState); actually changes the color of svgImage circle; however, only the very last dot visually changes color. I apologize for such a long question, but does anybody have any idea why?
Could it be because self.normalState is not defined in DigitalChannel?
It turned out that it was a timing issue. Creating the HTML that would hold the Raphael paper and then immediately creating the paper caused something to get messed up, so I added a delay in the creation of the papers and everything is working as expected now.
Related
I have a function which works fine when its ran synchronously, but as soon as I make it asynchronous it fails to work because it is returning true before all images are loaded.
Here is the original function which existed:
if (window.num_alt > 0) {
var div = document.getElementById('productImageLarge');
if (div) {
var html = '';
colour = colour || '';
var tmp = getTemplate('image_holder');
if (!tmp) { tmp = 'image_holder is missing<br>'; }
// num_alt same number as images - use this for the loop
for (var i=0; i<num_alt-0+1; i++) {
var tmp1 = tmp;
tmp1 = tmp1.replace(/\[xx[_ ]image\]/ig, imagename+colour+alt_ext[i]);
tmp1 = tmp1.replace(/\[xx[_ ]img_no\]/ig, i);
html += tmp1;
// at the end of the loop
if (i == num_alt) {
imagesDone = true;
}
}
div.innerHTML = html;
}
}
return imagesDone;
Basically it takes the num_alt images set in a variable (set to 8) and fills in a JS template. Once its at the end of the loop I have another function on an interval testing whether imagesDone == true. Once it is set to true, the function fires and the image slider kicks in.
I wanted to lazy-load the images, and for some reason the current function wouldn't allow me to do this without trying to load images that return a 404. So I converted the function to use promises which calls itself until all images are processed (removed the for loop) and this has worked for a while, but its using async:false....
var imagesDone = false;
//console.log("Create Image");
if (window.num_alt > 0) {
var div = document.getElementById('productImageLarge');
if (div) {
var html = '';
colour = colour || '';
var tmp = getTemplate('image_holder');
if (!tmp) { tmp = 'image_holder is missing<br>'; }
var i = 0;
var promises = [];
function ajax_data() {
promises.push($.ajax({
url: thisUrl+'product-image.php?size=large&image=/'+imagename+colour+alt_ext[i]+'.jpg',
method: 'post',
data: promises,
async : false,
success: function (resp) {
if (i<=num_alt) {
var tmp1;
tmp1 = tmp;
tmp1 = tmp1.replace(/\[xx[_ ]image\]/ig, imagename+colour+alt_ext[i]);
tmp1 = tmp1.replace(/\[xx[_ ]img_no\]/ig, i);
html += tmp1;
div.innerHTML = html;
i++;
ajax_data();
}
}
}))
}
Promise.all([ajax_data()])
.then([imagesDone = true])
.catch(e => console.log(e));
}
}
return imagesDone;
If I remove the async:false, imagesDone is returned too soon and the slider function kicks in to early. Can anyone help me understand how to make this work in a synchronous / chained fashion? I've been trying for a while but just can't seem to get it to work.
Thanks in advance.
It's not clear what you want to do, your code looks like part of a function that already doesn't do what you want it to do. Maybe the following will work for you:
var Fail = function(reason){this.reason=reason;};
var isFail = function(o){return (o||o.constructor)===Fail;};
var isNotFail = function(o){return !isFail(0);};
//...your function returning a promise:
var tmp = getTemplate('image_holder') || 'image_holder is missing<br>';
var div = document.getElementById('productImageLarge');
var html = '';
var howManyTimes = (div)?Array.from(new Array(window.num_alt)):[];
colour = colour || '';
return Promise.all(//use Promise.all
howManyTimes.map(
function(dontCare,i){
return Promise.resolve(//convert jQuery deferred to real/standard Promise
$.ajax({
url: thisUrl+'product-image.php?size=large&image=/'+imagename+colour+alt_ext[i]+'.jpg',
method: 'post',
data: noIdeaWhatYouWantToSendHere//I have no idea what data you want to send here
// async : false //Are you kidding?
})
).catch(
function(error){return new Fail(error);}
);
}
)
).then(
function(results){
console.log("ok, worked");
return results.reduce(
function(all,item,i){
return (isFail(item))
? all+"<h1>Failed</h1>"//what if your post fails?
: all+tmp.replace(/\[xx[_ ]image\]/ig, imagename + colour + alt_ext[i])
.replace(/\[xx[_ ]img_no\]/ig, i);
},
""
);
}
).then(
function(html){
div.innerHTML=html;
}
)
There are many issues in the code above, indicating that you may need to understand better how Promises work. Your function should probably return the Promise so that the caller handle the asynchronicity on their side.
So either use:
return Promise.all(promises);
or:
return Promise.all(promises).then(function() { return true; })
I have a javascript function that has about 4 ajax requests in it. It typically takes less than a second to run. However, I'm working on the error handling now and was wondering. How long, in seconds, should I allow my javascript function to try to keep working until I manually cancel it and allow the user to try again?
Here's what the function in question looks like. (not everything is there, but it could potentially have (1000*5000*3)+(70)+(1000)+(6)+(2500) bytes being sent)
function saveChanges(bypassDeckSave){
// bypassDeckSave = undefined - does not bypass
showSavedNotification_check = 1;
if(userid != 0){
//values in database
var subjectID = $('.lib_folder_id').val(),
folderID = $('.lib_subject_id').val();
if(subjectID == 0 || folderID == 0){//if database values null, ask for some
console.log("db deck location not saved, asked for it");
//values to set to
var setFolderID = $('.libDeckLocationModifierDiv .folders li.on').val(),
setSubjectID = $('.libDeckLocationModifierDiv .subjects li.on').val();
if(isNaN(setFolderID) || isNaN(setSubjectID) ||
setFolderID == 0 || setSubjectID == 0)
{
openDeckLocationDiv();
showSavedNotification_check = 0;
return;
}
}
}
var deck_id = $('.deck_id').val();
if(deck_id == 0){
// create a new deck
createDeckThenSave();
return;
}
if(userid != 0){
//values in database
var subjectID = $('.lib_folder_id').val(),
folderID = $('.lib_subject_id').val();
if(subjectID == 0 || folderID == 0){//if database values null, ask for some
//values to set to
saveDeckLocation();
}
}
// removes empty rows
$('.editMain li').each(function(){
var one = $(this).find('.text1').val(),
two = $(this).find('.text2').val();
if(one == "" && two == ""){
//remove this row and remove value from updateSaveArray + add to delete array
var currentval = $(this).val();
var rowid = ".row_"+currentval;
updateSaveArray = jQuery.grep(updateSaveArray, function(value) {
return value != currentval;
});
$(rowid).remove();
updateDeleteArray[updateDeleteArray.length] = currentval;
}
});
if(bypassDeckSave == undefined){
// save deck info to db
var deckname = $('.editDeckNameInput').val(),
cardCount = $('.editMain li.mainLi:visible').length,
deckTermLanguage = $('.selector.one select').val(),
deckDefinitionLanguage = $('.selector.two select').val(),
deckThirdBoxLanguage = $('.selector.three select').val(),
deckDescription = $('.editMoreDeckOptionsDiv textarea').val();
if($('.editMoreDeckOptionsSelector .onlyme').hasClass("on")){
var viewPreferences = 1;
}else{
var viewPreferences = 0;
}
if($('.editUseThirdboxDiv').hasClass('on')){ var thirdbox = 1;
}else{ var thirdbox = 2; }
// console.log("deckInfoSave called");
$.ajax({
type: "POST",
url: "/edit/deckInfoSave.php",
data: { pDeckid: deck_id, pDeckname: deckname, pCardCount: cardCount,
pDeckTermLanguage: deckTermLanguage, pDeckDefinitionLanguage: deckDefinitionLanguage,
pDeckThirdBoxLanguage: deckThirdBoxLanguage, pThirdbox: thirdbox,
pDeckDescription: deckDescription, pViewPreferences: viewPreferences
}
})
.done(function(data){
// console.log(data);
// decksaved = 1;
saveDeckInfoHasFinished = 1;
});
}else{
saveDeckInfoHasFinished = 1;
}
// prepares edited card array
// gets all needed values and stores in holdSaveCardArray
var holdSaveCardArray = [];
for (i = 0; i < updateSaveArray.length; ++i) {
var currentCard_id = updateSaveArray[i],
rowidClass = ".row_"+currentCard_id,
text1val = $(rowidClass+" .text1").val(),
text2val = $(rowidClass+" .text2").val(),
text3val = $(rowidClass+" .text3").val();
cardOrder = $(".editMain li.mainLi:visible").index($(rowidClass)) + 1;
holdSaveCardArray[holdSaveCardArray.length] = {
"card_id": currentCard_id,
"text1val": text1val,
"text2val": text2val,
"text3val": text3val,
"cardOrder": cardOrder
};
}
// console.log(print_r(holdSaveCardArray));
// delete cards start
// deletes any card with an id in updateDeleteArray
$.ajax({
type: "POST",
url: "/edit/deleteCards.php",
data: { pDeck_id: deck_id, pDeleteArray: updateDeleteArray }
})
.done(function( msg ) {
// $('.temp').append(msg);
updateDeleteArray = [];
deleteCardsHasFinished = 1;
});
// save cards to database
// loops through each card that had changes made to it
$.ajax({
type: "POST",
url: "/edit/saveCardsArray.php",
dataType: "JSON",
data: { pDeck_id: deck_id, pCardArray: holdSaveCardArray}
}).done(function(data){
for (var i = 0; i < data.length; i++) {
var temp_id = data[i]["temp_id"], // new id
card_key = data[i]["card_key"], // old id
currentClassName = 'row_'+temp_id,
currentClass = '.row_'+temp_id,
nextClassName = 'row_'+card_key;
$(currentClass).val(card_key);
$(currentClass).removeClass(currentClassName).addClass(nextClassName);
}
saveCardsHasFinished = 1;
});
updateSaveArray = [];
// update order start // uses li value
updateOrderArray = [];
$('.editMain').find(".mainLi").each(function(){
var temp = $(this).val();
updateOrderArray[updateOrderArray.length] = temp;
});
$.ajax({
type: "POST",
url: "/edit/orderCards.php",
data: { pUpdateOrderArray: updateOrderArray }
})
.done(function( msg ) {
updateOrder = 0;
updateOrdersHasFinished = 1;
});
closeLibDLM(); console.log("closeLibDLM1");
changeSaveStudyButton(1);
} //saveChanges function end
So you could totally set an arbitrary timeout, or even a timeout that should encompass everything finishing on time? But, what happens when it doesn't? What happens when it takes longer to finish?
At that point, you're going to be in quite a pickle. I did not thoroughly read your code, but I would highly advise trying to use a callback() or Promise to end your function. And, not set a timeout. - This is a cleaner solution in that things happen when you want them, and after some defined has happened. Time is a relative, and finicky attribute of our world (Einstein proved this =P) that would be best be used as your friend, and not your enemy.
The counter argument would be, well sometimes things just hang. And, that is totally valid. For that case, you could set a timeout for a long period of time. But, again, that is still a 'hacky' way to handle things. In this case, I would try to create some handling to detect errors, or timeouts. i.e you could periodically check the page for a status. You could check to see which events are in existence that you could hook into.
If you could share in what instances our program hangs, I could better suggest a solution. Otherwise this question may end up being opinionated based on coding styles.
Hope this helps in some regard :)
I've worked in the Aerospace Aviation Industry and have asked a similar question when working with Microcontrollers. It seems you are looking for an appropriate timeout value based on calculation, but this may not be necessary in your case. Often times timeout values are more or less arbitrary. If your function executes in an average of roughly 1 second, maybe your timeout value should be set to 3 seconds. You should come to a conclusion based on testing.
I really try my damndest not to ask, but i have to at this point before I tear my hair out.
By the time the js interpreter gets to this particular method, I can print it to the console no problem, it is an array of "event" objects. From FireBug I can see it, but when I try to set a loop to do anything with this array its as if it doesn't exist. I am absolutely baffled......
A few things:
I am a newbie, I have tried a for(var index in list) loop, to no avail, I have also tried a regular old for(var i = 0; i < listIn.length; i++), and I also tried to get the size of the local variable by setting var size = listIn.length.
As soon as I try to loop through it I get nothing, but I can access all the objects inside it from the FireBug console no problem. Please help, even just giving me a little hint on where I should be looking would be great.
As for the array itself, I have no problems with getting an array back from PHP in the form of: [{"Event_Id":"9", "Title":"none"}, etc etc ]
Here is my code from my main launcher JavaScript file. I will also post a sample of the JSON data that is returned. I fear that I may be overextending myself by creating a massive object in the first place called content, which is meant to hold properties such as DOM strings, settings, and common methods, but so far everything else is working.
The init() function is called when the body onload is called on the corresponding html page, and during the call to setAllEvents and setEventNavigation I am lost.
And just to add, I am trying to learn JavaScript fundamentals before I ever touch jQuery.
Thanks
var dom, S, M, currentArray, buttonArray, typesArray, topicsArray;
content = {
domElements: {},
settings: {
allContent: {},
urlList: {
allURL: "../PHP/getEventsListView.php",
typesURL: "../PHP/getTypes.php",
topicsURL: "../PHP/getTopics.php"
},
eventObjArray: [],
buttonObjArray: [],
eventTypesArray: [],
eventTopicsArray: []
},
methods: {
allCallBack: function (j) {
S.allContent = JSON.parse(j);
var list = S.allContent;
for (var index in list) {
var event = new Event(list[index]);
S.eventObjArray.push(event);
}
},
topicsCallBack: function(j) {
S.eventTopicsArray = j;
var list = JSON.parse(S.eventTopicsArray);
topicsArray = list;
M.populateTopicsDropDown(list);
},
typesCallBack: function(j) {
S.eventTypesArray = j;
var list = JSON.parse(S.eventTypesArray);
typesArray = list;
M.populateTypesDropDown(list);
},
ajax: function (url, callback) {
getAjax(url, callback);
},
testList: function (listIn) {
// test method
},
setAllEvents: function (listIn) {
// HERE IS THE PROBLEM WITH THIS ARRAY
console.log("shall we?");
for(var index in listIn) {
console.log(listIn[index]);
}
},
getAllEvents: function () {
return currentArray;
},
setAllButtons: function (listIn) {
buttonArray = listIn;
},
getAllButtons: function () {
return buttonArray;
},
setEventNavigation: function(current) {
// SAME ISSUE AS ABOVE
var l = current.length;
//console.log("length " + l);
var counter = 0;
var endIndex = l - 1;
if (current.length < 4) {
switch (l) {
case 2:
var first = current[0];
var second = current[1];
first.setNextEvent(second);
second.setPreviousEvent(first);
break;
case 3:
var first = current[0];
var second = current[1];
var third = current[2];
first.setNextEvent(second);
second.setPreviousEvent(first);
second.setNextEvent(third);
third.setPreviousEvent(second);
break;
default:
break;
}
} else {
// do something
}
},
populateTopicsDropDown: function(listTopics) {
//console.log("inside topics drop");
//console.log(listTopics);
var topicsDropDown = document.getElementById("eventTopicListBox");
for(var index in listTopics) {
var op = document.createElement("option");
op.setAttribute("id", "dd" + index);
op.innerHTML = listTopics[index].Main_Topic;
topicsDropDown.appendChild(op);
}
},
populateTypesDropDown: function(listTypes) {
//console.log("inside types drodown");
//console.log(listTypes);
var typesDropDown = document.getElementById("eventTypeListBox");
for(var index2 in listTypes) {
var op2 = document.createElement("option");
op2.setAttribute("id", "dd2" + index2);
op2.innerHTML = listTypes[index2].Main_Type;
typesDropDown.appendChild(op2);
}
}
},
init: function() {
dom = this.domElements;
S = this.settings;
M = this.methods;
currentArray = S.eventObjArray;
buttonArray = S.buttonObjArray;
topicsArray = S.eventTopicsArray;
typesArray = S.eventTypesArray;
M.ajax(S.urlList.allURL, M.allCallBack);
//var tempList = currentArray;
//console.log("temp array length: " + tempList.length);
M.setAllEvents(currentArray);
M.testList(currentArray);
M.setEventNavigation(currentArray);
//M.setEventNavigation();
M.ajax(S.urlList.topicsURL, M.topicsCallBack);
M.ajax(S.urlList.typesURL, M.typesCallBack);
}
};
The problem you have is that currentArray gets its value asynchronously, which means you are calling setAllEvents too soon. At that moment the allCallBack function has not yet been executed. That happens only after the current running code has completed (until call stack becomes emtpy), and the ajax request triggers the callback.
So you should call setAllEvents and any other code that depends on currentArray only when the Ajax call has completed.
NB: The reason that it works in the console is that by the time you request the value from the console, the ajax call has already returned the response.
Without having looked at the rest of your code, and any other problems that it might have, this solves the issue you have:
init: function() {
dom = this.domElements;
S = this.settings;
M = this.methods;
currentArray = S.eventObjArray;
buttonArray = S.buttonObjArray;
topicsArray = S.eventTopicsArray;
typesArray = S.eventTypesArray;
M.ajax(S.urlList.allURL, function (j) {
// Note that all the rest of the code is moved in this call back
// function, so that it only executes when the Ajax response is
// available:
M.allCallBack(j);
//var tempList = currentArray;
//console.log("temp array length: " + tempList.length);
M.setAllEvents(currentArray);
M.testList(currentArray);
M.setEventNavigation(currentArray);
//M.setEventNavigation();
// Note that you will need to take care with the following asynchronous
// calls as well: their effect is only available when the Ajax
// callback is triggered:
M.ajax(S.urlList.topicsURL, M.topicsCallBack); //
M.ajax(S.urlList.typesURL, M.typesCallBack);
});
}
Thank you for taking the time to help me.
I am writing a game where an animated train icon moves along a given path to a destination, pausing at waypoints along the way. This is intended to give the impression of animation.
The game is coded in Facebook Javascript. I need to find a way to make the train icon pause for 1 second before moving on to the next waypoint. I hoped to find a function that would allow me to pause script execution for one second, but nothing like that seems to exist in JS. So I tried setTimeout, but my primary problem with this is twofold:
I need to pass an array into the callback function as an argument, and I can't figure out how to make setTimeout do this.
I finally succeeded in using setTimeout to execute my train animation code for 5 waypoints (I overcame the issue in 1 by using global variables). Unfortunately, it appears that all five calls to setTimeout got queued almost simultaneously, which resulted in waiting one second for the first setTimeout to fire, thenn they all fired at once ruining the illusion of train animation.
I've been battling this problem for six hours straight. It would be wonderful if someone could help me find a solution. Thanks!
Here's the code:
function myEventMoveTrainManual(evt, performErrorCheck) {
if(mutexMoveTrainManual == 'CONTINUE') {
var ajax = new Ajax();
var param = {};
if(evt) {
var cityId = evt.target.getParentNode().getId();
var param = { "city_id": cityId };
}
ajax.responseType = Ajax.JSON;
ajax.ondone = function(data) {
var actionPrompt = document.getElementById('action-prompt');
actionPrompt.setInnerXHTML('<span><div id="action-text">'+
'Train en route to final destination...</div></span>');
for(var i = 0; i < data.length; i++) {
statusFinalDest = data[i]['status_final_dest'];
//pause(1000);
gData = data[i];
setTimeout(function(){drawTrackTimeout()},1000);
if(data[i]['code'] == 'UNLOAD_CARGO' && statusFinalDest == 'ARRIVED') {
unloadCargo();
} else if (data[i]['code'] == 'MOVE_TRAIN_AUTO' || data[i]['code'] == 'TURN_END') {
//moveTrainAuto();
} else {
// handle error
}
mutexMoveTrainManual = 'CONTINUE';
}
}
ajax.post(baseURL + '/turn/move-train-final-dest', param);
}
}
function drawTrackTimeout() {
var trains = [];
trains[0] = gData['train'];
removeTrain(trains);
drawTrack(gData['y1'], gData['x1'], gData['y2'], gData['x2'], '#FF0', trains);
gData = null;
}
Typically this would be done by creating an object (say called myTrain) that has all its own data and methods, then call a myTrain.run mehod that looks to see where the train is. If it's between two stations, it calls itself with setTimeout and say a 50ms delay. When it reaches a station, it calls itself in 1000ms, creating a 1 second pause at the station.
If you queue the setTimeouts all at once, you run the risk of them all being delayed by some other process, then all running at once.
Hey, bit of fun (careful of wrapping). Needed a bit of practice with good 'ole prototype inheritance:
<!-- All the style stuff should be in a rule -->
<div style="position: relative; border: 1px solid blue;">
<div id="redTrain"
style="width:10px;height:10px;background-color:red; position:absolute;top:0px;left:0px;"></div>
</div>
<script type="text/javascript">
// Train constructor
function Train(id) {
this.element = document.getElementById(id);
this.timerId;
}
// Methods
// Trivial getPos function
Train.prototype.getPos = function() {
return this.element.style.left;
}
// Trivial setPos function
Train.prototype.setPos = function(px) {
this.element.style.left = parseInt(px,10) + 'px';
}
// Move it px pixels to the right
Train.prototype.move = function(px) {
this.setPos(px + parseInt(this.getPos(),10));
}
// Recursive function using setTimeout for animation
// Probably should accept a parameter for lag, long lag
// should be a multiple of lag
Train.prototype.run = function() {
// If already running, stop it
// so can interrupt a pause with a start
this.stop();
// Move the train
this.move(5);
// Keep a reference to the train for setTimeout
var train = this;
// Default between each move is 50ms
var lag = 50;
// Pause for 1 second each 100px
if (!(parseInt(this.getPos(),10) % 100)) {
lag = 1000;
}
train.timerId = window.setTimeout( function(){train.run();}, lag);
}
// Start should do a lot more initialising
Train.prototype.start = function() {
this.run();
}
// Stops the train until started again
Train.prototype.stop = function() {
if (this.timerId) {
clearTimeout(this.timerId);
}
}
// Set back to zero
Train.prototype.reset = function() {
this.stop();
this.setPos(0);
}
// Initialise train here
var myTrain = new Train('redTrain');
</script>
<p> </p>
<button onclick="myTrain.start();">Start the train</button>
<button onclick="myTrain.stop();">Stop the train</button>
<button onclick="myTrain.reset();">Reset the train</button>
To pass arguments, this might help you:
setTimeout(function() {
(function(arg1, arg2) {
// you can use arg1 / arg2 here
})('something', 123);
}, 1000);
Or, if you use a defined function:
setTimeout(function() {
someFunction('something', 123);
}, 1000);
It basically starts a timeout; after one second the function is invoked with the specified arguments.
How about using OO principles to simplify the problem? Create an "object" Train which has the following methods:
//train obj
function Train(){
this.isOnWaypoint = function(){
return calculateIsWayPoint()
}
}
//main logic
var train = new Train()
var doneWaiting = false
var doneWaitingTimeout = undefined
var gameLoop = setInterval(1000,function(){
...
if(train.isOnWaypoint() && !doneWaiting){
if(doneWaitingTimeout == undefined){
setTimeOut(5000,function(){
doneWaiting = true
doneWaitingTimeout = undefined
})
}
}
...
})
Here's the solution I finally came up with:
function drawTrackTimeout() {
if(gData != null && gIndex < gData.length) {
var trains = [];
trains[0] = gData[gIndex]['train'];
removeTrain(trains);
drawTrack(gData[gIndex]['y1'], gData[gIndex]['x1'], gData[gIndex]['y2'], gData[gIndex]['x2'], '#FF0', trains);
statusFinalDest = gData[gIndex]['status_final_dest'];
if(statusFinalDest == 'ARRIVED') {
unloadCargo();
} else if (gData[gIndex]['code'] == 'MOVE_TRAIN_AUTO' || gData[gIndex]['code'] == 'TURN_END') {
//moveTrainAuto();
} else {
// handle error
}
gIndex++;
} else {
clearInterval(gIntid);
gIntid = null;
gData = null;
gIndex = 0;
}
}
function myEventMoveTrainManual(evt, performErrorCheck) {
//debugger;
if(mutexMoveTrainManual == 'CONTINUE') {
var ajax = new Ajax();
var param = {};
if(evt) {
var cityId = evt.target.getParentNode().getId();
var param = { "city_id": cityId };
}
ajax.responseType = Ajax.JSON;
ajax.ondone = function(data) {
var actionPrompt = document.getElementById('action-prompt');
actionPrompt.setInnerXHTML('<span><div id="action-text">'+
'Train en route to final destination...</div></span>');
gData = data;
gIndex = 0;
gIntid = setInterval(function(){drawTrackTimeout()},1000);
}
ajax.post(baseURL + '/turn/move-train-final-dest', param);
}
}
Update: I guess the subject gave a wrong notion that I'm looking for an existing addon. This is a custom problem and I do NOT want an existing solution.
I wish to WRITE (or more appropriately, modify and existing) Addon.
Here's my requirement:
I want my addon to work for a particular site only
The data on the pages are encoded using a 2 way hash
A good deal of info is loaded by XHR requests, and sometimes
displayed in animated bubbles etc.
The current version of my addon parses the page via XPath
expressions, decodes the data, and replaces them
The issue comes in with those bubblified boxes that are displayed
on mouse-over event
Thus, I realized that it might be a good idea to create an XHR
bridge that could listen to all the data and decode/encode on the fly
After a couple of searches, I came across nsITraceableInterface[1][2][3]
Just wanted to know if I am on the correct path. If "yes", then kindly
provide any extra pointers and suggestions that may be appropriate;
and if "No", then.. well, please help with correct pointers :)
Thanks,
Bipin.
[1]. https://developer.mozilla.org/en/NsITraceableChannel
[2]. http://www.softwareishard.com/blog/firebug/nsitraceablechannel-intercept-http-traffic/
[3]. http://www.ashita.org/howto-xhr-listening-by-a-firefox-addon/
nsITraceableChannel is indeed the way to go here. the blog posts by Jan Odvarko (softwareishard.com) and myself (ashita.org) show how to do this. You may also want to see http://www.ashita.org/implementing-an-xpcom-firefox-interface-and-creating-observers/, however it isn't really necessary to do this in an XPCOM component.
The steps are basically:
Create Object prototype implementing nsITraceableChannel; and create observer to listen to http-on-modify-request and http-on-examine-response
register observer
observer listening to the two request types adds our nsITraceableChannel object into the chain of listeners and make sure that our nsITC knows who is next in the chain
nsITC object provides three callbacks and each will be called at the appropriate stage: onStartRequest, onDataAvailable, and onStopRequest
in each of the callbacks above, our nsITC object must pass on the data to the next item in the chain
Below is actual code from a site-specific add-on I wrote that behaves very similarly to yours from what I can tell.
function TracingListener() {
//this.receivedData = [];
}
TracingListener.prototype =
{
originalListener: null,
receivedData: null, // array for incoming data.
onDataAvailable: function(request, context, inputStream, offset, count)
{
var binaryInputStream = CCIN("#mozilla.org/binaryinputstream;1", "nsIBinaryInputStream");
var storageStream = CCIN("#mozilla.org/storagestream;1", "nsIStorageStream");
binaryInputStream.setInputStream(inputStream);
storageStream.init(8192, count, null);
var binaryOutputStream = CCIN("#mozilla.org/binaryoutputstream;1",
"nsIBinaryOutputStream");
binaryOutputStream.setOutputStream(storageStream.getOutputStream(0));
// Copy received data as they come.
var data = binaryInputStream.readBytes(count);
//var data = inputStream.readBytes(count);
this.receivedData.push(data);
binaryOutputStream.writeBytes(data, count);
this.originalListener.onDataAvailable(request, context,storageStream.newInputStream(0), offset, count);
},
onStartRequest: function(request, context) {
this.receivedData = [];
this.originalListener.onStartRequest(request, context);
},
onStopRequest: function(request, context, statusCode)
{
try
{
request.QueryInterface(Ci.nsIHttpChannel);
if (request.originalURI && piratequesting.baseURL == request.originalURI.prePath && request.originalURI.path.indexOf("/index.php?ajax=") == 0)
{
var data = null;
if (request.requestMethod.toLowerCase() == "post")
{
var postText = this.readPostTextFromRequest(request, context);
if (postText)
data = ((String)(postText)).parseQuery();
}
var date = Date.parse(request.getResponseHeader("Date"));
var responseSource = this.receivedData.join('');
//fix leading spaces bug
responseSource = responseSource.replace(/^\s+(\S[\s\S]+)/, "$1");
piratequesting.ProcessRawResponse(request.originalURI.spec, responseSource, date, data);
}
}
catch (e)
{
dumpError(e);
}
this.originalListener.onStopRequest(request, context, statusCode);
},
QueryInterface: function (aIID) {
if (aIID.equals(Ci.nsIStreamListener) ||
aIID.equals(Ci.nsISupports)) {
return this;
}
throw Components.results.NS_NOINTERFACE;
},
readPostTextFromRequest : function(request, context) {
try
{
var is = request.QueryInterface(Ci.nsIUploadChannel).uploadStream;
if (is)
{
var ss = is.QueryInterface(Ci.nsISeekableStream);
var prevOffset;
if (ss)
{
prevOffset = ss.tell();
ss.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
}
// Read data from the stream..
var charset = "UTF-8";
var text = this.readFromStream(is, charset, true);
// Seek locks the file so, seek to the beginning only if necko hasn't read it yet,
// since necko doesn't seek to 0 before reading (at lest not till 459384 is fixed).
if (ss && prevOffset == 0)
ss.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
return text;
}
else {
dump("Failed to Query Interface for upload stream.\n");
}
}
catch(exc)
{
dumpError(exc);
}
return null;
},
readFromStream : function(stream, charset, noClose) {
var sis = CCSV("#mozilla.org/binaryinputstream;1", "nsIBinaryInputStream");
sis.setInputStream(stream);
var segments = [];
for (var count = stream.available(); count; count = stream.available())
segments.push(sis.readBytes(count));
if (!noClose)
sis.close();
var text = segments.join("");
return text;
}
}
hRO = {
observe: function(request, aTopic, aData){
try {
if (typeof Cc == "undefined") {
var Cc = Components.classes;
}
if (typeof Ci == "undefined") {
var Ci = Components.interfaces;
}
if (aTopic == "http-on-examine-response") {
request.QueryInterface(Ci.nsIHttpChannel);
if (request.originalURI && piratequesting.baseURL == request.originalURI.prePath && request.originalURI.path.indexOf("/index.php?ajax=") == 0) {
var newListener = new TracingListener();
request.QueryInterface(Ci.nsITraceableChannel);
newListener.originalListener = request.setNewListener(newListener);
}
}
} catch (e) {
dump("\nhRO error: \n\tMessage: " + e.message + "\n\tFile: " + e.fileName + " line: " + e.lineNumber + "\n");
}
},
QueryInterface: function(aIID){
if (typeof Cc == "undefined") {
var Cc = Components.classes;
}
if (typeof Ci == "undefined") {
var Ci = Components.interfaces;
}
if (aIID.equals(Ci.nsIObserver) ||
aIID.equals(Ci.nsISupports)) {
return this;
}
throw Components.results.NS_NOINTERFACE;
},
};
var observerService = Cc["#mozilla.org/observer-service;1"]
.getService(Ci.nsIObserverService);
observerService.addObserver(hRO,
"http-on-examine-response", false);
In the above code, originalListener is the listener we are inserting ourselves before in the chain. It is vital that you keep that info when creating the Tracing Listener and pass on the data in all three callbacks. Otherwise nothing will work (pages won't even load. Firefox itself is last in the chain).
Note: there are some functions called in the code above which are part of the piratequesting add-on, e.g.: parseQuery() and dumpError()
Tamper Data Add-on. See also the How to Use it page
You could try making a Greasemonkey script and overwriting the XMLHttpRequest.
The code would look something like:
function request () {
};
request.prototype.open = function (type, path, block) {
GM_xmlhttpRequest({
method: type,
url: path,
onload: function (response) {
// some code here
}
});
};
unsafeWindow.XMLHttpRequest = request;
Also note that you can turn a GM script into an addon for Firefox.