locked
How to add event listeners for dynamically created elements?

    Question

  • I am trying to create an app based on a memory game I have written which works well in firefox and javascript.

    Have just about got my head around addEventListeners. My problem is that the card elements are all added dynamically. I tried adding event listeners under onappactivation, but that did not work - presumably as the elements would not have been created at this stage.

    Have got a small mock-up app to try out the various options. It creates 5 img elements. These photos are (supposed to be)  changed on click to another image.

     /    var image = new Array();
    
        var app = WinJS.Application;
        var activation = Windows.ApplicationModel.Activation;
    
        app.onactivated = function (args) {
            if (args.detail.kind === activation.ActivationKind.launch) {
                if (args.detail.previousExecutionState !== activation.ApplicationExecutionState.terminated) {
                     // TODO: This application has been newly launched. Initialize
                    // your application here.
                } else {
                    // TODO: This application has been reactivated from suspension.
                    // Restore application state here.
                }
                args.setPromise(WinJS.UI.processAll());
            }
    
    
           
            var outerdeck = document.querySelector('#outerdeck');
              for (var k = 0; k < 5; k++) {
                image = document.createElement('img');
                image.src = "/images/329.jpg";
                image.id = k;
                outerdeck.appendChild(image);
                var newevent = document.getElementById(k);
                newevent.addEventListener("click", changeItem(k), false);
                
            }
                   
            function changeItem(k) {
                var newpic = document.getElementById(k);
                newpic.src = "/images/391.jpg";
    
            }
        };
    
        app.oncheckpoint = function (args) {
            // TODO: This application is about to be suspended. Save any state
            // that needs to persist across suspensions here. You might use the
            // WinJS.Application.sessionState object, which is automatically
            // saved and restored across suspension. If you need to complete an
            // asynchronous operation before your application is suspended, call
            // args.setPromise().
        };
    
        app.start();
    
      
    })();

    Currently I am trying to add events listeners dynamically, but they fire as soon as they are created. I get 5 391.jpgs not 5 329.jpg when the app starts.

    Am not too sure what to do. With the web browser game I used: document.getElementById(k).setAttribute("onclick","show(this.id)");

    With this I can pass this.id to the function in the windows app, can even change image.src in javascript but  the programme gets stuck in this function in the app - it just goes round and round and I cannot break out of the function. Needless to say this worked fine in a browser.

    I did wonder if I needed to bind, but to be honest this seems as opaque as mud - I have no idea how to do it dynamically, despite several days of trying and reading .

    Would be grateful for any help.

    Many thanks.


    • Edited by chickfeed Thursday, May 08, 2014 5:01 PM
    Thursday, May 08, 2014 4:57 PM

Answers

  • What you're trying to do, in essence, is pass an argument to the event handler using this:

    newevent.addEventListener("click", changeItem(k), false);

    However, the second argument to addEventListener is a function. What you're passing is the return value of calling changeItem(k). That is, each time you execute this line of code, you'll call changeItem(k) immediately, and because you don't return anything from that handler, you're just passing undefined to addEventListener.

    To do this kind of parameterized handler, the best way is to use the .bind(<this>) method that exists on a function object. Whatever you pass as the argument to bind becomes thethisvariable inside that function when it's called. In your case you could do something like this:

    newevent.addEventListener("click", changeItem.bind( {id: k} ), false);

    Then in your handler you can use this.id:

            function changeItem() {
               
    var newpic = document.getElementById(this.id);
                newpic
    .src = "/images/391.jpg";
           
    }

    That should do the trick. Note that oftentimes you'll have a more complete object instance for each of the clickable elements, and that's the object you'd want in the event handler. For example, in the constructor for some object class you often see lines like this to assign a member of the object (this._clickHandler) as the handler:

    myObj.addEventListener("click", this._clickHandler.bind(this));

    With this code you can create as many instances of the object class as you want, and each instance will see a "this" variable inside the event handler that's the same as that object instance.

    Kraig

    Author, Programming Windows Store Apps with HTML, CSS, and JavaScript, Second Edition, a free ebook from Microsoft Press.


    • Marked as answer by chickfeed Friday, May 09, 2014 7:13 AM
    Thursday, May 08, 2014 7:09 PM

All replies

  • What you're trying to do, in essence, is pass an argument to the event handler using this:

    newevent.addEventListener("click", changeItem(k), false);

    However, the second argument to addEventListener is a function. What you're passing is the return value of calling changeItem(k). That is, each time you execute this line of code, you'll call changeItem(k) immediately, and because you don't return anything from that handler, you're just passing undefined to addEventListener.

    To do this kind of parameterized handler, the best way is to use the .bind(<this>) method that exists on a function object. Whatever you pass as the argument to bind becomes thethisvariable inside that function when it's called. In your case you could do something like this:

    newevent.addEventListener("click", changeItem.bind( {id: k} ), false);

    Then in your handler you can use this.id:

            function changeItem() {
               
    var newpic = document.getElementById(this.id);
                newpic
    .src = "/images/391.jpg";
           
    }

    That should do the trick. Note that oftentimes you'll have a more complete object instance for each of the clickable elements, and that's the object you'd want in the event handler. For example, in the constructor for some object class you often see lines like this to assign a member of the object (this._clickHandler) as the handler:

    myObj.addEventListener("click", this._clickHandler.bind(this));

    With this code you can create as many instances of the object class as you want, and each instance will see a "this" variable inside the event handler that's the same as that object instance.

    Kraig

    Author, Programming Windows Store Apps with HTML, CSS, and JavaScript, Second Edition, a free ebook from Microsoft Press.


    • Marked as answer by chickfeed Friday, May 09, 2014 7:13 AM
    Thursday, May 08, 2014 7:09 PM
  • Thank you so much. That works beautifully.  Not nearly as complicated as I was trying to make it.

    (I think I had better read your book!)

    Friday, May 09, 2014 7:13 AM
  • Glad to hear it. This is just one of those little JavaScript tricks that you learn once, then you get it. :)
    Friday, May 09, 2014 1:55 PM