Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

jQuery plugin with "local" variables belonging to the given plugin instance

I'm working on a jQuery plugin but I'm encountering issues with the "scope" of variables. Each plugin will need to keep track of a considerably sized multidimensional array, as well as the root element that the jQuery plugin was attached to.

As shown below, I've defined var $rootEl = null; and var multidArray = null; at the top of the plugin code; however, when I run $(anySelector).pluginName("value"); on multiple elements (or when I call .pluginName() twice with different selectors), it would appear that these variables are not sandboxed / scoped to the plugin instance, so the second instance overwrites both of the values and the first instance looks blank in the DOM (because all jQuery actions are applied on $rootEl with the original value being overwritten on the second jQuery plugin call).

(function ($) {

    var $rootEl = null;
    var multidArray = null;

    directChildren = function(uuid) {

        return $rootEl.children("[data-uuid=" + uuid + "]");

    },

    incrementCounter = function() {
        this.counter++;
    },

    resetGenIteration = function() {
        this.counter = 0;
    },

    process = function(item) { // Will be called from a callback(val) in another
        // javascript file but still needs to have access to the local variables
        // of the current jQuery plugin

        incrementCounter();

        ...

    },

    ...

    $.fn.pluginName = function(rootvar) {

        this.each(function() {

            $rootEl = $(this);

            resetGenIteration();
            populate(rootvar);

            $rootEl.addClass("pluginName").delegate("div", "click", divClick);

        });

    }

}(jQuery));

Yesterday I attempted to convert the plugin to use init and this.varname to store the values, and it appeared to hold onto the this.$rootEl property correctly for each instance of the plugin, but I had to move all my functions inside of var PluginName = { ... } and switch them from funcName = function() to funcName: function() to be able to see the plugin instance's "local" variables. Then I encountered numerous errors where functions were not defined, so I tried prefixing all my function calls with this. and PluginName. to read as this.functionName() or PluginName.functionName() respectively, which worked for some functions, but not all. Some of my functions are called from another jQuery plugin that runs a callback function and passes data to it, and in these cases, this.$rootEl becomes undefined.

(function ($){

    var PluginName = {

        init: function(options, rootEl) {

            this.options = $.extend({}, this.options, options);

            this.rootEl  = rootEl;
            this.$rootEl = $(rootEl);

            this.setListeners();

            return this;

        },

        options: {
            rootvar: "test"
        },

        ...

        setListeners:function (){

            var self = this;

            this.$rootEl.on("div", "click", function () {
                $.proxy(self.divClick, self);
            });

        }

    };

    ...

    $.fn.pluginName = function(options) {

        this.init = function(options, rootEl) {

            this.options = $.extend({}, this.options, options);

            this.rootEl  = rootEl;
            this.$rootEl = $(rootEl);
            this.multidArray = null;


            return this;

        };

        this.each(function() {

            var plugInstance = Object.create(PluginName);
            plugInstance.init(options, this);
            $.data(this, 'pluginName', plugInstance);

        });

    }

}(jQuery));

How do I refactor my code to store the multi-dimensional array and root element in a way that only that given plugin can internally retrieve and modify? Surely I should not be using .data() for large arrays?

Thanks!

like image 541
Chad Avatar asked Nov 06 '22 23:11

Chad


1 Answers

A plugin is just a function that gets attached to the jQuery object. It doesn't have multiple instances, but rather it can be called multiple times. Each call creates a new scope, however, variables declared outside the pluginName function will be shared by every call.

Rather than the object oriented approach you are attempting in your second iteration, I would suggest going back to using multiple functions. However, you can pass these functions all required arguments so they don't have to rely on ambiguous this values.

Since you only included a partial snippet of your code, I can't get a clear idea of what you are trying to accomplish, but I've included an example plugin below that highlights how to properly use scope.

(function ($) {
  // declare variables that can be shared by every call of "pluginName" here

  var defaultColor = 'green'

  // passing the element as an argument means we don't have to rely on ambiguous 'this'.
  function someTransformation ($el, someColor) {
    return $el.css({backgroundColor: someColor})
  }


  $.fn.pluginName = function (color, idx) {
    // declare variables that should be reinitialized on every call of "pluginName" here
    var els = []

    this.each(function () {
      var $el = $(this)
      someTransformation($el, defaultColor)
      els.push($el)
    })

    // we can still use the array we stored
    if (els[idx]) {
      someTransformation(els[idx], color)
    }
  }
})($)

$('#first i').pluginName('red', 2)
$('#second i').pluginName('blue', 0)
i {
  display: inline-block;
  width: 50px;
  height: 50px;
  margin: 10px;
}

div {
 border: 1px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<div id="first">
 <i></i><i></i><i></i><i></i>
</div>

<div id="second">
  <i></i><i></i><i></i><i></i>
</div>

If you are still having trouble, start by doing everything in the pluginName function. without declaring extra helpers. This will help you make sure that all of your logic is working properly. Then start moving any reused code into helper functions that accept all the arguments they need to run.

Somewhat relevant but not immediately helpful nitpick: The function expressions outside of pluginName (directChildren for example) are globals. Make sure to preface those with var to keep them scoped to your IIFE.

like image 192
Damon Avatar answered Nov 12 '22 17:11

Damon