user3733648
user3733648

Reputation: 1333

Keeping the reference of DOM element in plugin

I am writing a plugin where I want to keep the reference of the DOM element on which I called my plugin

The code looks like

;(function ( $, window, document, undefined ) {
    "use strict";

    var $elem = $(this);    

    var methods = {
        /**
         * __constructor method
         * @param {} user defined options
         */
         init: function( options ) {

            $elem = (this);

            /**
             * save options
             */
            options = $.extend( true, {}, $.fn.xyz.defaults, options );
            $elem.data('options', options);

            console.info($elem);
         },

        /**
         * Attachs window's scroll event
         */
        attach: function(  ) {
            var w = $(window);
            w.on( 'scroll', methods._load);
            return this;
        },


        detach: function() {
            var w = $(window);  
            w.off( 'scroll', methods._load);    
        },

        /**
         * Tests it the DIV is within viewport
         */
        _visible: function() {
            var w = $(window);  

            return w.scrollTop() + w.height() >= $elem.offset().top;
        },


        _load: function() {
            if( methods._visible() ) {
                console.info("load data");      
                methods.detach();
            }
        }


    }





    $.fn.xyz = function() {
        var _this = $(this);


        if(!_this[0]) return _this;  // stop here if the container does not exist

        methods.init.apply( _this, arguments );
        return methods.attach.apply( _this );

    }

    $.fn.xyz.defaults = {
        onSuccess: false,
        onFailure: false,
        data: {}
    }

})( jQuery, window, document );

My problem is that within the methods I want to have the reference of the DOM element on which i use this plugin

In this case within

_visible method

I want to test if the DOM is now in visible area

return w.scrollTop() + w.height() >= $elem.offset().top;

I don't know how to keep track of the DOM element within the methods, as if there are more than one elements then how can I reference the the right DOM element.

Upvotes: 1

Views: 269

Answers (1)

Roamer-1888
Roamer-1888

Reputation: 19288

Your var $elem = $(this); won't work reliably because its scope is that of the entire plugin, not of any particular invocation of it.

Instead, make sure that :

  • methods are written such that their this is the jQuery collection being acted on.
  • methods are called/passed in such a way that their context, this, is specified. For that, you need to be familiar with .call(), .apply() and .bind().

Here's the modified code, complete with further improvements (untested) :

;(function ($, window, document, undefined) {
    "use strict";

    var pluginName = 'xyz'; //avoids potential repetition throughout the plugin

    // `defaults` can be defined here.
    // Like `methods` it will remain available due to closure.
    var defaults = {
        onSuccess: false,
        onFailure: false,
        data: {}
    };

    var methods = {
        /**
         * __constructor method
         * @param {} user defined options
         */
        init: function( options ) {
            // Each element in the collection should get its own *independent* options.
            // This immunises every element against future destruction or changes made to other elements' options.
            return this.each(function() {
                $( this ).data( pluginName+'Data', $.extend( true, {}, defaults, options ) );
            });
         },
        /**
         * Attaches window.onScroll handler(s)
         */
        attach: function() {
            // The attached handler is now subject to `.bind()` and, as a function in its own right, can't be removed with `$(window).off( 'scroll', methods._load);`.
            // A reference to each bound instance needs to be kept to allow it to be detached independently of any others.
            // The most convenient scope for each element to store a reference to *its* scroll handler is the data object established in init().
            return this.each(function() {
                var $this = $( this );
                var data = $this.data( pluginName+'Data' );
                data.scrollHandler = methods._load.bind( $this );
                $(window).on( 'scroll', data.scrollHandler );
            });
        },
        /**
         * Detaches window.onScroll handler(s)
         */
        detach: function() {
            return this.each(function() {
                var scrollHandler = $( this ).data( pluginName+'Data' ).scrollHandler;
                if( scrollHandler ) {
                    $( window ).off( 'scroll', scrollHandler );
                }
            });
        },
        /**
         * Tests it the DIV is within viewport
         */
        _visible: function() {
            var w = $( window );
            return w.scrollTop() + w.height() >= this.offset().top;
        },
        _load: function() {
            return this.each(function() {
                var $this = $( this );
                if( methods._visible.call( $this ) ) {
                    methods.detach.call( $this );
                }
            });
        }
    }
    $.fn[pluginName] = function() {
        var _this = this; // `this` is already a jQuery collection object, so no need for $(this).
        if(!_this[0]) return _this;  // stop here if the collection is empty
        methods.init.apply(_this, arguments);
        return methods.attach.call(_this);
    }
})(jQuery, window, document);

Upvotes: 2

Related Questions