Multiple jQuery instances with vars, events, and DOM manipulation
<p>I am working on a project that started out as simply building a function that takes an argument (XML file) and converts it into a HTML/CSS structure (earlier version to be found <a href="http://bramvanroy.be/projects/html-trees/" rel="noreferrer">here</a>). This worked great. However, I'd like to implement more options and more flexibility. I've read on the topic (e.g. <a href="https://learn.jquery.com/plugins/" rel="noreferrer">1</a>, <a href="https://css-tricks.com/forums/topic/multiple-instances-of-jquery-plugin-on-one-page/" rel="noreferrer">2</a>, <a href="http://pioul.fr/architecture-of-a-multi-instantiable-jquery-plugin" rel="noreferrer">3</a>) but I can't wrap my head around it.</p>

<p>My plugin has a lot instance-specific needs:</p>

<ul>
<li>variables</li>
<li>options</li>
<li>event handlers</li>
</ul>

<p>and there are some important options:</p>

<ul>
<li>display a fullscreen version</li>
<li>display a normal version (comes with a "open fs version" button)</li>
<li>specify a container for the normal version</li>
<li>set fontsize of normal version</li>
<li>set possible fontsizes for fs version (for zooming in and out the tree)</li>
<li>set a class that, when a user clicks on an element with that class, opens the fs version</li>
</ul>

<p>I'll sketch the base structure of the current state of the plugin.</p>

<p>The two first options are the most important ones. But default to <code>true</code>, and if a user has both of them on <code>false</code>, the plugin won't execute. </p>

<p>The plugin then assigns global variables and creates new DOM elements based on this information. In practice it looks something like this (note that the global variables are declared at the top of my script).</p>

<pre><code>function initVars(args) {
    fontsizes = fsFontSizes;
    errorContainer = $(".tv-error");
    var trees = [],
        tooltips = [];

    if (args.normalView) {
        normalView = true;
        $(args.container).append('&lt;div id="tree-visualizer" style="display: none"&gt;&lt;/div&gt;');
        SS = $("#tree-visualizer");
        var SSHTML = '&lt;div class="tv-error" style="display: none"&gt;&lt;p&gt;&lt;/p&gt;&lt;/div&gt;' +
            '&lt;div class="tree" style="font-size: ' + args.fontSize + 'px;"&gt;&lt;/div&gt;' +
            '&lt;aside class="tooltip" style="display: none"&gt;&lt;ul&gt;&lt;/ul&gt;' +
            '&lt;button&gt;&amp;#10005;&lt;/button&gt;&lt;/aside&gt;';
        if (args.fsView) {
            SSHTML += '&lt;button class="tv-show-fs"&gt;Fullscreen&lt;/button&gt;';
        }
        SS.append(SSHTML);

        treeSS = SS.find(".tree");
        tooltipSS = SS.find(".tooltip");

        trees.push("#tree-visualizer .tree");
        tooltips.push("#tree-visualizer .tooltip");
    }
    if (args.fsView) {
        fsView = true;
        $("body").append('&lt;div id="fs-tree-visualizer-" class=""fs-tree-visualizer" style="display: none"&gt;&lt;/div&gt;');
        FS = $("#fs-tree-visualizer");
        var FSHTML = '&lt;div class="tv-error" style="display: none"&gt;&lt;p&gt;&lt;/p&gt;&lt;/div&gt;' +
            '&lt;div class="tree"&gt;&lt;/div&gt;&lt;aside class="tooltip" style="display: none"&gt;&lt;ul&gt;&lt;/ul&gt;' +
            '&lt;button&gt;&amp;#10005;&lt;/button&gt;&lt;/aside&gt;&lt;div class="zoom-opts"&gt;&lt;button class="zoom-out"&gt;-&lt;/button&gt;' +
            '&lt;button class="zoom-default"&gt;Default&lt;/button&gt;&lt;button class="zoom-in"&gt;+&lt;/button&gt;' +
            '&lt;button class="close"&gt;&amp;#10005;&lt;/button&gt;&lt;/div&gt;';
        FS.hide().append(FSHTML);
        treeFS = FS.find(".tree");
        tooltipFS = FS.find(".tooltip");
        zoomOpts = FS.find(".zoom-opts");
        zoomCounter = Math.round(fontSizes.length / 2);

        trees.push("#fs-tree-visualizer .tree");
        tooltips.push("#fs-tree-visualizer .tooltip");
    }

    if (args.fsBtn != "") {
        $(args.fsBtn).addClass("tv-show-fs");
    }

    anyTree = $(trees.join());
    anyTooltip = $(tooltips.join());
}
</code></pre>

<p>You'll see that I am working with IDs which makes it hard to work with multiple instances. One way to solve this, I thought, would be to add a class for styling, and add an ID to each instance by using a global counter that keeps track of the instantiations (<code>counter++</code> on each instance). Note that <code>anyTree</code> is used when I wish to target FS tree as well as the normal views tree. This does NOT mean that I want to target all trees of all instances! This has to be restricted per instance as well.</p>

<p>So my question is, how do I allow for multiple instances and especially: how can I move from global variables to local variables without losing the power that I have now? At this moment I can work with global variables and access each variable wherever I want. But how can I restrict the global variable per instance? Work with that counter as I proposed?</p>

<p>Also, where do I assign events? Currently this is how my plugin initializes (I left out global variables and functions):</p>

<pre><code>$.treeVisualizer = function(xml, options) {
        var args = $.extend({}, $.treeVisualizer.defaults, options);

        /* At least one of the arguments (default true) have to be true */
        if (args.normalView || args.fsView) {
            initVars(args);
            loadXML(xml);

        } else {
            console.error("Cannot initialize Tree Visualizer: either the container " +
                "does not exist, or you have set both normal and fullscreen view to " +
                "false, which does not make sense.");
        }

        /* Event handlers -- only after we've initiated the variables to globals */
        $(document).ready(function() {
            // Show fs-tree-visualizer tree
            $(".tv-show-fs").click(function(e) {
                // Show corresponding fullscreen version
                FS.show();
                // Execute some functions
                sizeTreeFS();
                e.preventDefault();
            });

            // Zooming
            zoomOpts.find("button").click(function() {
                var $this = $(this);
                // Do something
            });

            anyTree.on("click", "a", function(e) {
                // Do something, and execute function: has to 
                // target the right tooltip
                tooltipPosition();
                e.preventDefault();
            });
        });
    }
</code></pre>

<p>Is this the right place to put event handlers?</p>