JQuery插件在单个页面中的多个位置使用时无效

时间:2016-03-13 15:15:26

标签: javascript jquery jquery-plugins jquery-boilerplate

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

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

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

如果页面上只有一个带有“.tabs2accordion”类的元素,则插件会按预期工作,但只要将具有相同类的另一个元素添加到页面中,该插件就会出现故障。我已经创建了一个基本代码的codepen来演示这个问题。要显示此问题,请在窗口大小>> 768px上尝试单击任何标题,并观察下面的内容在单击每个标题时的更改方式。接下来取消注释HTML块并尝试再次单击标题。

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

我尝试使用类“tabs2accordion”循环遍历每个元素,如下所示:

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

但这也没有解决问题。

有什么想法吗?

2 个答案:

答案 0 :(得分:6)

我没有使用过jQuery Boilerplate,但我认为这里的问题是你的变量plugin

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

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

修复是在设置plugin挂钩时传递对$().on()对象的引用。传递的数据在事件处理程序中通过event属性中传递的data参数提供。

这是解决方案(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"
  });
});

答案 1 :(得分:1)

我按照jQuery&#39s的插件创建标准重写了你的代码。

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

基本上,我做了两件事:

  • 远离使用数据属性进行样式设置(切换为使用.active类)
  • 远离在任何地方使用this,因为它带来了一波又一波的约束性问题......

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

然后,所有内部方法都是函数表达式,它们与所有旧this"变量"的范围相同。这大大简化了代码,因为您可以引用这些变量而不将它们作为参数传递或不必以某种方式.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'
    })
})