The release of version 2.4 of Williamsburger Maps includes a few functional enhancements, as well as the usage of a significant number of new JavaScript patterns. These patterns have unfortunately brought a memory leak / performance problem with them which I hope to address in the very near future.
Functional Changes
- Add the ability to preference certain restaurants/businesses over others. Using sorting and filtering in the JavaScript tier, the maps are able to display businesses based on their rating, and the businesses list in the side panel is sorted based on various criteria (presence of a review, rating, presence of an online menu, etc.) All buttons, including the new Ratings section, are still being generated in PHP.
- Add review icons to the side panel description.
- For space conservation, pre-collapse the Types button panel.
- Add an admin interface page for managing the ratings scheme.
- I would love feedback about these changes, especially suggestions as to better colors to use for the great/good/bad markers. The current scheme of red/purple/blue doesn’t really sit right with me, although maybe it’s just the inconsistency of using a purple star to denote a Williamsburger review, but a purple marker to denote the mid-range of recommendation.
JavaScript Patterns
- Move more logic into AJAX patterns, moving logic involving sorting and filtering out of the PHP and into the JavaScript. The side panel descriptions are now being generated entirely via asynchronous JavaScript creation of html elements based on the XML returned from the wbmaps-locations.php feed. My suspicion is, however, that this piece of code is responsible for much of the memory leak problem introduced in this release (see Bugs, later).
- Use Prototype.js patterns for dealing with objects. The Rico libraries already use these patterns, so I had a good example to learn from. The primary object I dealt with was the Location object, to hold all data about a given business.
I’m a fervent proponent of the use of getters and setters for all instance variable access, both internal and external, so being able to add get/set methods for all Location properties was very attractive to me. As an aside, the #1 Google hit for ‘java getter setter’ is the well-intentioned but insufficiently clear article ‘Why getter and setter methods are evil’.
You shouldn’t use accessor methods (getters and setters) unless absolutely necessary because these methods expose information about how a class is implemented and as a consequence make your code harder to maintain.
This single article has caused me no end of grief in professional work. I do agree with the statement when applied to exposed methods; public getters (and especially setters) do work against the grain of object-oriented design. In its defense, the article does only address public methods, but its omission of any mention of private ones has inspired a great deal of confusion. Within an object itself, I firmly believe that — with very few exceptions for issues like synchronization — all access to instance variables should be done through protected/private getters and setters (or public ones if the design requires them). In the case of getters, strictly following this pattern allows for vastly increased flexibility in how the instance value is managed (hardcoded vs pulled from a DB or a config file vs cached), which in turn leads to decreased maintenance costs. With setters, validation can be done on the input before overwriting the previous value, and all setting logic can be kept consistent. At any rate, in this JavaScript I followed the convention of prepending all variables and methods which should not be accessed externally with a single ‘_’ to mark them as private (or at least, protected; maybe in the future, I’ll adopt ‘__’ to mean truly private). There’s no compile time or runtime enforcement of this, obviously, but with the visual cues, I can code as if there were. For now, all the getters and setters are marked as public, although there’s no real reason for external setters to exist, and this is something which I could certainly clean up. A nice perk of commenting in JavaScript and the way the Prototype code works is that I can javadoc many of my methods as well./** * Williamsburger Maps Location object. */ WbMaps.Location = Class.create( ); WbMaps.Location.prototype = { initialize: function( ) { }, /** * Get the location's ID. * * @return the location's ID */ getId: function( ) { return this._id; }, /** * Set the location's ID. This must either be an int or be able to * be parsed into an int. * * @param the location's ID */ setId: function( id ) { this._id = parseInt( id ); }, getTitle: function( ) { return this._title; }, setTitle: function( title ) { this._title = title; }, ... /** * Get the appropriate GIcon for map display of this image. The * image in the icon is based on the rating of the location. * * @return a GIcon for this location */ getGIcon: function( ) { if ( WbMaps.Location._baseIcon == null ) { WbMaps.Location._baseIcon = WbMaps.Location._createBaseIcon( ); } var icon = new GIcon( WbMaps.Location._baseIcon ); icon.image = this.getGIconImageName( ); return icon; }, /** * Get the appropriate image name for the GIcon used for map display * of this image. The image in the icon is based on the rating of * the location. * * @return an image name URL */ getGIconImageName: function( ) { var baseUrl = '/wb/wp-content/themes/williamsburger/wbmaps-' + WbMaps.Version + '/images/'; if ( this.getRating( ) > 0 ) { return baseUrl + 'mm_20_red.png'; } else if ( this.getRating( ) == 0 ) { return baseUrl + 'mm_20_purple.png'; } else { return baseUrl + 'mm_20_blue.png'; } }, ... };The latter section of this is both an example of object-oriented programming in JavaScript as well as a pattern pulled from the Google Maps API page to use GIcon’s copy constructor to construct many instances of GIcon with different marker image, but with the same shadow image and dimensions. The static (global, really) variable WbMaps.Location._baseIcon is declared outside of Location’s method declarations, as is the static method WbMaps.Location._createBaseIcon. Here’s their content:/** * Private static variable to store the base GIcon preferences (size, * shadow image, etc). */ WbMaps.Location._baseIcon = null; /** * Method to create the singleton instance of a base GIcon to be * reused in creating other GIcons. */ WbMaps.Location._createBaseIcon = function( ) { var icon = new GIcon( ); icon.shadow = '/wb/wbmaps-' + WbMaps.Version + '/images/mm_20_shadow.png'; icon.iconSize = new GSize( 12, 20 ); icon.shadowSize = new GSize( 22, 20 ); icon.iconAnchor = new GPoint( 6, 20 ); icon.infoWindowAnchor = new GPoint( 5, 1 ); return icon; }; - Created a few common methods for dealing with XML, as well as factory methods to create Location objects from XML.
function getElementValue( xml, tagName ) { var elements = xml.getElementsByTagName( tagName ); if ( elements.length == 1 ) { return elements[ 0 ].firstChild.nodeValue; } else { return null; } } function getAttributeValue( xml, attributeName ) { return xml.getAttribute( attributeName ); } /** * Factory method to create a Location from an XML representation of a * location. * * @param xml a valid XML document representing a location * @return a WbMaps.Location object */ WbMaps.Location.createLocationFromXml = function( xml ) { var location = new WbMaps.Location( ); location.setId( getAttributeValue( xml, 'id' ) ); location.setTitle( getElementValue( xml, 'title' ) ); location.setLat( getAttributeValue( xml, 'lat' ) ); location.setLng( getAttributeValue( xml, 'lng' ) ); location.setAddress1( getAttributeValue( xml, 'address1' ) ); location.setAddress2( getAttributeValue( xml, 'address2' ) ); location.setCity( getAttributeValue( xml, 'city' ) ); location.setState( getAttributeValue( xml, 'state' ) ); location.setZip( getAttributeValue( xml, 'zip' ) ); location.setPhone( getAttributeValue( xml, 'phone' ) ); location.setImageUrl( getElementValue( xml, 'imageurl' ) ); location.setReviewUrl( getElementValue( xml, 'reviewurl' ) ); location.setReviewClass( getAttributeValue( xml, 'reviewclass' ) ); location.setMenuUrl( getElementValue( xml, 'menuurl' ) ); location.setMenuClass( getAttributeValue( xml, 'menuclass' ) ); location.setDohUrl( getElementValue( xml, 'dohurl' ) ); location.setRating( getAttributeValue( xml, 'rating' ) ); return location; };
Bugs
- Memory leaks abound in this new version. In Firefox, a single page reload can increase the memory by up to 4MB, and this memory doesn’t appear to be garbage collected after the page is unloaded. A major goal of the next version will need to be cleaning up the memory problems. My main suspect is the loading of the locations via one large XML list, so I’ll need to think about how to deal with that more cleanly in a future release.
- One thing I’ve noticed while trying to debug the "map doesn’t drag" bug is that none of the mouseover texts in the links within the info window popups work anymore either. Maybe there’s some kind of focus which the map requires which is being stolen by some other div in the page. None of that would be intentional, but maybe there’s something in the Rico or Prototype code which is interacting poorly with this code here.
- The marker PNG images have a transparent background in Firefox, and an inexplicable grey background in IE (but only when used outside of the Google Map div). I expect I’ll need to figure out how to do gif transparencies. I haven’t done this for years and years, so any pointers would be very welcome.


Leave a Reply