我需要构建一个jQuery插件,它会为每个选择器id返回一个实例。该插件应该并且只会用于具有id的元素(不能使用与许多元素匹配的选择器),所以它应该像这样使用:
$('#element-id').myPlugin(options);
options
参数,对于构造,之后我不想执行构造,这样我就可以像$('#element-id'一样访问插件).myPlugin()我对其他语言有很多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插件实现的!我坚持认为它是灵活且可扩展的,因为我将在一个大型项目中使用它,目前有十几个不同的屏幕,将在几个月后使用,改变它的使用界面将是整个项目重构的噩梦。
我会为此添加一笔赏金。
答案 0 :(得分:19)
当您通过$('#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
答案 1 :(得分:4)
这是一个想法......
(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小部件。
答案 2 :(得分:4)
我认为解决问题所需的基本上是一个很好的OO结构来保存你的设置和GoogleMap。
如果你不依赖于jQuery并且非常了解OOP,我会使用YUI3 Widget。
只需看一眼Sample Widget Template就可以了解框架提供对OOP结构的访问,例如:
在你的情况下:
这基本上可以追溯到基本的OO编程,正确的JS框架可以帮助您实现这一目标。虽然也可以使用其他OO JS框架,但我发现YUI3为大型Javascript项目提供了比其他框架更好的结构。
答案 3 :(得分:3)
我将提供我最近发表的关于类似内容的博客文章的链接。 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代码位于单独的文件中,但也可以包含在与插件代码相同的文件中。
答案 4 :(得分:2)
您的许多要求都是不必要的。无论如何,这里是我自己采用的设计模式的大致轮廓 - 这基本上直接来自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);
请注意,代码未经测试。
快乐编码:)
答案 5 :(得分:1)
这可能超出了你的问题的范围,但我真的认为你应该重构你如何处理PHP - > JS转换(具体来说,就是你最后的整个PHP代码块)。
我认为在PHP中生成大量JS是一种反模式,然后在客户端上运行。相反,您应该将JSON数据返回给您的客户端,该客户端根据该数据调用所需的任何内容。
这个例子不完整,但我认为它会给你一个想法。你的所有JS实际上都应该在JS中,并且唯一被发送回来的东西应该是JSON。生成动态JS并不是理所当然的IMO。
<?php
// static example; in real use, this would be built dynamically
$data = array(
$id => array(
'options' => array(),
'venues' => array(/* 0..N venues here */),
)
);
echo json_encode($data);
?>
<script>
xhr.success = function (data) {
for (var id in data)
{
$('#' + id).csGoogleMapsHelper(data[id].options);
for (var i = 0, len = data[id].venues.length; i < len; i++)
{
$.fn.csGoogleMapsHelper.createMarker(id, data[id].venues[i], true);
}
$.fn.csGoogleMapsHelper.finalize(id);
}
}
</script>
答案 6 :(得分:0)
我在jQuery plugin template - best practice, convention, performance and memory impact
处理了这些问题我在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));