Armel Larcier
Armel Larcier

Reputation: 16037

Best way to silently bind window resize event to jQuery plugin without keeping a reference to the targeted element

I'm looking for best-practice advice.

I'm writing a small jQuery plugin to manage horizontal scroll on elements.

I need all the dom elements targeted by that plugin to update on window resize.

Fact is, my website is a full ajax 'app' so when I remove DOM elements, I need them gone so memory doesn't leak.

But I can't find a way to bind the resize event without keeping a reference to the DOM node.

EDIT :

Actually I need the resize handler to get the plugin-targeted elements at 'call' time, coz I don't want to keep any reference to those elements in memory, because I might call .html('') on a parent of theirs...

I did not paste all my code, just an empty shell. I already have a destroy method that unbinds handlers. But I'm generating, removing and appending html nodes dynamically and I the the elements targeted by the plugin to remove silently.

Kevin B stated I could override jQuery .remove method to deal with the handlers, but would have to load jQuery UI for it to work. I don't want that either..

Here is what I tried (attempts commented):

(function($) {
    // SOLUTION 2 (see below too)
    // Not good either coz elements are not removed until resize is triggered
    /*
    var hScrolls = $([]);
    $(window).bind('resize.hScroll',function(){
        if(!hScrolls.length) return;
        hScrolls.each(function(){
            if($(this).data('hScroll')) $(this).hScroll('updateDimensions');
            else hScrolls = hScrolls.not($(this));
        });
    });
    */
    // END SOLUTION 2

    // SOLUTION 3 (not implemented but I think I'm on the right path)
    $(window).bind('resize.hScroll',function(){
        // need to get hScroll'ed elements via selector...
        $('[data-hScroll]').hScroll('updateDimensions');
        // I don't know how....
    });
    // END SOLUTION 3
    var methods = {
        init : function(options) {
            var settings = $.extend( {
                defaults: true
            }, options);

            return this.each(function() {
                var $this = $(this),
                    data = $this.data('hScroll');
                if (!data) {
                    $this.data('hScroll', {
                        target: $this
                    });
                    // SOLUTION 1
                    // This is not good: it keeps a reference to $this when I remove it...
                    /*
                    $(window).bind('resize.hScroll', function(){
                        $this.hScroll('updateDimensions');
                    });
                    */
                    // END SOLUTION 1
                    $this.hScroll('updateDimensions');

                    // SOLUTION 2 (see above too)

                    hScrolls = hScrolls.add(this);

                }
            });
        },
        updateDimensions: function(){
            var hScroll = this.data('hScroll');
            // do stuff with hScroll.target
        }
    }

    $.fn.hScroll = function(method) {
        if (methods[method]) {
            return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
        } else if ( typeof method === 'object' || !method) {
            return methods.init.apply(this, arguments);
        } else {
            $.error('Method ' + method + ' does not exist on jQuery.hScroll');
        }
    };
})(jQuery);​

Thanks all in advance!

Upvotes: 3

Views: 3844

Answers (3)

jantimon
jantimon

Reputation: 38160

You might use a class name and forward the resize event:

$.fn.hScroll = function(method) {
    this
      .addClass('hScroll')
      .data('method', arguments)
};

var methods['alert_text'] = function(config){
  alert( config + " " + $(this).text() );
}

$(window).bind('resize.hScroll',function(){
  $(".hScroll").each(function(){
     var method_config = $(this).data('method');
     var method = method_config.shift();
     // Forward the resize event with all resize event arguments:
     methods[method].apply(this, method_config);
   })
})

// Register a resize event for all a.test elements:
$("a.test").hScroll('alert_text', "hey");
// Would alert "hey you" for <a class="test">you</a> on every resize

Update

If you change the dom and want to keep the selector you might try this one:

var elements = [];
 $.fn.hScroll = function(method) {
     elements.push({'selector' : this.selector, 'arguments' : arguments });
 };

var methods['alert_text'] = function(config){
  alert( config + " " + $(this).text() );
}

$(window).bind('resize.hScroll',function(){
  $.each(elements,function(i, element){
    $(element.selector).each(function(){
       var method_config = element.arguments;
       var method = method_config.shift();
       // Forward the resize event with all resize event arguments:
       methods[method].apply(this, method_config);
     })
   })
})

// Register a resize event for all a.test elements:
$("a.test").hScroll('alert_text', "hey");
$(document.body).html("<a class='test'>you</a>");
// Would alert "hey you" for every window resize

Upvotes: 1

Kevin B
Kevin B

Reputation: 95066

jQuery calls cleanData any time you do something that removes or replaces elements (yes, even if you use parent.html("") ). You can take advantage of that by extending it and having it trigger an event on the target elements.

// This is taken from https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.widget.js 10/17/2012
if (!$.widget) { // prevent duplicating if jQuery ui widget is already included
    var _cleanData = $.cleanData;
    $.cleanData = function( elems ) {
        for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
            try {
                $( elem ).triggerHandler( "remove" );
                // http://bugs.jquery.com/ticket/8235
            } catch( e ) {}
        }
        _cleanData( elems );
    };
}

Now you can bind to the remove event when setting up your plugin and have it run your destroy method.

$(elem).bind("remove",methods.destroy)

Upvotes: 2

Fisch
Fisch

Reputation: 3815

You should have the scroll event bound in the extension. Also, you will want to add a "destroy" method to your extension as well. Before you remove the element from the DOM, you will want to call this method. Inside the detroy method is where you will want to unbind the resize event.

One important thing in making this work is that you have a reference to each handler method that is bound to the resize event. Alternatively, you can unbind All resize events upon the removal on an element and then rebind the scroll event to the remaining elements that require it.

Upvotes: 0

Related Questions