Note that there are some explanatory texts on larger screens.

plurals
  1. POWhich jQuery plugin design pattern should I use?
    primarykey
    data
    text
    <p>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:</p> <pre><code>$('#element-id').myPlugin(options); </code></pre> <ul> <li>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(). </li> <li>And I want to have some code that should be executed only the first time the plugin is initialized for a given ID (construct).</li> <li>The <code>options</code> 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()</li> <li>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). </li> <li>The above syntax is just for example - I'm open for any suggestions on how to achieve that pattern</li> </ul> <p>I have quite some OOP experience with other language, but limited knowledge of javascript and I'm really confused on how do it right.</p> <p><strong>EDIT</strong></p> <p>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):</p> <pre><code>;(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); </code></pre> <p>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):</p> <pre><code>$js = "$('#$id').csGoogleMapsHelper($jsOptions);\n"; if ($this-&gt;venues !== null) { foreach ($this-&gt;venues as $row) { $data = GoogleMapsHelper::getVenueMarkerOptionsJs($row); $js .= "$.fn.csGoogleMapsHelper.createMarker('$id', $data, true);\n"; } } $js .= "$.fn.csGoogleMapsHelper.finalize('$id');\n"; echo $js; </code></pre> <p>The problems of the above implementation are that I don't like to keep a hash-map for "settings" and "maps" </p> <p>The <code>$id</code> 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 <code>$jsOptions</code> and <code>$data</code> from the PHP code are JSON objects.</p> <p>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.</p> <p><strong>I do not insist that this is implemented as a jQuery plugin!</strong> 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.</p> <p>I will add a bounty for this.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload