1

I am trying to create a plugin where I pass on handler functions for specific events. In the simple example below, I have two buttons. When I press button 1, its label should change to 'Button A', and when I press button 2, its label should change to 'Button B'.

However, what is happening is when I press button 1, the label changes to 'Button B'. I know this has something to do with the handler function closure in the loop, but I just can't figure out how to fix it.

<body>
    <button class="btn1">Button 1</button>
    <button class="btn2">Button 2</button>
</body>    

Here's my jquery:

$(function() {

    $("body").myplugin( {
        helperFunctions : {
            funcA : function( obj ) {
                $(obj).text( "Button A" );
            },

            funcB : function( obj ) {
                $(obj).text( "Button B" );
            }
        },

        handlers : [
            {   
             on : "click",
             selector : ".btn1",
             handlerFunc : function( funcs, obj ){
                               funcs.funcA( obj );
                           }

            },

            {   
             on : "click",
             selector : ".btn2",
             handlerFunc : function( funcs, obj ){
                               funcs.funcB( obj );
                           }

            }
        ]
    });

});


(function ($) {
    $.fn.extend({
        myplugin: function ( settings ) {
            var $this = $(this);

            function createHandlerFunction( func, obj ){
                return func( settings.helperFunctions, obj ) ;
            }

            for( var handlerIdx in settings.handlers ){
                var handler = settings.handlers[ handlerIdx ];
                $this.on(  
                     handler.on, 
                     handler.selector, 
                     function() { 
                         return createHandlerFunction( handler.handlerFunc, this ) } );
                     }
            }
    });
})(jQuery);

Sorry if the example is a bit long. Here's http://jsfiddle.net/30nL07db/

user2943775
  • 263
  • 3
  • 8
  • Put your function expression inside the `createHandlerFunction` (so that it is returned), not around the call, and it will work. – Bergi Jan 28 '15 at 14:16
  • Slightly off-topic: you shouldn't be using `for..in` to iterate arrays. – Ben Fortune Jan 28 '15 at 15:24

2 Answers2

2

So the problem in your code is with this :

for( var handlerIdx in settings.handlers ){
    var handler = settings.handlers[ handlerIdx ];
...

Your are using in event delegation the handler variable, which is bound to the outer variable handlerIdx, and becouse of that when exiting the for loops, handlerIdx will be the last id...

Here is an contra-example with 3 id's : see fiddle

Look at other question about closures : here

Tutorial about closures : here

So let's make a function which creates the event delegation, outside the loop, and inside just pass that function , like this :

function createEvent(id) {
    $this.on(
      settings.handlers[id].on,
      settings.handlers[id].selector,
      function() {
         settings.handlers[id].handlerFunc( settings.helperFunctions, this)
      }
    );
}

 for( var handlerIdx in settings.handlers ){
     var handler = settings.handlers[ handlerIdx ];
     var s = handler.selector;
     createEvent(handlerIdx);
 }

See fiddle with this answer, working as you expect

Community
  • 1
  • 1
ali404
  • 1,143
  • 7
  • 13
1

Here is your updated working fiddle : http://jsfiddle.net/30nL07db/42/

for( var handlerIdx in settings.handlers ){
    var handler = settings.handlers[ handlerIdx ];
    $this.on(handler.on, handler.selector, handler, function(event) {
        return createHandlerFunction( event.data.handlerFunc, this );
    });
}

Simply pass the handler as data parameter to the event registration Or the variable handler will point to its last assigned value(i.e, the last value of loop);

Govan
  • 7,751
  • 5
  • 26
  • 42