JQuery 在单个页面的多个位置使用时插件无法正常工作

JQuery plugin not working when used in multiple places in a single page

我正在为我正在从事的项目编写一个 JQuery 插件,该项目从桌面设备上的选项卡式内容转变为移动设备上的手风琴。我使用 JQuery 样板 (https://github.com/jquery-boilerplate/jquery-boilerplate/blob/master/dist/jquery.boilerplate.js) 作为我的插件的初始模式。

在具有 class“.tabs2accordion”的任何元素上调用插件,如下所示:

 $(".tabs2accordion").tabs2Accordion({state:"desktop"});

如果页面上只有一个带有“.tabs2accordion”class 的元素,插件将按预期工作,但一旦将另一个具有相同 class 的元素添加到页。我已经创建了一个基本代码的代码笔来演示这个问题。要显示此问题,请在 window 大小 >768px 上尝试单击任何标题并观察单击每个标题时下面的内容如何变化。接下来取消注释 HTML 的块并尝试再次单击标题。

http://codepen.io/decodedcreative/pen/MyjpRj

我试过像这样用 class "tabs2accordion" 遍历每个元素:

$(".tabs2accordion").each(function(){
    $(this).tabs2Accordion({state:"desktop"});
});

但这也没有解决问题。

有什么想法吗?

我没有使用 jQuery 样板,但我相信这里的问题出在你的名为 plugin.

的变量上

您的代码中没有任何地方声明名为 plugin 的变量。当我在 Plugin.prototype.showTabContent 中停止调试器时,我可以评估 window.plugin 并且它 returns 插件的全局值。

在插件的构造函数中,第一行显示为 plugin= this;。由于未定义 plugin,因此它在 window 对象的全局范围内声明变量。

解决方法是在设置 $().on() 挂钩时传递对 plugin 对象的引用。传递的数据通过 data 属性.

中传递的 event 参数在事件处理程序中可用

这是解决方案(在 http://codepen.io/shhQuiet/pen/JXEjMV

(function($, window, document, undefined) {
  var pluginName = "tabs2Accordion",
    defaults = {
      menuSelector: ".tabs2accordion-menu",
      tabContentSelector: ".tabs2accordion-content"
    };

  function Plugin(element, options) {
    this.element = element;
    this.$element = $(this.element);
    this.options = $.extend({}, defaults, options);
    this.$menu = $(this.element).find(this.options.menuSelector),
    this.$tabs = $(this.element).find(this.options.tabContentSelector),
    this.$accordionTriggers = $(this.element).find(this.$tabs).find("h3");
    this._defaults = defaults;
    this._name = pluginName;
    this.init();
  }

  Plugin.prototype = {

    init: function() {
      //Set all the tab states to inactive
      this.$tabs.attr("data-active", false);

      //Set the first tab to active
      this.$tabs.first().attr("data-active", true);

      //If you click on a tab, show the corresponding content
      this.$menu.on("click", "li", this, this.showTabContent);

      //Set the dimensions (height) of the plugin
      this.resizeTabs2Accordion({
        data: this
      });

      //If the browser resizes, adjust the dimensions (height) of the plugin
      $(window).on("resize", this, this.resizeTabs2Accordion);

      //Add a loaded class to the plugin which will fade in the plugin's content
      this.$element.addClass("loaded");

      console.log(this.$element);

    },

    resizeTabs2Accordion: function(event) {
      var contentHeight;
      var plugin = event.data;

      if (!plugin.$element.is("[data-nested-menu]")) {
        contentHeight = plugin.$tabs.filter("[data-active='true']").outerHeight() + plugin.$menu.outerHeight();
      } else {
        contentHeight = plugin.$tabs.filter("[data-active='true']").outerHeight();
      }

      plugin.$element.outerHeight(contentHeight);
    },

    showTabContent: function(event) {
      var $target;
      var plugin = event.data;
      plugin.$menu.children().find("a").filter("[data-active='true']").attr("data-active", false);
      plugin.$tabs.filter("[data-active='true']").attr("data-active", false);
      $target = $($(this).children("a").attr("href"));
      $(this).children("a").attr("data-active", true);
      $target.attr("data-active", true);
      plugin.resizeTabs2Accordion({data: plugin});

      return false;
    },

    showAccordionContent: function(event) {
      var plugin = event.data;
      $("[data-active-mobile]").not($(this).parent()).attr("data-active-mobile", false);

      if ($(this).parent().attr("data-active-mobile") === "false") {
        $(this).parent().attr("data-active-mobile", true);
      } else {
        $(this).parent().attr("data-active-mobile", false);
      }
    }

  };

  $.fn[pluginName] = function(options) {
    return this.each(function() {
      if (!$.data(this, "plugin_" + pluginName)) {
        $.data(this, "plugin_" + pluginName, new Plugin(this, options));
      }
    });
  };

})(jQuery, window, document);

$(window).on("load", function() {
  $(".tabs2accordion").tabs2Accordion({
    state: "desktop"
  });
});

我按照 jQuery 的插件创建标准重写了您的代码。

http://codepen.io/justinledouxmusique/pen/GZrMgB

基本上,我做了两件事:

  • 不再使用数据属性进行样式设置(改为使用 .active class)
  • 不再在任何地方使用 this,因为它会带来一连串的绑定问题...

$.fn.tabs2Accordion 遍历所有选择器,并应用 $.tabs2Accordion。它还 returns 链接选择器(这是 jQuery 中的标准)。

然后,所有内部方法都是函数表达式,它们与您所有的旧 this "variables" 在同一范围内。这大大简化了代码,因为您可以引用这些变量而无需将它们作为参数传递,也不必以某种方式 .bind( this )

最后,旧的 init() 功能消失了。相反,我将代码放在 $.tabs2Accordion 函数的末尾。

希望对您有所帮助!

(function ( window, $ ) {
    $.tabs2Accordion = function ( node, options ) {
        var options = $.extend({}, {
                    menuSelector: '.tabs2accordion-menu',
                    tabContentSelector: '.tabs2accordion-content'
                }, options )

        var $element = $( node ),
                $menu = $element.find( options.menuSelector ),
                $tabs = $element.find( options.tabContentSelector ),
                $accordionTriggers = $tabs.find( 'h3' )

        var resizeTabs2Accordion = function () {
            $element.outerHeight( !$element.is( '[data-nested-menu]' )
                ? $element.find( 'div.active' ).outerHeight() + $menu.outerHeight()
                : $element.find( 'div.active' ).outerHeight() )
        }

        var showTabContent = function () {
            var $this = $( this ) // This will be the clicked element

            $menu
                .find( '.active' )
                    .removeClass( 'active' )

            $element
                .find( '.active' )
                    .removeClass( 'active' )

            $( $this.find( 'a' ).attr( 'href' ) )
                .addClass( 'active' )

            $this
                .find( 'a' )
                    .addClass( 'active' )

            resizeTabs2Accordion()

            return false
        }

        var showAccordionContent = function () {
            var $this                   = $( this ),
                    $parent                 = $this.parent(),
                    mobileIsActive  = $parent.data( 'active-mobile' )

            $( '[data-active-mobile]' )
                .not( $parent )
                    .data( 'active-mobile', false )

            $parent
                .data( 'active-mobile', mobileIsActive ? false : true )
        }

        // The equivalent of init()
        $tabs
            .removeClass( 'active' )
            .first()
                .addClass( 'active' )

        $element.addClass( 'loaded' )

        $menu.on( 'click', 'li', showTabContent )

        $( window ).on( 'resize', resizeTabs2Accordion )

        resizeTabs2Accordion()

        console.log( $element )
    }

    $.fn.tabs2Accordion = function ( options ) {
        this.each( function ( index, node ) {
            $.tabs2Accordion( node, options )
        })

        return this
    }
})( window, jQuery )

$( window ).on( 'load', function () {
    $( '.tabs2accordion' ).tabs2Accordion({
        state: 'desktop'
    })
})