diff --git a/jquery.jmapping.js b/jquery.jmapping.js index 13420c3..212fec4 100644 --- a/jquery.jmapping.js +++ b/jquery.jmapping.js @@ -114,16 +114,52 @@ link.attr('href', ("#" + location_data.id)); }; + var chooseIconOptions = function(category){ + if (settings.category_icon_options){ + if ($.isFunction(settings.category_icon_options)){ + return settings.category_icon_options(category); + } else { + return settings.category_icon_options[category] || settings.category_icon_options['default']; + } + } else { + return {}; + } + }; + var createMarker = function(place_elm){ var $place_elm = $(place_elm), place_data, point, marker, $info_window_elm, info_window; place_data = $place_elm.metadata(settings.metadata_options); point = $.jMapping.makeGLatLng(place_data.point); - marker = new google.maps.Marker({ - position: point, - map: map - }); + + if (settings.category_icon_options){ + icon_options = chooseIconOptions(place_data.category); + if (typeof icon_options === "string"){ + marker = new google.maps.Marker({ + icon: icon_options, + position: point, + map: map + }); + } else if (icon_options instanceof google.maps.MarkerImage){ + marker = new google.maps.Marker({ + icon: icon_options, + position: point, + map: map + }); + } else { + marker = new StyledMarker({ + styleIcon: new StyledIcon(StyledIconTypes.MARKER, icon_options), + position: point, + map: map + }); + } + } else { + marker = new google.maps.Marker({ + position: point, + map: map + }); + } $info_window_elm = $place_elm.find(settings.info_window_selector); if ($info_window_elm.length > 0){ diff --git a/spec/jmapping_spec.js b/spec/jmapping_spec.js index d9ec755..927cbdb 100644 --- a/spec/jmapping_spec.js +++ b/spec/jmapping_spec.js @@ -355,7 +355,7 @@ Screw.Unit(function(){ }); }); - describe("jMapping with no cateogies and 'category_icon_options'", function(){ + describe("jMapping with no categories and 'category_icon_options'", function(){ before(function(){ // mock out Marker google.maps.InfoWindow = Smoke.MockFunction(function(options){}, 'InfoWindow'); @@ -366,7 +366,7 @@ Screw.Unit(function(){ }); }); - mockGMaps(); + mockGMaps(true); mockMarkerManager(); }); after(function(){ @@ -379,7 +379,7 @@ Screw.Unit(function(){ $('#map').jMapping({ side_bar_selector: 'ul#map-list', location_selector: 'li.location', - category_icon_options: {'fun': {primaryColor: '#33FFFF'}, 'default': {primaryColor: '#CC0000'}}, + category_icon_options: {'fun': {color: '#33FFFF'}, 'default': {color: '#CC0000'}}, metadata_options: {type: 'html5'} }); expect($('#map').data('jMapping')).to(be_true); @@ -398,16 +398,16 @@ Screw.Unit(function(){ }); }); - mockGMaps(); + mockGMaps(true); mockMarkerManager(); category_function = mock_function(function(category){ if (category.charAt(0).match(/[a-m]/i)){ - return {primaryColor: '#CC0000'}; + return {color: '#CC0000'}; } else if (category.charAt(0).match(/[n-z]/i)){ - return {primaryColor: '#33FFFF'}; + return {color: '#33FFFF'}; } else { - return {primaryColor: '#00FFCC'}; + return {color: '#00FFCC'}; } }, 'category_icon_options_function'); category_function.should_be_invoked().exactly('twice'); @@ -459,7 +459,7 @@ Screw.Unit(function(){ }); }); - describe("setting a function to category_icon_options that returns a GIcon", function(){ + describe("setting a function to category_icon_options that returns a MarkerImage", function(){ var category_function; before(function(){ google.maps.InfoWindow = Smoke.MockFunction(function(options){}, 'InfoWindow'); @@ -474,7 +474,7 @@ Screw.Unit(function(){ mockMarkerManager(); category_function = mock_function(function(category){ - return 'GIcon'; + return new google.maps.MarkerImage('/images/map_icons/icon1.jpg'); }, 'category_icon_options_function'); category_function.should_be_invoked().exactly('twice'); }); diff --git a/spec/spec_helper.js b/spec/spec_helper.js index 7165a4a..f3203b5 100644 --- a/spec/spec_helper.js +++ b/spec/spec_helper.js @@ -11,11 +11,17 @@ var google = { LatLngBounds: function(){}, LatLng: function(){}, Marker: function(){}, - InfoWindow: function(){} + InfoWindow: function(){}, + MarkerImage: function(){} } -}; +}, +StyledIconTypes = { + MARKER: 'MARKER' +}, +StyledIcon = function(){}, +StyledMarker = function(){}; -Screw.Specifications.mockGMaps = function(gmap_mock_setup){ +Screw.Specifications.mockGMaps = function(custom_markers){ // mock out GLatLng google.maps.LatLng = Smoke.MockFunction(function(lat, lng){}, 'LatLng'); google.maps.LatLng.should_be_invoked().at_least('once'); @@ -23,9 +29,6 @@ Screw.Specifications.mockGMaps = function(gmap_mock_setup){ // mock out GMap2 var gmap_mock = Smoke.Mock(); gmap_mock.should_receive('fitBounds').exactly('once'); - if ($.isFunction(gmap_mock_setup)){ - gmap_mock_setup(gmap_mock); - } $.extend(google.maps.Map.prototype, gmap_mock); // mock out Bounds @@ -36,8 +39,15 @@ Screw.Specifications.mockGMaps = function(gmap_mock_setup){ $.extend(google.maps.LatLngBounds.prototype, bounds_mock); // mock out Marker - google.maps.Marker = Smoke.MockFunction(function(point, options){}, 'Marker'); - google.maps.Marker.should_be_invoked().exactly('twice'); + if (custom_markers){ + StyledIcon = Smoke.MockFunction(function(options){}, 'StyledIcon'); + StyledIcon.should_be_invoked().exactly('twice'); + StyledMarker = Smoke.MockFunction(function(options){}, 'StyledMarker'); + StyledMarker.should_be_invoked().exactly('twice'); + } else { + google.maps.Marker = Smoke.MockFunction(function(options){}, 'Marker'); + google.maps.Marker.should_be_invoked().exactly('twice'); + } google.maps.event = Smoke.Mock(); google.maps.event.should_receive('addListener').at_least('once'); @@ -60,7 +70,7 @@ Screw.Specifications.mockGMapsUpdate = function(){ $.extend(google.maps.LatLngBounds.prototype, bounds_mock); // mock out Marker - google.maps.Marker = Smoke.MockFunction(function(point, options){}, 'Marker'); + google.maps.Marker = Smoke.MockFunction(function(options){}, 'Marker'); google.maps.Marker.should_be_invoked().exactly(4, 'times'); google.maps.event = Smoke.Mock(); diff --git a/vendor/StyledMarker.js b/vendor/StyledMarker.js new file mode 100644 index 0000000..b6ba9a1 --- /dev/null +++ b/vendor/StyledMarker.js @@ -0,0 +1,264 @@ +/** + * @name StyledMarkerMaker + * @version 0.5 + * @author Gabriel Schneider + * @copyright (c) 2010 Gabriel Schneider + * @fileoverview This gives you static functions for creating dynamically + * styled markers using Charts API outputs as well as an ability to + * extend with custom types. + */ + +/** + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var StyledIconTypes = {}; +var StyledMarker, StyledIcon; + +(function() { + var bu_ = 'http://chart.apis.google.com/chart?chst='; + var gm_ = google.maps; + var gp_ = gm_.Point; + var ge_ = gm_.event; + var gmi_ = gm_.MarkerImage; + + + /** + * This class is an extended version of google.maps.Marker. It allows + * styles to be applied that change it's appearance. + * @extends google.maps.Marker + * @param {StyledMarkerOptions} StyledMarkerOptions The options for the Marker + */ + StyledMarker = function(styledMarkerOptions) { + var me=this; + var ci = me.styleIcon = styledMarkerOptions.styleIcon; + me.bindTo('icon',ci); + me.bindTo('shadow',ci); + me.bindTo('shape',ci); + me.setOptions(styledMarkerOptions); + }; + StyledMarker.prototype = new gm_.Marker(); + + /** + * This class stores style information that can be applied to StyledMarkers. + * @extends google.maps.MVCObject + * @param {StyledIconType} styledIconType The type of style this icon is. + * @param {StyledIconOptions} styledIconOptions The options for this StyledIcon. + * @param {StyledIcon} styleClass A class to apply extended style information. + */ + StyledIcon = function(styledIconType,styledIconOptions,styleClass) { + var k; + var me=this; + var i_ = 'icon'; + var sw_ = 'shadow'; + var s_ = 'shape'; + var a_ = []; + + function gs_() { + var image_ = document.createElement('img'); + var simage_ = document.createElement('img'); + ge_.addDomListenerOnce(simage_, 'load', function() { + var w = simage_.width, h = simage_.height; + me.set(sw_,new gmi_(styledIconType.getShadowURL(me),null,null,styledIconType.getShadowAnchor(me,w,h))); + simage = null; + }); + ge_.addDomListenerOnce(image_, 'load', function() { + var w = image_.width, h = image_.height; + me.set(i_,new gmi_(styledIconType.getURL(me),null,null,styledIconType.getAnchor(me,w,h))); + me.set(s_,styledIconType.getShape(me,w,h)); + image_ = null; + }); + image_.src = styledIconType.getURL(me); + simage_.src = styledIconType.getShadowURL(me); + } + + /** + * set: + * This function sets a given style property to the given value. + * @param {String} name The name of the property to set. + * @param {Object} value The value to set the property to. + * get: + * This function gets a given style property. + * @param {String} name The name of the property to get. + * @return {Object} + */ + me.as_ = function(v) { + a_.push(v); + for(k in styledIconOptions) { + v.set(k, styledIconOptions[k]); + } + } + + if (styledIconType !== StyledIconTypes.CLASS) { + for (k in styledIconType.defaults) { + me.set(k, styledIconType.defaults[k]); + } + me.setValues(styledIconOptions); + me.set(i_,styledIconType.getURL(me)); + me.set(sw_,styledIconType.getShadowURL(me)); + if (styleClass) styleClass.as_(me); + gs_(); + me.changed = function(k) { + if (k!==i_&&k!==s_&&k!==sw_) { + gs_(); + } + }; + } else { + me.setValues(styledIconOptions); + me.changed = function(v) { + styledIconOptions[v] = me.get(v); + for (k = 0; k < a_.length; k++) { + a_[k].set(v,me.get(v)); + } + }; + if (styleClass) styleClass.as_(me); + } + }; + StyledIcon.prototype = new gm_.MVCObject(); + + /** + * StyledIconType + * This class holds functions for building the information needed to style markers. + * getURL: + * This function builds and returns a URL to use for the Marker icon property. + * @param {StyledIcon} icon The StyledIcon that holds style information + * @return {String} + * getShadowURL: + * This function builds and returns a URL to use for the Marker shadow property. + * @param {StyledIcon} icon The StyledIcon that holds style information + * @return {String{ + * getAnchor: + * This function builds and returns a Point to indicate where the marker is placed. + * @param {StyledIcon} icon The StyledIcon that holds style information + * @param {Number} width The width of the icon image. + * @param {Number} height The height of the icon image. + * @return {google.maps.Point} + * getShadowAnchor: + * This function builds and returns a Point to indicate where the shadow is placed. + * @param {StyledIcon} icon The StyledIcon that holds style information + * @param {Number} width The width of the shadow image. + * @param {Number} height The height of the shadow image. + * @return {google.maps.Point} + * getShape: + * This function builds and returns a MarkerShape to indicate where the Marker is clickable. + * @param {StyledIcon} icon The StyledIcon that holds style information + * @param {Number} width The width of the icon image. + * @param {Number} height The height of the icon image. + * @return {google.maps.MarkerShape} + */ + + StyledIconTypes.CLASS = {}; + + StyledIconTypes.MARKER = { + defaults: { + text:'', + color:'00ff00', + fore:'000000', + starcolor:null + }, + getURL: function(props){ + var _url; + var starcolor_=props.get('starcolor'); + var text_=props.get('text'); + var color_=props.get('color').replace(/#/,''); + var fore_=props.get('fore').replace(/#/,''); + if (starcolor_) { + _url = bu_ + 'd_map_xpin_letter&chld=pin_star|'; + } else { + _url = bu_ + 'd_map_pin_letter&chld='; + } + if (text_) { + text_ = text_.substr(0,2); + } + _url+=text_+'|'; + _url+=color_+'|'; + _url+=fore_; + if (starcolor_) { + _url+='|'+starcolor_.replace(/#/,''); + } + return _url; + }, + getShadowURL: function(props){ + if (props.get('starcolor')) { + return bu_ + 'd_map_xpin_shadow&chld=pin_star'; + } else { + return bu_ + 'd_map_pin_shadow'; + } + }, + getAnchor: function(props,width,height){ + return new gp_(width / 2,height); + }, + getShadowAnchor: function(props,width,height){ + return new gp_(width / 4,height); + }, + getShape: function(props,width,height){ + var _iconmap = {}; + _iconmap.coord = [ + width / 2, height, + (7 / 16) * width, (5 / 8) * height, + (5 / 16) * width, (7 / 16) * height, + (7 / 32) * width, (5 / 16) * height, + (5 / 16) * width, (1 / 8) * height, + (1 / 2) * width, 0, + (11 / 16) * width, (1 / 8) * height, + (25 / 32) * width, (5 / 16) * height, + (11 / 16) * width, (7 / 16) * height, + (9 / 16) * width, (5 / 8) * height + ]; + for (var i = 0; i < _iconmap.coord.length; i++) { + _iconmap.coord[i] = Math.round(_iconmap.coord[i]); + } + _iconmap.type = 'poly'; + return _iconmap; + } + }; + StyledIconTypes.BUBBLE = { + defaults: { + text:'', + color:'00ff00', + fore:'000000' + }, + getURL: function(props){ + var _url = bu_ + 'd_bubble_text_small&chld=bb|'; + _url+=props.get('text')+'|'; + _url+=props.get('color').replace(/#/,'')+'|'; + _url+=props.get('fore').replace(/#/,''); + return _url; + }, + getShadowURL: function(props){ + return bu_ + 'd_bubble_text_small_shadow&chld=bb|' + props.get('text'); + }, + getAnchor: function(props,width,height){ + return new google.maps.Point(0,42); + }, + getShadowAnchor: function(props,width,height){ + return new google.maps.Point(0,44); + }, + getShape: function(props,width,height){ + var _iconmap = {}; + _iconmap.coord = [ + 0,44, + 13,26, + 13,6, + 17,1, + width - 4,1, + width,6, + width,21, + width - 4,26, + 21,26 + ]; + _iconmap.type = 'poly'; + return _iconmap; + } + }; +})(); \ No newline at end of file