views:

47

answers:

2

I am trying to implement a moving waiting bar in JavaScript. This is supposed to be visible during an AJAX request.

My initial code seems to work, but has the restriction that it can be used only once on the page, since there is only the variables timerW and stateW only appear once as global variable, and the element ids can also appear only once in the html document.

var timerW;
var stateW = 1;


 function nextW(){

  switch (stateW){
   case 1:
    document.getElementById('d3').style.background = "grey";
    document.getElementById('d1').style.background = "red";
    stateW++;
    timerW = setTimeout("nextW()",250);
    break;
   case 2:
    document.getElementById('d1').style.background = "grey";
    document.getElementById('d2').style.background = "red";
    stateW++;
    timerW = setTimeout("nextW()",250);
    break;
   case 3:

    document.getElementById('d2').style.background = "grey";
    document.getElementById('d3').style.background = "red";
    stateW = 1;
    timerW = setTimeout("nextW()",250);
    break;
  }
 }


 function Wait(mainE){
  document.getElementById(mainE).innerHTML = '<div id="d1" class="d"></div><div id="d2" class="d"></div><div id="d3" class="d"></div>';
  nextW(mainE);

}

So, from there I want to extend it to make it possible to have multiple wait blocks visible.

The first problem I try to solve is the html generated, so have unique ID's

 var timerW;
 var stateW = 1;

 function nextW(mainE){


  // Unexplained behaviour
  // for some reason, I get the HTMLDivElement in stead of it's ID in a string,
  // This appears to start when stateW is 3.
  // the setTimeout in case 1 looks identical to the case 2, so I have no clue why
  // I am getting an object in stead of my string.


  var m;
  if (typeof(mainE)=="object") {
   m = mainE;
   mainE = m.id;
  } else {
   m = document.getElementById(mainE);
  }



  if (m == undefined ) return; 
  if (m.style.visibility == "hidden") return;

  for(i=0; i < m.children.length; i++) {
   if (m.children[i].id=="wait") {
    m = m.children[i];
    break;
   }
  }
  if (stateW[mainE]==undefined) stateW[mainE] = 1;

  switch (stateW[mainE]){
   case 1:

    for(i=0; i < m.children.length; i++) {
     var e = m.children[i];
     if (e.id==mainE+'d1'){
      e.style.background = "red";
     } else {
      e.style.background = "gray";
     }
    }
    stateW++;
    timerW = setTimeout("nextW("+mainE+")",250);
    break;
   case 2:

    for(i=0; i < m.children.length; i++) {
     var e = m.children[i];
     if (e.id==mainE+'d2'){
      e.style.background = "red";
     } else {
      e.style.background = "gray";
     }
    }

    stateW++;
    timerW = setTimeout("nextW("+mainE+")",250);
    break;
   case 3:
    for(i=0; i < m.children.length; i++) {
     var e = m.children[i];
     if (e.id==mainE+'d3'){
      e.style.background = "red";
     } else {
      e.style.background = "gray";
     }
    }

    stateW = 1;
    timerW = setTimeout("nextW("+mainE+")",250);
    break;
  }
 }

 function Wait(mainE){
  document.getElementById(mainE).innerHTML = '<div id="wait"><div id="'+mainE+'d1" class="d"></div><div id="'+mainE+'d2" class="d"></div><div id="'+mainE+'d3" class="d"></div></div>';
  nextW(mainE);
 }

This code results in unexplained behaviour. As I set the timer using

 timerW = setTimeout("nextW("+mainE+")",250);

in each case, the 2nd block is drawn red correctly, but after that, I am getting an HTMLDivElement in stead of the string I expected. The HTMLDivElement corresponds with the ID in the string I expect. I cannot explain why, only after the 2nd block is drawn red, the behaviour changes. It keeps giving me an HTMLDivElement consequently after the 2nd red block, but I cannot explain why.

So far so good, I can write code around this little problem, even though I cannot explain why this is happening.

Still, the code is not ready yet for multiple wait timers running at the same time. For this, I tried to create two objects

 var timerW = new Object();
 var stateW = new Object();

and replace my

 switch (stateW){

with

 if (stateW[mainE]==undefined) stateW[mainE] = 1;
 switch (stateW[mainE]){

and replace

   timerW = setTimeout("nextW("+mainE+")",250);

with

   timerW[mainE] = setTimeout("nextW("+mainE+")",250);

Now, the problem is, my objects turn into NaN after the first run through my nextW function. I expect it's caused by garbage collection.

Can anyone explain (1) Why I am getting HTMLDivElements in stread of strings? (2) Why my Object turns into a NaN? (3) How to fix this issue?

I am not just looking for a fix, but also wanting to know what is going on.

+3  A: 

Constructor function

function Wait(el) {
    if (typeof el == "string") el = document.getElementById(el);
    this.el = el;
    this.state = 1;
    this.el.innerHTML = '<div id="d1" class="d"></div>\
                         <div id="d2" class="d"></div>\
                         <div id="d3" class="d"></div>';
    this.next();
}

Animation step

Wait.prototype.next = function () {
    if (this.el.style.visibility == "hidden") return;
    var children = this.el.childNodes;

    for (var i = children.length; i--;) {
        if (children[i].id == "wait") {
            var wait = children[i];
            break;
        }
    }

    children = wait.childNodes;

    for (var i = children.length; i--;) {
        var child = children[i];
        if (child.id == this.el.id + 'd' + this.state) {
            child.style.background = "red";
        } else {
            child.style.background = "gray";
        }
    }

    this.state++;
    if (this.state > 3) this.state = 0;
    this.timer = setTimeout(this.next, 250);
}​

Usage

new Wait("element_id");​
galambalazs
A: 

To explain, whats wrong:

setTimeout("nextW("+mainE+")",250);

first: don't provide strings to setTimeout(), they will be evaluated. setTimeout expects functions, not strings.

example for use with a function::

setTimeout(function(){nextW(mainE);},250);

The string you provide inside your script will be evaluated to :

nextW(mainE)

since setTimeout() is a method of the global window-object, this will fail, because there(inside the global scope) is nothing known with the name "mainE".

If you do it with strings, it must look like that:

setTimeout("nextW('"+mainE+"')",250);

this will be evaluated to nextW('theGivenId')

...but as I said before, don't do it like that.

You should read a liitle bit about the use of objects and closures in javascript, they will make your work with it easier, as you can see on the example provided by galambalazs. You dont need to anger with lots of global objects and name-conflicts with it :)

Dr.Molle
The example on http://www.w3schools.com/js/js_timing.asp puts a string in there.Before my function had a parameter, I tried to enter the function without the quotes, and it resulted in no delay. When I added the quotes, the delay worked.
andremo
So what else can I say: its not a good example there! Not everything that works, is good^^
Dr.Molle