我应该使用哪种jQuery插件设计模式?

ddi*_*hev 22 javascript jquery design-patterns jquery-plugins

我需要构建一个jQuery插件,它将返回每个选择器id的单个实例.该插件应该并且将仅用于具有id的元素(不可能使用与许多元素匹配的选择器),因此它应该像这样使用:

$('#element-id').myPlugin(options);
Run Code Online (Sandbox Code Playgroud)
  • 我需要能够为插件提供一些私有方法以及一些公共方法.我可以实现这一点,但我的主要问题是每次调用$('#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);
Run Code Online (Sandbox Code Playgroud)

它的用法看起来像这样(实际上并不完全像这样,因为有一个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;
Run Code Online (Sandbox Code Playgroud)

以上实现的问题是我不喜欢为"设置"和"地图"保留哈希映射

$id是其中地图初始化DIV元素ID.它用作.map中的一个键,而.settings有一些映射,我在页面上为每个初始化的这样的GoogleMaps保存设置和GoogleMaps MapObject实例.在$jsOptions$data从PHP代码是JSON对象.

现在我需要能够创建一个拥有自己的设置和GoogleMaps映射对象的GoogleMapsHelper实例,以便在我对某个元素(通过其ID)初始化之后,我可以重用该实例.但是如果我在页面上的N个元素上初始化它,它们中的每一个都应该有自己的配置,地图对象等.

我不坚持认为这是作为jQuery插件实现的!我坚持认为它具有灵活性和可扩展性,因为我将在一个大型项目中使用它,目前计划使用不同的屏幕将在几个月内使用它,因此更改它的使用界面将是重构整个项目的噩梦.

我会为此添加一笔赏金.

Dav*_*ing 19

当你说"获取"实例时,$('#element').myPlugin()我认为你的意思是:

var instance = $('#element').myPlugin();
instance.myMethod();
Run Code Online (Sandbox Code Playgroud)

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

另一种方便的方法是将实例保存在$.data对象中,因此您只需初始化插件一次,然后您可以随时使用DOM元素作为参考来获取实例,f.ex:

$('#element').myPlugin();
$('#element').data('myplugin').myMethod();
Run Code Online (Sandbox Code Playgroud)

这是我用来在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 ));
Run Code Online (Sandbox Code Playgroud)

现在,您可以:

$('div').myClass();
Run Code Online (Sandbox Code Playgroud)

这将为找到的每个div添加一个新实例,并将其保存在$ .data中.现在,要检索某个实例的一个apply方法,你可以这样做:

$('div').eq(1).data('myclass').increaseId(3).applyIdAsClass();
Run Code Online (Sandbox Code Playgroud)

这是我多次使用的模式,非常适合我的需求.

您还可以通过添加来公开该类,以便在不使用jQuery原型的情况下使用它window.MyClass = MyClass.这允许以下语法:

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