Hello and welcome to our community! Is this your first visit?
Register
Enjoy an ad free experience by logging in. Not a member yet? Register.
Results 1 to 4 of 4
  1. #1
    New Coder
    Join Date
    Sep 2013
    Posts
    20
    Thanks
    4
    Thanked 0 Times in 0 Posts

    bind argument to a click handler at the time of setting the click handler

    Hi,
    I have the following code to add an html <li> element to an html <ul> element and attach a click handler to it.

    Code:
    someUiObject.prototype.addListItem = function( list ){
    	var htmlList = $('.someClass');
    
    	for( var i=0; i<list.length; i++ ){
    		var item = list[i];
    		var htmlListItem = $('<li />');
    		htmlList.html( item['text'] );
    		htmlListItem.click( $.proxy( function( event ){
    			this.doSomething( item['itemId'] );
    		}, this ) );
    	}
    
    	htmlList.append( htmlListItem );
    }
    
    
    someUiObject.prototype.doSomething = function( itemId ){
    	alert( itemId );
    }
    Assume the list looks like this:
    list = [{'text': 'a', 'itemId':1}, {'text': 'b', 'itemId': 2 }]

    The problem that occurs is that when a html <li> element is clicked, the alert in doSomething() always alerts the itemId of the last item that has been added instead of alerting 1 if the 'a' item was clicked and 2 if the 'b' item was clicked.

    I think it has to do with that the doSomething(item['itemId']) is executed when the actual click is made, and at that time item['itemId'] refers to the last item of the for loop.

    If thats the case, my question then is: how do I achieve what I want? I want it to use the argument as it is at the time of setting the click handler.

    Hope you can help me out
    Thanks in advance!

    Stefan1

  • #2
    Senior Coder
    Join Date
    Dec 2010
    Posts
    2,396
    Thanks
    12
    Thanked 569 Times in 562 Posts
    Yes, this is the infamous "closure inside a loop" issue. You identified it correctly. The issue is that the closure you create with the .click() callback has access to its surrounding scope at execution time, but not at creation time.

    Solution: At creation time create a copy of the variables you wish to have access to and then use those copies at execution time. You can achieve this with an inner closure

    Code:
    htmlListItem.click(
       (function(innerItem) {
          return $.proxy( function( event ){
             this.doSomething( innerItem );
          }, this );
       })(item['itemId']);
    );
    This will immediately execute the anonymous inner function (at creation time) and create a copy of item['itemId'] by passing it as a parameter. Inside this (immediately executed) function you return the proxy that will be executed at execution time of the .click() handler. It has then access to the local copy of item['itemId']

  • Users who have thanked devnull69 for this post:

    stefan1 (09-30-2013)

  • #3
    New Coder
    Join Date
    Sep 2013
    Posts
    20
    Thanks
    4
    Thanked 0 Times in 0 Posts
    Great explanation, thanks!

    I added another proxy around the "immediately executed function" otherwise the 'this' is not correct for the 'this.doSomething()'.

    I love javascript but I have to say you can get some ugly pieces of code in cases like this and with all the proxy's to maintain scope (or apply()/call()/bind()). Especially when working with asynchronous code due to the use of callback functions.

    Anyway, thanks again.
    Stefan1

  • #4
    Supreme Master coder! glenngv's Avatar
    Join Date
    Jun 2002
    Location
    Philippines
    Posts
    11,042
    Thanks
    0
    Thanked 251 Times in 247 Posts
    Another solution is to pass data to the handler.

    Code:
    htmlListItem.on('click',  { itemId: item['itemId'] }, $.proxy( function( event ){
      this.doSomething( event.data.itemId );
    }, this ) );
    http://api.jquery.com/on/#passing-data


  •  

    Posting Permissions

    • You may not post new threads
    • You may not post replies
    • You may not post attachments
    • You may not edit your posts
    •