views:

40

answers:

3

On Window's load, every DD element inside Quote_App should have an onCLick event appended that triggers the function Lorem, however, Lorem returns the nodeName and Id of the last element in the For statement rather than that of the element that trigged the function. I would want Lorem to return the nodeName and Id of the element that triggered the function.

function Lorem(Control){

/*     this.Control=Control; */
    this.Amet=function(){
        return Control.nodeName+"\n"+Control.id;
    };

};

function Event(Mode,Function,Event,Element,Capture_or_Bubble){
    if(Mode.toLowerCase()!="remove"){
        if(Element.addEventListener){
            if(!Capture_or_Bubble){
                Capture_or_Bubble=false;
            }else{
                if(Capture_or_Bubble.toLowerCase()!="true"){
                    Capture_or_Bubble=false;
                }else{
                    Capture_or_Bubble=true;
                };
            };
            Element.addEventListener(Event,Function,Capture_or_Bubble);
        }else{
            Element.attachEvent("on"+Event,Function);
        };
    };
};

function Controls(){
    var Controls=document.getElementById("Quote_App").getElementsByTagName("dd");
    for(var i=0;i<Controls.length;i++){

        var Control=Controls[i];

        Event("add",function(){

            var lorem=new Lorem(Control);
            lorem.Control=Control;
            alert(lorem.Amet());

        },"click",Controls[i]);

    };
};

Event("add",Controls,"load",window);

Currently you click on any DD element Lorem always returns the nodeName and Id of the last DD element.

Lorem should return the nodeName and Id of the Control (Control[i]) that triggered Lorem.

How do I go about making this happen?

Thank you!

+3  A: 

you need a closure inside the loop where you are attaching the event handlers to capture the value of i in each loop iteration.

  for(var i=0;i<Controls.length;i++) {   
    (function() {
        var Control=Controls[i];

        Event("add",function(){

            var lorem=new Lorem(Control);
            lorem.Control=Control;
            alert(lorem.Amet());

         },"click",Controls[i]);
    })();
  };

Here I've created a closure above using JavaScript's good friend, the self-invoking anonymous function.

The reason a closure is required is that without it, the value of i at the point at which any event handler function is executed would be the value of i in the last loop iteration, not what we want. We want the value of i as it is in each loop iteration, at the point at which we declare each event handler function, thus we need to capture this value in each iteration. Using an anonymous function that executes as soon as it's declared is a good mechanism for capturing the desired value.

Another point, slightly off topic but it may help you out, is that event capturing is not supported in every browser (ahem, IE) but event bubbling is. This effectively makes the useCapture boolean flag in addEventListener quite useless for developing cross browser web applications. I'd therefore advise not to use it.

One more thing, JavaScript generally uses camel casing for function names and variable names. Pascal casing is generally used only for constructor functions (functions that create objects).

Russ Cam
I'll get searching right away as I don't know what that means. I'm still only starting out with Javascript. I would appreciate it also if you'd expand. Thank you!
Jonathon David Oates
I thought the <code>For</code> loop was the problem. Your solution is perfect, so thank you. others in a situation similar may find http://www.javascriptkit.com/javatutors/closures.shtml an interesting read.You're awesome, thanks again!
Jonathon David Oates
Thanks, tip taken on board! I knew Internet Explorer does not support the capture or bubble boolean. I included it to make a complete cross browser function. But I get what you mean, you couldn't make a cross browser app. that employed capturing. I included it for the sake of W3C standards.With regards to the casing, it's a personal thing so I know what's mine and what's a default method. Plus it helps if I accidentally name a variable the same as a default method, as Javascript is case sensitive. I hope that makes sense. Do you think I should always use camel casing as good practice?
Jonathon David Oates
@user311403 - I would use whatever casing you feel comfortable with and/or whatever standard has been agreed between you and the people you work with. IMO though, I would say it was a good habit to get into using camel casing as it generally is the standard and so if you take your skills elsewhere, it'll be a painless transistion :) As for `Capture_or_Bubble`, I'd be inclined to take it out altogether and simply have `addEventListener(Event,Function,false);` inside your function.
Russ Cam
@Russ: Good call.
Jonathon David Oates
+2  A: 

This may be a more obvious variant of Russ Cam's answer:

function Controls() {
  var Controls = document.getElementById("Quote_App").getElementsByTagName("dd");

  var createHandlerFor = function(CurrentControl) {
    return function() {
      var lorem=new Lorem(CurrentControl);
      lorem.Control=CurrentControl;
      alert(lorem.Amet());
    }
  };

  for (var i=0; i<Controls.length; i++) {
    Event("add", createHandlerFor(Controls[i]), "click", Controls[i]);
  }
};
Tomalak
+1 - but for some reason I find that one more confusing :)
Russ Cam
@Russ: It depends on your mindset I guess. :) If your focus is on the loop, I find it helpful to separate the closure and make a speaking name for it, instead of using an anonymous function. This way the loop body appears more straight-forward.
Tomalak
+2  A: 

When you create a function that refers to variables outside of it, these references will be resolved at the time you call this function.

In your case:

var functions = [];
function outer() {
    for (var i = 0; i < N; i++) { // <------------
        functions[i] = function() { //            |
            alert(i); // <-- this 'i' refers to this one
        }
    } // At the end of the for loop, i == N (or N+1?)
}
functions[x](); // Will show N (or N+1)
// because that's the current value of i in the outer function
// (which is kept alive just because something refers to it)

What you want to do is capture the value of 'i' at each step of the loop, for later evaluation, e.g.:

var functions = [];
function outer() {
    for (var i = 0; i < N; i++) { // <---------------------------------------
        functions[i] = (function(my_i) { // <----                            |
            return function () { //              |                           |
                alert(my_i); // my_i refers to this which is a copy of 'i' there
            }
        }(i)); // run function(with my_i = the i at each step of the loop)
    }
}
functions[x](); // Will show x

You can see there is an inner function that gets a copy of the current counter as a parameter. This inner function stays alive with that copy, so that later calls to the stored innest function returns the value of the my_i of the function that created it -- Clear? :-)

This is the wonderful world of closures. It may take a bit of mind-bending to first get it, but then you'll be glad you did, so go and google "javascript closures" to death!

squelart