我应该使用哪种jQuery插件设计模式?
我需要构建一个jQuery插件,它将为每个选择器id返回一个实例。 该插件应该并且将仅用于具有id的元素(不可能使用与许多元素匹配的选择器),因此它应该像这样使用:
$('#element-id').myPlugin(options);
- 我需要能够为插件提供一些私有方法以及一些公共方法。 我可以实现这一点,但我的主要问题是每次调用$(’#element-id’)。myPlugin()时我想得到相同的实例。
- 我希望有一些代码只应在第一次为给定ID(构造)初始化插件时执行。
- 应该首次为构造提供
options
参数,之后我不希望执行构造,这样我就可以像$(’#element-id’)一样访问插件.myPlugin() - 该插件应该能够在同一页面上使用多个元素(通常最多2个)(但是每个元素都需要自己的配置,它们将通过ID初始化,例如,不是常见的类选择器)。
- 上面的语法只是举例 – 我对如何实现该模式的任何建议持开放态度
我对其他语言有很多OOP经验,但对javascript的知识有限,我真的很困惑如何正确。
编辑
详细说明 – 这个插件是一个GoogleMaps v3 API包装器(帮助器)来帮助我摆脱代码重复,因为我在许多地方使用谷歌地图,通常使用标记。 这是当前的库(删除了大量代码,只剩下最重要的方法):
;(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包装器通过一次调用自动化它,但基本上):
$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;
以上实现的问题是我不喜欢为“设置”和“地图”保留哈希映射
$id
是初始化地图的DIV元素ID。 它用作.map中的一个键,而.settings有一些映射,我在页面上为每个初始化的这样的GoogleMaps保存设置和GoogleMaps MapObject实例。 PHP代码中的$jsOptions
和$data
是JSON对象。
现在我需要能够创建一个拥有自己的设置和GoogleMaps地图对象的GoogleMapsHelper实例,以便在我对某个元素(通过其ID)初始化之后,我可以重用该实例。 但是如果我在页面上的N个元素上初始化它,它们中的每一个都应该有自己的配置,映射对象等。
我不坚持认为这是作为jQuery插件实现的! 我坚持认为它具有灵活性和可扩展性,因为我将在一个大型项目中使用它,目前计划使用不同的屏幕将在几个月内使用,因此更改它的使用界面将是整个项目重构的噩梦。
我会为此添加一笔赏金。
当你通过$('#element').myPlugin()
来说“获取”实例$('#element').myPlugin()
我假设你的意思是:
var instance = $('#element').myPlugin(); instance.myMethod();
这一开始似乎是一个好主意,但是因为你破坏了jQuery实例链,所以它被认为是扩展jQuery原型的不良做法。
另一种方便的方法是将实例保存在$.data
对象中,因此您只需初始化插件一次,然后您可以随时使用DOM元素作为参考来获取实例,f.ex:
$('#element').myPlugin(); $('#element').data('myplugin').myMethod();
这是我用来在JavaScript和jQuery中维护类似类结构的模式(包括注释,希望你可以遵循):
(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 ));
现在,您可以:
$('div').myClass();
这将为找到的每个div添加一个新实例,并将其保存在$ .data中。 现在,要检索某个实例的一个apply方法,你可以这样做:
$('div').eq(1).data('myclass').increaseId(3).applyIdAsClass();
这是我多次使用的模式,非常适合我的需求。
您还可以通过添加window.MyClass = MyClass
来公开该类,以便在没有jQuery原型的情况下使用它。 这允许以下语法:
var instance = new MyClass( document.getElementById('element'), { id: 5 }); instance.increaseId(5); alert( instance.options.id ); // yields 10
这是一个想法……
(function($){ var _private = { init: function(element, args){ if(!element.isInitialized) { ... initialization code ... element.isInitialized = true; } } } $.fn.myPlugin(args){ _private.init(this, args); } })(jQuery);
…然后你可以添加更多私人方法。 如果要“保存”更多数据,可以使用传递给init函数的元素并将对象保存到dom元素……如果您使用的是HTML5,则可以使用元素上的数据属性。
编辑
另一件事情浮现在脑海中。 您可以使用jQuery.UI小部件。
我认为你需要解决的问题基本上是一个很好的OO结构来保存你的设置和GoogleMap。
如果你不依赖于jQuery并且非常了解OOP,我会使用YUI3 Widget 。
浏览Sample Widget Template可以让您了解框架提供对OOP结构的访问,例如:
- 它提供了命名空间支持。
- 它支持类和对象的概念
- 它整齐地支持类扩展
- 它提供了构造函数和析构函数
- 它支持实例变量的概念
- 它提供渲染和事件绑定
在你的情况下:
- 您可以创建具有自己的实例变量的GoogleHelper类以及我认为您想要的Google Map对象。
- 然后,您将开始使用自己的设置创建此类的实例。
- 对于每个新实例,您只需要使用您稍后可以引用的ID映射它。 通过将ID引用到同时具有设置和GoogleMap的GoogleHelper实例,您不必保留两个地图(一个用于保存设置,另一个用于GoogleMap),我恰好认同它不是理想的情况。
这基本上可以追溯到基本的OO编程,正确的JS框架可以帮助您实现这一目标。 虽然也可以使用其他OO JS框架,但我发现YUI3为大型Javascript项目提供了比其他框架更好的结构。
我将提供一个最近的博客文章的链接,我做了类似的事情。 http://aknosis.com/2011/05/11/jquery-pluginifier-jquery-plugin-instantiator-boilerplate/
基本上这个包装器(我称之为pluginifier)将允许您创建一个单独的JavaScript对象,它将容纳所有内容(公共/私有方法/选项对象等),但允许使用常见的$(’#myThing’快速检索和创建).myPlugin();
源代码也可以在github上找到: https : //github.com/aknosis/jquery-pluginifier
这是你要放置代码的片段:
//This should be available somewhere, doesn't have to be here explicitly var namespace = { //This will hold all of the plugins plugins : {} }; //Wrap in a closure to secure $ for jQuery (function( $ ){ //Constructor - This is what is called when we create call new namspace.plugins.pluginNameHere( this , options ); namespace.plugins.pluginNameHere = function( ele , options ){ this.$this = $( ele ); this.options = $.extend( {} , this.defaults , options ); }; //These prototype items get assigned to every instance of namespace.plugins.pluginNameHere namespace.plugins.pluginNameHere.prototype = { //This is the default option all instances get, can be overridden by incoming options argument defaults : { opt: "tion" }, //private init method - This is called immediately after the constructor _init : function(){ //useful code here return this; //This is very important if you want to call into your plugin after the initial setup }, //private method - We filter out method names that start with an underscore this won't work outside _aPrivateMethod : function(){ //Something useful here that is not needed externally }, //public method - This method is available via $("#element").pluginNameHere("aPublicMethod","aParameter"); aPublicMethod : function(){ //Something useful here that anyone can call anytime } }; //Here we register the plugin - $("#ele").pluginNameHere(); now works as expected $.pluginifier( "pluginNameHere" ); })( jQuery );
$ .pluginifier代码位于单独的文件中,但也可以包含在与插件代码相同的文件中。
您的许多要求都是不必要的。 无论如何这里是我自己采用的设计模式的大致轮廓 – 这基本上直接来自jQuery创作文档。 如果您有任何疑问,请给我留言。
描述的模式允许以下用途:
var $myElements = $('#myID').myMapPlugin({ center:{ lat:174.0, lng:-36.0 } }); $myElements.myMapPlugin('refresh'); $myElements.myMapPlugin('addMarker', { lat:174.1, lng:-36.1 }); $myElements.myMapPlugin('update', { center:{ lat:175.0, lng:-33.0 } }); $myElements.myMapPlugin('destroy');
这是一般模式 – 只实现了一些方法。
;(function($) { var privateFunction = function () { //do something } var methods = { init : function( options ) { var defaults = { center: { lat: -36.8442, lng: 174.7676 } }; var t = $.extend(true, defaults, options); return this.each(function () { var $this = $(this), data = $this.data('myMapPlugin'); if ( !data ) { var map = new google.maps.Map(this, { zoom: 8, center: new google.maps.LatLng(t['center'][lat], t['center']['lng']), mapTypeId: google.maps.MapTypeId.ROADMAP, mapTypeControlOptions:{ mapTypeIds: [google.maps.MapTypeId.ROADMAP] } }); var geocoder = new google.maps.Geocoder(); var $form = $('form', $this.parent()); var form = $form.get(0); var $search = $('input[data-type=search]', $form); $form.submit(function () { $this.myMapPlugin('search', $search.val()); return false; }); google.maps.event.addListener(map, 'idle', function () { // do something }); $this.data('myMapPlugin', { 'target': $this, 'map': map, 'form':form, 'geocoder':geocoder }); } }); }, resize : function ( ) { return this.each(function(){ var $this = $(this), data = $this.data('myMapPlugin'); google.maps.event.trigger(data.map, 'resize'); }); }, search : function ( searchString ) { return this.each(function () { // do something with geocoder }); }, update : function ( content ) { // ToDo }, destroy : function ( ) { return this.each(function(){ var $this = $(this), data = $this.data('myMapPlugin'); $(window).unbind('.locationmap'); data.locationmap.remove(); $this.removeData('locationmap'); }); } }; $.fn.myMapPlugin = function (method) { if ( methods[method] ) { return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 )); } else if ( typeof method === 'object' || ! method ) { return methods.init.apply( this, arguments ); } else { $.error( 'Method ' + method + ' does not exist on jQuery.myMapPlugin' ); } }; })(jQuery);
请注意,代码未经测试。
快乐编码:)
这可能超出了你的问题的范围,但我真的认为你应该重构你如何处理PHP – > JS转换(特别是你的整个最后一个PHP代码块)。
我认为在PHP中生成大量JS是一种反模式,然后在客户端上运行。 相反,您应该将JSON数据返回给您的客户端,该客户端根据该数据调用所需的任何内容。
这个例子不完整,但我认为它给你一个想法。 你的所有JS实际上都应该在JS中,而且来回发送的唯一东西应该是JSON。 生成动态JS并不是一个理智的做法IMO。
array( 'options' => array(), 'venues' => array(/* 0..N venues here */), ) ); echo json_encode($data); ?>
我在jQuery插件模板中解决了这些问题- 最佳实践,约定,性能和内存影响
我在jsfiddle.net发布的部分内容:
;(function($, window, document, undefined){ var myPluginFactory = function(elem, options){ ........ var modelState = { options: null //collects data from user + default }; ........ function modeler(elem){ modelState.options.a = new $$.A(elem.href); modelState.options.b = $$.B.getInstance(); }; ........ return { pluginName: 'myPlugin', init: function(elem, options) { init(elem, options); }, get_a: function(){return modelState.options.a.href;}, get_b: function(){return modelState.options.b.toString();} }; }; //extend jquery $.fn.myPlugin = function(options) { return this.each(function() { var plugin = myPluginFactory(this, options); $(this).data(plugin.pluginName, plugin); }); }; }(jQuery, window, document));
我的项目: https : //github.com/centurianii/jsplugin
见: http : //jsfiddle.net/centurianii/s4J2H/1/