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

Bra*_*roy 10 jquery global-variables instance-variables jquery-plugins

我正在开发一个项目,最初只是构建一个带有参数(XML文件)的函数,并将其转换为HTML/CSS结构(此处可以找到早期版本).这很有效.但是,我想实现更多选项和更多灵活性.我读过的主题(如1,2,3),但我不能换我的头周围.

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

  • 变量
  • 选项
  • 事件处理程序

并且有一些重要的选择:

  • 显示全屏版本
  • 显示正常版本(附带"打开fs版本"按钮)
  • 指定正常版本的容器
  • 设置普通版本的fontsize
  • 为fs版本设置可能的字体大小(用于放大和缩小树)
  • 设置一个类,当用户单击具有该类的元素时,将打开fs版本

我将描绘插件当前状态的基本结构.

两个第一选项是最重要的选项.但默认为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());
}
Run Code Online (Sandbox Code Playgroud)

您将看到我正在使用ID,这使得很难处理多个实例.我想,解决这个问题的一种方法是添加一个样式类,并使用一个跟踪实例化的全局计数器(counter++在每个实例上)为每个实例添加一个ID .注意,anyTree当我希望定位FS树以及普通视图树时使用.这并不意味着我想要定位所有实例的所有树!这也必须在每个实例中受到限制.

所以我的问题是,如何允许多个实例,尤其是:如何在不失去现有功能的情况下从全局变量转移到局部变量?此时我可以使用全局变量并在任何我想要的地方访问每个变量.但是如何限制每个实例的全局变量?像我提议的那样使用那个柜台?

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

$.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();
            });
        });
    }
Run Code Online (Sandbox Code Playgroud)

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

Swi*_*ger 5

那个XML可视化工具看起来不错.我有几个指示你.

首先,使用$ .fn.componentName而不是$ .componentName创建jquery-component/widget/plugin.

$.fn.treeVisualizer
Run Code Online (Sandbox Code Playgroud)

其次,我建议将所有全局值移动到选项中,或将局部变量移动到组件中.您提供的代码段应该有效.每个实例的每个全局值都必须是一个选项.

$.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
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

$.fn.myComponent = function(){
  someFunction();
  function someFunction(){
    //do something
  }
}
Run Code Online (Sandbox Code Playgroud)

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

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

或者如果你想利用数据属性(我推荐使用数据属性,因为html更容易阅读,并且有更多的意义imo)

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

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

$.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
  );
}
Run Code Online (Sandbox Code Playgroud)

然后最终初始化你的组件.

$('#tree1').treeVisualizer(xml,
  {
    fullscreen: true
  }
);
$('#tree2').treeVisualizer(xml);
Run Code Online (Sandbox Code Playgroud)

评论中的额外问题:

查找方法

find方法与全局查询方法的作用相同,区别在于它只在jQueryObject的DOM中搜索.

获取当前匹配元素集中每个元素的后代,由选择器,jQuery对象或元素过滤.来源:jQuery文档

例:

var footer = $('footer');
footer.find('div');// searches for div's inside the footer
Run Code Online (Sandbox Code Playgroud)

这种用法

插件内部是this指您初始化插件的jQueryObject.

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

var $el = $('#id');
$el.myComponent();
Run Code Online (Sandbox Code Playgroud)

在事件回调中,这指的是您附加事件处理程序的本机元素.您必须将其包装在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
});
Run Code Online (Sandbox Code Playgroud)

每当您不确定其中的位置时this,只需将其记录到控制台即可.通过这些信息,您可以看到this.find('.tree-structure')在组件内部使用时,您确实只会在初始化组件的jQuery对象中搜索具有"tree-structure"类的元素.