具有变量、事件和 DOM 操作的多个 jQuery 实例

Multiple jQuery instances with vars, events, and DOM manipulation

我正在做一个项目,一开始只是简单地构建一个函数,该函数接受一个参数(XML 文件)并将其转换为 HTML/CSS 结构(可以找到更早的版本 here). This worked great. However, I'd like to implement more options and more flexibility. I've read on the topic (e.g. 1, 2, 3) 但我无法理解它。

我的插件有很多特定于实例的需求:

还有一些重要的选项:

我将勾勒出插件当前状态的基本结构。

前两个选项是最重要的。但默认为 true,如果用户在 false 上同时拥有它们,则插件将不会执行。

插件然后分配全局变量并根据此信息创建新的 DOM 元素。实际上它看起来像这样(注意全局变量是在我的脚本顶部声明的)。

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

    if (args.normalView) {
        normalView = true;
        $(args.container).append('<div id="tree-visualizer" style="display: none"></div>');
        SS = $("#tree-visualizer");
        var SSHTML = '<div class="tv-error" style="display: none"><p></p></div>' +
            '<div class="tree" style="font-size: ' + args.fontSize + 'px;"></div>' +
            '<aside class="tooltip" style="display: none"><ul></ul>' +
            '<button>&#10005;</button></aside>';
        if (args.fsView) {
            SSHTML += '<button class="tv-show-fs">Fullscreen</button>';
        }
        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('<div id="fs-tree-visualizer-" class=""fs-tree-visualizer" style="display: none"></div>');
        FS = $("#fs-tree-visualizer");
        var FSHTML = '<div class="tv-error" style="display: none"><p></p></div>' +
            '<div class="tree"></div><aside class="tooltip" style="display: none"><ul></ul>' +
            '<button>&#10005;</button></aside><div class="zoom-opts"><button class="zoom-out">-</button>' +
            '<button class="zoom-default">Default</button><button class="zoom-in">+</button>' +
            '<button class="close">&#10005;</button></div>';
        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());
}

您会看到我正在处理 ID,这使得处理多个实例变得困难。我想,解决这个问题的一种方法是为样式添加 class,并通过使用跟踪实例化的全局计数器(每个实例上的 counter++)为每个实例添加一个 ID .请注意,当我希望将 FS 树和普通视图树作为目标时,会使用 anyTree。这并不意味着我要针对所有实例的所有树!这也必须针对每个实例进行限制。

所以我的问题是,如何允许多个实例,尤其是:如何在不失去我现在拥有的能力的情况下从全局变量转移到局部变量?此时此刻,我可以使用全局变量并在任何需要的地方访问每个变量。但是我怎样才能限制每个实例的全局变量呢?按照我的建议使用那个计数器?

此外,我在哪里分配事件?目前这是我的插件初始化的方式(我省略了全局变量和函数):

$.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();
            });
        });
    }

这是放置事件处理程序的正确位置吗?

下面的代码是我制作插件时使用的基本格式。我希望这可以帮助您找到一些问题的解决方案。

(function($) {
    $.fn.FUNCTIONNAME = function(options) {
        var obj = $(this);
        var opts = $.extend({
            OPTION1 : 'OPTION1 VALUE',
            OPTION2 : 'OPTION2 VALUE',
        }, options);
    }

    function FUNCTIONNAME(PARAMETER) {
        FUNCTION CONTENT
        IF YOU WANT TO USE OPTION VALUE,
        YOU CAN CALL IT BY A FORM AS opts.OPTION1
    }        

    obj.find($('ID OR CLASS DOESNT MATTER')).FUNCTIONNAME(PARAMETER);
    // this will make the plugin only work for this element

    return this; // or you can each() over every functions and return it
})(jQuery);

+我建议你不要在插件中使用 document.ready。事件处理程序可以放在任何地方。

编辑:这个 -> obj

XML 可视化工具看起来不错。 我有几个建议给你。

首先,您使用 $.fn.componentName 而不是 $.componentName.

创建一个 jquery-component/widget/plugin
$.fn.treeVisualizer

其次,我建议将所有全局值移动到选项中,或将局部变量移动到您的组件中。您提供的代码片段应该可以工作。每个实例可能不同的每个全局值都必须是一个选项。

$.fn.myComponent(options){
  var defaults = {
     option1: true,
     option2: 5
  };
  options = $.extend({}, defaults, options);//defaults and options are being merged into {}
  var constantValue = 15; //values that won't change, you can just move locally from global
}

第三,对于你的全局函数,我建议将它们与组件一起放在一个自调用函数中,这样组件就可以访问该函数并且它们不是不必要的全局。

(function initComponent(){
  $.fn.myComponent(){
    someFunction();
  }
  function someFunction(){
    //do something
  }
})();
//immediately invoke function/ self-invoking function

或者您可以将函数放在组件范围内。

$.fn.myComponent = function(){
  someFunction();
  function someFunction(){
    //do something
  }
}

第四,我建议使用 classes 而不是 id。您确实可以通过跟踪存在的实例数量来确保 id 是唯一的来解决这个问题,但只需使用 classes (fe, xml-id-1)。或者您也可以使用 data-id/data-xml-id 属性。不用担心在不同组件中查询具有相同 class 的元素,我将在下一个指针中介绍。

$jqueryElement.addClass('xml-id-'+id);
$('.xml-id-'+id);//you can query it by class-name like this

或者如果你想利用数据属性(我推荐它超过 classes 因为 html 更容易阅读并且在我看来更有意义)

$jqueryElement.attr('data-xml-id', id); //fe <div data-xml-id="1"></div>
$('[data-xml-id="'+id+'"]'); //you can query it like this

最后,每个查询和事件都需要与您的组件相关,以便您仅在组件实例中搜索元素和添加事件。这可确保您不会意外地对组件的另一个实例执行操作。

$.fn.myComponent=function(){
  $('[data-xml-id="1"]'); //this queries the whole DOM, don't do this!
  var $el = this.find('[data-xml-id="1"]'); //this only looks for matching elements within the current instance of the component
  $el.click(function(){// also add events to elements you searched within your components
    //do something
  );
}

最后像这样初始化你的组件。

$('#tree1').treeVisualizer(xml,
  {
    fullscreen: true
  }
);
$('#tree2').treeVisualizer(xml);

来自评论的额外问题:

查找方法

find 方法的作用与全局查询方法相同,不同之处在于它只在 jQuery 对象的 DOM 范围内搜索。

Get the descendants of each element in the current set of matched elements, filtered by a selector, jQuery object, or element. Source: jQuery documentation

示例:

var footer = $('footer');
footer.find('div');// searches for div's inside the footer

这种用法

在插件内部 this 指的是 jQuery您初始化插件的对象。

$.fn.myComponent = function(){
  console.log(this);// refers to $el
}

var $el = $('#id');
$el.myComponent();

在事件回调中,this 指的是您附加事件处理程序的本机元素。您必须将它包装在 jQuery 对象中才能对其执行 jQuery 操作。

$('button').click(function(){
  console.log(this); //prints out the button that was clicked
  $(this).css('background','#000'); //wrap native button element in jQuery object and perform jQuery methods on it
});

每当您不确定 this 也指向何处时,只需将其记录到控制台即可。 有了这个信息,您可以看到,当您在组件中使用 this.find('.tree-structure') 时,您确实只会搜索 jQuery-object 中带有 'tree-structure'-class 的元素用

初始化组件

您可以引用要插入的 DOM 元素,而无需重新查询 appendTo 返回对插入元素的引用

//before:
$(args.container).append('<div id="tree-visualizer" style="display: none"></div>');
$SS = $("#tree-visualizer"); //prefixing variables with $ when referencing jquery objects

//after:
$SS = $('<div style="display: none"></div>').appendTo( $(args.container) );

如果您将点击绑定应用为初始化代码的一部分

$SS.append(SSHTML).on('click', '.tv-show-fs', function() {
    $FS.show();
});

您会注意到 SSFS 现在都可以在本地范围内。

或者,您可以将 "show in full screen" 功能与核心插件分离,避免重复功能。一个天真的方法可能是切换一些样式,但即使你想操纵 DOM 你也明白了。您不能同时拥有多个有意义的全屏实例。

$SS.append(SSHTML).on('click', '.tv-show-fs', function() {
    $SS.toggleClass('tree-visualiser__fullscreen');
});