我应该使用哪个jQuery插件设计模式? [英] Which jQuery plugin design pattern should I use?

查看:109
本文介绍了我应该使用哪个jQuery插件设计模式?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要构建一个jQuery插件,每个选择器id都会返回一个实例。该插件应该和将只用于具有id的元素(不可能使用与许多元素匹配的选择符),因此应该像这样使用:

I need to build a jQuery plugin that would return a single instance per selector id. The plugin should and will only be used on elements with id (not possible to use selector that matches many elements), so it should be used like this:

$('#element-id').myPlugin(options);




  • 我需要能够为插件提供很少的私有方法很少的公共方法。我可以实现这一点,但是我的主要问题是每次调用$('#element-id')时,我想要得到相同的实例。

  • 我想有一些代码只能在第一次为给定的ID(构造)初始化插件时执行。

  • 应该首先提供参数,对于构造,在此之后,我不希望执行该构造,以便我可以像$(' #element-id')。myPlugin()

  • 该插件应该能够在同一页面上使用多个元素(通常最多2个)(但是每个元素都会需要自己的配置,再次 - 它们将被ID初始化,例如不是常用的类选择器)。

  • 上面的语法仅仅是例子 - 我有任何关于如何实现该模式的建议开放

    • I need to be able to have few private methods for the plugin as well as few public methods. I can achieve that but my main issue is that I want to get the very same instance every time I call $('#element-id').myPlugin().
    • And I want to have some code that should be executed only the first time the plugin is initialized for a given ID (construct).
    • The options parameter should be supplied the first time, for the construct, after that I do not want the construct to be executed, so that I can access the plugin just like $('#element-id').myPlugin()
    • The plugin should be able to work with multiple elements (usually up to 2) on the same page (but each and every one of them will need own config, again - they will be initialized by ID, not common class selector for example).
    • The above syntax is just for example - I'm open for any suggestions on how to achieve that pattern
    • 我有一些OOP与其他语言的经验,但有限的JavaScript知识,我真的很困惑,如何做到正确。

      I have quite some OOP experience with other language, but limited knowledge of javascript and I'm really confused on how do it right.

      编辑

      要详细说明 - 这个插件是一个GoogleMaps v3 API包装(帮助),以帮助我摆脱代码重复,因为我在许多地方使用谷歌地图,通常用标记。这是当前的库(删除了很​​多代码,只是最重要的方法可以看到):

      To elaborate - this plugin is a GoogleMaps v3 API wrapper (helper) to help me get rid of code duplication as I use google maps on many places, usually with markers. This is the current library (lots of code removed, just most important methods are left to see):

      ;(function($) {
          /**
           * csGoogleMapsHelper set function.
           * @param options map settings for the google maps helper. Available options are as follows:
           * - mapTypeId: constant, http://code.google.com/apis/maps/documentation/javascript/reference.html#MapTypeId
           * - mapTypeControlPosition: constant, http://code.google.com/apis/maps/documentation/javascript/reference.html#ControlPosition
           * - mapTypeControlStyle: constant, http://code.google.com/apis/maps/documentation/javascript/reference.html#MapTypeControlStyle
           * - mapCenterLatitude: decimal, -180 to +180 latitude of the map initial center
           * - mapCenterLongitude: decimal, -90 to +90 latitude of the map initial center
           * - mapDefaultZoomLevel: integer, map zoom level
           * 
           * - clusterEnabled: bool
           * - clusterMaxZoom: integer, beyond this zoom level there will be no clustering
           */
          $.fn.csGoogleMapsHelper = function(options) {
              var id = $(this).attr('id');
              var settings = $.extend(true, $.fn.csGoogleMapsHelper.defaults, options);
      
              $.fn.csGoogleMapsHelper.settings[id] = settings;
      
              var mapOptions = {
                  mapTypeId: settings.mapTypeId,
                  center: new google.maps.LatLng(settings.mapCenterLatitude, settings.mapCenterLongitude),
                  zoom: settings.mapDefaultZoomLevel,
                  mapTypeControlOptions: {
                      position: settings.mapTypeControlPosition,
                      style: settings.mapTypeControlStyle
                  }
              };
      
              $.fn.csGoogleMapsHelper.map[id] = new google.maps.Map(document.getElementById(id), mapOptions);
          };
      
          /**
           * 
           * 
           * @param options settings object for the marker, available settings:
           * 
           * - VenueID: int
           * - VenueLatitude: decimal
           * - VenueLongitude: decimal
           * - VenueMapIconImg: optional, url to icon img
           * - VenueMapIconWidth: int, icon img width in pixels
           * - VenueMapIconHeight: int, icon img height in pixels
           * 
           * - title: string, marker title
           * - draggable: bool
           * 
           */
          $.fn.csGoogleMapsHelper.createMarker = function(id, options, pushToMarkersArray) {
              var settings = $.fn.csGoogleMapsHelper.settings[id];
      
              markerOptions = {
                      map:  $.fn.csGoogleMapsHelper.map[id],
                      position: options.position || new google.maps.LatLng(options.VenueLatitude, options.VenueLongitude),
                      title: options.title,
                      VenueID: options.VenueID,
                      draggable: options.draggable
              };
      
              if (options.VenueMapIconImg)
                  markerOptions.icon = new google.maps.MarkerImage(options.VenueMapIconImg, new google.maps.Size(options.VenueMapIconWidth, options.VenueMapIconHeight));
      
              var marker = new google.maps.Marker(markerOptions);
              // lets have the VenueID as marker property
              if (!marker.VenueID)
                  marker.VenueID = null;
      
              google.maps.event.addListener(marker, 'click', function() {
                   $.fn.csGoogleMapsHelper.loadMarkerInfoWindowContent(id, this);
              });
      
              if (pushToMarkersArray) {
                  // let's collect the markers as array in order to be loop them and set event handlers and other common stuff
                   $.fn.csGoogleMapsHelper.markers.push(marker);
              }
      
              return marker;
          };
      
          // this loads the marker info window content with ajax
          $.fn.csGoogleMapsHelper.loadMarkerInfoWindowContent = function(id, marker) {
              var settings = $.fn.csGoogleMapsHelper.settings[id];
              var infoWindowContent = null;
      
              if (!marker.infoWindow) {
                  $.ajax({
                      async: false, 
                      type: 'GET', 
                      url: settings.mapMarkersInfoWindowAjaxUrl, 
                      data: { 'VenueID': marker.VenueID },
                      success: function(data) {
                          var infoWindowContent = data;
                          infoWindowOptions = { content: infoWindowContent };
                          marker.infoWindow = new google.maps.InfoWindow(infoWindowOptions);
                      }
                  });
              }
      
              // close the existing opened info window on the map (if such)
              if ($.fn.csGoogleMapsHelper.infoWindow)
                  $.fn.csGoogleMapsHelper.infoWindow.close();
      
              if (marker.infoWindow) {
                  $.fn.csGoogleMapsHelper.infoWindow = marker.infoWindow;
                  marker.infoWindow.open(marker.map, marker);
              }
          };
      
          $.fn.csGoogleMapsHelper.finalize = function(id) {
              var settings = $.fn.csGoogleMapsHelper.settings[id];
              if (settings.clusterEnabled) {
                  var clusterOptions = {
                      cluster: true,
                      maxZoom: settings.clusterMaxZoom
                  };
      
                  $.fn.csGoogleMapsHelper.showClustered(id, clusterOptions);
      
                  var venue = $.fn.csGoogleMapsHelper.findMarkerByVenueId(settings.selectedVenueId);
                  if (venue) {
                      google.maps.event.trigger(venue, 'click');
                  }
              }
      
              $.fn.csGoogleMapsHelper.setVenueEvents(id);
          };
      
          // set the common click event to all the venues
          $.fn.csGoogleMapsHelper.setVenueEvents = function(id) {
              for (var i in $.fn.csGoogleMapsHelper.markers) {
                  google.maps.event.addListener($.fn.csGoogleMapsHelper.markers[i], 'click', function(event){
                      $.fn.csGoogleMapsHelper.setVenueInput(id, this);
                  });
              }
          };
      
          // show the clustering (grouping of markers)
          $.fn.csGoogleMapsHelper.showClustered = function(id, options) {
              // show clustered
              var clustered = new MarkerClusterer($.fn.csGoogleMapsHelper.map[id], $.fn.csGoogleMapsHelper.markers, options);
              return clustered;
          };
      
          $.fn.csGoogleMapsHelper.settings = {};
          $.fn.csGoogleMapsHelper.map = {};
          $.fn.csGoogleMapsHelper.infoWindow = null;
          $.fn.csGoogleMapsHelper.markers = [];
      })(jQuery);
      

      它的用法看起来像这样(实际上不是这样,因为有一个PHP包装器来自动化一个电话,但基本上):

      It's usage looks like this (not actually exactly like this, because there is a PHP wrapper to automate it with one call, but basically):

      $js = "$('#$id').csGoogleMapsHelper($jsOptions);\n";
      
      if ($this->venues !== null) {
          foreach ($this->venues as $row) {
              $data = GoogleMapsHelper::getVenueMarkerOptionsJs($row);
              $js .= "$.fn.csGoogleMapsHelper.createMarker('$id', $data, true);\n";
          }
      }
      
      $js .= "$.fn.csGoogleMapsHelper.finalize('$id');\n";
      echo $js;
      

      上述实现的问题是我不想保留哈希映射为设置和地图

      The problems of the above implementation are that I don't like to keep a hash-map for "settings" and "maps"

      $ id 是映射初始化的DIV元素ID。它被用作.map和.settings中的一个关键字,我在该页面上为每个初始化的这样的Google Map创建了地图。来自PHP代码的 $ jsOptions $ data 是JSON对象。

      The $id is the DIV element ID where the map is initialized. It's used as a key in the .map and .settings has maps where I hold the settings and GoogleMaps MapObject instance for each initialized such GoogleMaps on the page. The $jsOptions and $data from the PHP code are JSON objects.

      现在,我需要创建一个GoogleMapsHelper实例,它保存自己的设置和Google Map映射对象,以便在对某个元素进行初始化(通过其ID)后,我可以重用该实例。但是如果我在页面上的N个元素上初始化它们,那么每个应该都有自己的配置,地图对象等。

      Now I need to be able to create a GoogleMapsHelper instance that holds its own settings and GoogleMaps map object so that after I initialize it on certain element (by its ID), I can reuse that instance. But if I initialize it on N elements on the page, each and every of them should have own configuration, map object, etc.

      我不坚持这实现为一个jQuery插件!我坚持认为它是灵活和可扩展的,因为我将使用它在一个大型项目与十几个目前计划的不同的屏幕,几个月将被使用,改变它的使用

      I do not insist that this is implemented as a jQuery plugin! I insist that it's flexible and extendable, because I will be using it in a large project with over dozen currently planned different screens where it will be used so in few months, changing it's usage interface would be a nightmare to refactor on the whole project.

      我将为此添加一个赏金。

      I will add a bounty for this.

      推荐答案

      当您通过 $('#element)get实例myPlugin()我假设你的意思是:

      When you say "get" the instance via $('#element').myPlugin() I assume you mean something like:

      var instance = $('#element').myPlugin();
      instance.myMethod();
      

      这可能似乎是一个好主意,但它被认为是扩展jQuery原型的不良做法,因为你打破了jQuery实例链。

      This might seem to be a good idea at first, but it’s considered bad practice for extending the jQuery prototype, since you break the jQuery instance chain.

      另一个方便的方法是将实例保存在$ $ $ data $ / code>对象,所以你只需要初始化插件一次,那么你可以随时获取实例,只需要使用DOM元素作为参考,f.ex:

      Another handy way to do this is to save the instance in the $.data object, so you just initialize the plugin once, then you can fetch the instance at any time with just the DOM element as a reference, f.ex:

      $('#element').myPlugin();
      $('#element').data('myplugin').myMethod();
      

      这是一个我用来在JavaScript和jQuery中维护类似类结构的模式(包括注释,希望你可以遵循):

      Here is a pattern I use to maintain a class-like structure in JavaScript and jQuery (comments included, hope you can follow):

      (function($) {
      
          // the constructor
          var MyClass = function( node, options ) {
      
              // node is the target
              this.node = node;
      
              // options is the options passed from jQuery
              this.options = $.extend({
      
                  // default options here
                  id: 0
      
              }, options);
      
          };
      
          // A singleton for private stuff
          var Private = {
      
              increaseId: function( val ) {
      
                  // private method, no access to instance
                  // use a bridge or bring it as an argument
                  this.options.id += val;
              }
          };
      
          // public methods
          MyClass.prototype = {
      
              // bring back constructor
              constructor: MyClass,
      
              // not necessary, just my preference.
              // a simple bridge to the Private singleton
              Private: function( /* fn, arguments */ ) {
      
                  var args = Array.prototype.slice.call( arguments ),
                      fn = args.shift();
      
                  if ( typeof Private[ fn ] == 'function' ) {
                      Private[ fn ].apply( this, args );
                  }
              },
      
              // public method, access to instance via this
              increaseId: function( val ) {
      
                  alert( this.options.id );
      
                  // call a private method via the bridge
                  this.Private( 'increaseId', val );
      
                  alert( this.options.id );
      
                  // return the instance for class chaining
                  return this;
      
              },
      
              // another public method that adds a class to the node
              applyIdAsClass: function() {
      
                  this.node.className = 'id' + this.options.id;
      
                  return this;
      
              }
          };
      
      
          // the jQuery prototype
          $.fn.myClass = function( options ) {
      
              // loop though elements and return the jQuery instance
              return this.each( function() {
      
                  // initialize and insert instance into $.data
                  $(this).data('myclass', new MyClass( this, options ) );
              });
          };
      
      }( jQuery ));
      

      现在,您可以执行以下操作:

      Now, you can do:

      $('div').myClass();
      

      这将为每个找到的div添加一个新实例,并将其保存在$ .data中。现在,要检索某个实例的应用方法,您可以执行以下操作:

      This will add a new instance for each div found, and save it inside $.data. Now, to retrive a certain instance an apply methods, you can do:

      $('div').eq(1).data('myclass').increaseId(3).applyIdAsClass();
      

      这是一个很多次使用的模式,对我的需求非常有用。

      This is a pattern I have used many times that works great for my needs.

      您还可以公开类,以便您可以在没有jQuery prototyp的情况下使用它,方法是添加 window.MyClass = MyClass 。这允许以下语法:

      You can also expose the class so you can use it without the jQuery prototyp by adding window.MyClass = MyClass. This allows the following syntax:

      var instance = new MyClass( document.getElementById('element'), {
          id: 5
      });
      instance.increaseId(5);
      alert( instance.options.id ); // yields 10
      

      这篇关于我应该使用哪个jQuery插件设计模式?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆