問題描述
我需要構建一個 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').myPlugin() 時我都想獲得相同的實例.
- 我想要一些代碼,只在第一次為給定 ID(構造)初始化插件時執行.
options
參數應該第一次提供,對于構造,之后我不希望構造被執行,這樣我就可以像 $('#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);
";
if ($this->venues !== null) {
foreach ($this->venues as $row) {
$data = GoogleMapsHelper::getVenueMarkerOptionsJs($row);
$js .= "$.fn.csGoogleMapsHelper.createMarker('$id', $data, true);
";
}
}
$js .= "$.fn.csGoogleMapsHelper.finalize('$id');
";
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 具有地圖,其中我保存頁面上每個已初始化的此類 GoogleMaps 的設置和 GoogleMaps MapObject 實例.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 實例來保存它自己的設置和 GoogleMaps 地圖對象,以便在我對某個元素(通過它的 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.
我會為此添加賞金.
推薦答案
當你說通過 $('#element').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
對象中,因此您只需初始化插件一次,然后您可以隨時使用 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.
您還可以通過添加 window.MyClass = MyClass
公開該類,以便在沒有 jQuery 原型的情況下使用它.這允許使用以下語法:
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 插件設計模式?的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網!