diff --git a/examples/partials/markers.html b/examples/partials/markers.html
index 4c00e61..40a91b6 100644
--- a/examples/partials/markers.html
+++ b/examples/partials/markers.html
@@ -13,11 +13,11 @@
Map With Markers
height: 300px;
}
-/*
+/*
Fixes Bootstrap issues with Google Maps
-see http://stackoverflow.com/a/9170756
+see http://stackoverflow.com/a/9170756
*/
-.map img {
+.map img {
max-width: none;
}
@@ -43,7 +43,7 @@ Map With Markers
icon: 'https://maps.gstatic.com/mapfiles/ms2/micons/yellow-dot.png',
}
};
-
+
$scope.volcanoes = [
{
id: 0,
@@ -51,7 +51,7 @@ Map With Markers
img: 'http://www.thetrackerfoundation.org/Images/MountRainier_SM.jpg',
elevationMeters: 4392,
location: {
- lat: 46.852947,
+ lat: 46.852947,
lng: -121.760424
}
},
@@ -61,7 +61,7 @@ Map With Markers
img: 'http://www.destination360.com/north-america/us/washington/images/s/washington-mt-baker-ski.jpg',
elevationMeters: 3287,
location: {
- lat: 48.776797,
+ lat: 48.776797,
lng: -121.814467
}
},
@@ -71,7 +71,7 @@ Map With Markers
img: 'http://www.rhinoclimbs.com/Images/Glacier.9.jpg',
elevationMeters: 3207,
location: {
- lat: 48.111844,
+ lat: 48.111844,
lng: -121.11412
}
}
@@ -84,7 +84,7 @@ Map With Markers
$scope.options.notselected
);
};
-
+
$scope.selectVolcano = function(volcano) {
if ($scope.volcano) {
$scope.volcano.selected = false;
@@ -99,7 +99,7 @@ Map With Markers
+
diff --git a/src/controllers/angulargmMapController.js b/src/controllers/angulargmMapController.js
index b411a60..88df52a 100644
--- a/src/controllers/angulargmMapController.js
+++ b/src/controllers/angulargmMapController.js
@@ -235,6 +235,22 @@
};
+ /**
+ * Alias for google.maps.event.addDomListener(object, event, handler)
+ */
+ this.addDomListener = function(object, event, handler) {
+ google.maps.event.addDomListener(object, event, handler);
+ };
+
+
+ /**
+ * Alias for google.maps.event.addDomListenerOnce(object, event, handler)
+ */
+ this.addDomListenerOnce = function(object, event, handler) {
+ google.maps.event.addDomListenerOnce(object, event, handler);
+ };
+
+
/**
* Alias for google.maps.event.trigger(map, event)
* @param {string} event an event defined on google.maps.Map
@@ -251,9 +267,9 @@
google.maps.event.trigger(object, event);
};
- this._newElement = function(type, opts) {
+ this._newElement = function(type, opts, CustomMarkerConstructor) {
if (type === 'marker') {
- return new angulargmDefaults.markerConstructor(opts);
+ return angular.isDefined(CustomMarkerConstructor) && angular.isFunction(CustomMarkerConstructor) ? new CustomMarkerConstructor(opts) : new angulargmDefaults.markerConstructor(opts);
} else if (type === 'polyline') {
if (!(opts.path instanceof Array)) {
throw 'polylineOptions did not contain a path';
@@ -295,7 +311,7 @@
* @throw if any arguments are null/undefined or elementOptions does not
* have all the required options (i.e. position)
*/
- this.addElement = function(type, scopeId, id, elementOptions, objectsName) {
+ this.addElement = function(type, scopeId, id, elementOptions, objectsName, customMarkerConstructor) {
assertDefined(type, 'type');
assertDefined(scopeId, 'scopeId');
assertDefined(id, 'id');
@@ -319,7 +335,7 @@
// extend instead of copy to preserve value objects
var opts = {};
angular.extend(opts, elementOptions);
- var element = this._newElement(type, opts);
+ var element = this._newElement(type, opts, customMarkerConstructor);
elements[scopeId][id] = element;
element.setMap(this._map);
diff --git a/src/directives/gmMarkers.js b/src/directives/gmMarkers.js
index 5f4a990..f15ba5c 100644
--- a/src/directives/gmMarkers.js
+++ b/src/directives/gmMarkers.js
@@ -86,6 +86,16 @@
* multiple `gm-on-*event*` handlers, but only one for each type of event.
* For events that have an underscore in their name, such as
* 'position_changed', write it as 'gm-on-position-changed'.
+ *
+* @param {expression} gm-marker-constructor an angular expression which evaluates
+ * to a custom marker constructor function (presumably based on
+ * [google.maps.OverlayView](https://developers.google.com/maps/documentation/javascript/reference#OverlayView).
+ * This constructor will be used for all markers created by this gm-markers
+ * directive. If not specified, the default from angulargmDefaults will be used.
+ * For example:
+ * ```html
+ * gm-marker-constructor="myCustomMarkerConstructor"
+ * ```
*/
/**
@@ -263,6 +273,7 @@
gmId: '&',
gmPosition: '&',
gmMarkerOptions: '&',
+ gmMarkerConstructor: '=',
gmEvents: '&'
},
require: '^gmMap',
diff --git a/src/services/angulargmShape.js b/src/services/angulargmShape.js
index a336b0e..7d8f3c6 100644
--- a/src/services/angulargmShape.js
+++ b/src/services/angulargmShape.js
@@ -51,7 +51,7 @@
* Create new shapes and add them to the map for objects which are not
* currently on the map.
*/
- function _addNewElements(type, scope, controller, handlers, objectCache, optionsFn, objectsName) {
+ function _addNewElements(type, scope, controller, handlers, objectCache, optionsFn, objectsName, attrs) {
var added = [];
angular.forEach(objectCache, function(object, id) {
var element = controller.getElement(type, scope.$id, id);
@@ -64,7 +64,11 @@
if (element) {
controller.updateElement(type, scope.$id, id, options);
} else {
- controller.addElement(type, scope.$id, id, options, objectsName);
+ if (type === 'marker' && attrs.gmMarkerConstructor) {
+ controller.addElement(type, scope.$id, id, options, objectsName, scope.gmMarkerConstructor);
+ } else {
+ controller.addElement(type, scope.$id, id, options, objectsName);
+ }
element = controller.getElement(type, scope.$id, id);
added.push({
id: id,
@@ -73,7 +77,7 @@
// set up element event handlers
angular.forEach(handlers, function(handler, event) {
- controller.addListener(element, event, function() {
+ function listenerCb() {
$timeout(function() {
var context = {object: object};
context[type] = element;
@@ -86,7 +90,19 @@
handler(scope.$parent.$parent.$parent , context);
}
});
- });
+ }
+ if (type === 'marker' && attrs.gmMarkerConstructor) {
+ // nodeType check adapted from Lodash
+ if('getDomElement' in element && element.getDomElement() && element.getDomElement().nodeType === 1) {
+ controller.addDomListener(element.getDomElement(), event, listenerCb);
+ } else {
+ throw 'the constructor provided as gmMarkerConstructor must include a ' +
+ '"getDomElement" method that returns its DOM node, which must exist ' +
+ 'after the constructor is called';
+ }
+ } else {
+ controller.addListener(element, event, listenerCb);
+ }
});
}
});
@@ -208,7 +224,7 @@
_addNewElements(
type, scope, controller, handlers,
- objectCache, elementOptions, objectsName
+ objectCache, elementOptions, objectsName, attrs
);
_removeOrphanedElements(type, scope, controller, objectCache, objectsName);
diff --git a/test/unit/directives/gmMarkersSpec.js b/test/unit/directives/gmMarkersSpec.js
index edab3cb..a9379ca 100644
--- a/test/unit/directives/gmMarkersSpec.js
+++ b/test/unit/directives/gmMarkersSpec.js
@@ -213,6 +213,148 @@ describe('gmMarkers', function() {
});
+ describe('gmMarkerConstructor', function() {
+ var elm, scope, markersScopeId, mapCtrl, objectsName;
+ var latLngToObj;
+ var $timeout;
+
+ var CustomMarker = function(options) {
+ if (options) {
+ if (options.position) {
+ this.latlng = new google.maps.LatLng(options.position.lat, options.position.lng);
+ }
+ this.div = document.createElement('div');
+ if (options.map) {
+ this.setMap(options.map);
+ }
+ }
+ };
+ CustomMarker.prototype = new google.maps.OverlayView();
+ CustomMarker.prototype.setMap = function (map) {
+ this._map = map;
+ };
+ CustomMarker.prototype.getPosition = function () {
+ return this.latlng;
+ };
+
+ beforeEach(inject(function($rootScope, $compile, _$timeout_, angulargmUtils) {
+ // set up scopes
+ scope = $rootScope.$new();
+ scope.people2 = [
+ {name: '0', id: 0, location: {lat: 1, lng: 2}},
+ {name: '3', id: 3, location: {lat: 4, lng: 5}}
+ ];
+ scope.getOpts2 = function(person) {
+ return {
+ key: 'value',
+ title: person.name
+ };
+ };
+ scope.mapId = 'test5';
+
+ $timeout = _$timeout_;
+ latLngToObj = angulargmUtils.latLngToObj;
+
+ scope.myCustomMarker = CustomMarker;
+
+ // compile angulargmMarkers directive
+ elm = angular.element('' +
+ '' +
+ '' +
+ '');
+
+ objectsName = 'people2';
+
+ $compile(elm)(scope);
+
+ mapCtrl = elm.controller('gmMap');
+ spyOn(scope.myCustomMarker.prototype, 'setMap').and.callThrough();
+ spyOn(scope.myCustomMarker.prototype, 'getPosition').and.callThrough();
+ spyOn(mapCtrl, 'addElement').and.callThrough();
+ spyOn(mapCtrl, 'trigger').and.callThrough();
+ spyOn(mapCtrl, 'addDomListener').and.callThrough();
+
+ markersScopeId = elm.find('gm-markers').isolateScope().$id;
+
+ scope.$digest();
+ $timeout.flush();
+ }));
+
+ it('is used when a custom marker constructor is provided', inject(function($compile) {
+ expect(mapCtrl.addElement.calls.count()).toEqual(scope.people2.length);
+ expect(mapCtrl.addElement).toHaveBeenCalledWith('marker', markersScopeId,
+ jasmine.any(String), {key: 'value', title: jasmine.any(String), position: jasmine.any(Object)}, objectsName, scope.myCustomMarker);
+ expect(scope.myCustomMarker.prototype.setMap.calls.count()).toEqual(scope.people2.length);
+ }));
+
+ it('triggers events', function() {
+ var person = scope.people2[0];
+ var position = person.location;
+ var id = person.name;
+ scope.markerEvents = [{
+ event: 'click',
+ ids: [id],
+ }];
+
+ scope.$digest();
+ $timeout.flush();
+ var marker = mapCtrl.trigger.calls.mostRecent()['args'][0];
+ var event = mapCtrl.trigger.calls.mostRecent()['args'][1];
+ expect(latLngToObj(marker.getPosition())).toEqual(position);
+ expect(event).toEqual('click');
+ });
+
+
+ it('triggers events on multiple markers', function() {
+
+ var position0 = scope.people2[0].location;
+ var position1 = scope.people2[1].location;
+ var id0 = scope.people2[0].name;
+ var id1 = scope.people2[1].name;
+ scope.markerEvents = [{
+ event: 'click',
+ ids: [id0, id1]
+ }];
+ scope.$digest();
+ $timeout.flush();
+ var marker0 = mapCtrl.trigger.calls.argsFor(0)[0];
+ var marker1 = mapCtrl.trigger.calls.argsFor(1)[0];
+ expect(latLngToObj(marker0.getPosition())).toEqual(position0);
+ expect(latLngToObj(marker1.getPosition())).toEqual(position1);
+ });
+
+
+ it('triggers multiple events on markers', function() {
+ var position = scope.people2[0].location;
+ var id = scope.people2[0].name;
+ scope.markerEvents = [
+ {
+ event: 'event0',
+ ids: [id]
+ },
+ {
+ event: 'event1',
+ ids: [id]
+ }
+ ];
+ scope.$digest();
+ $timeout.flush();
+ var event0 = mapCtrl.trigger.calls.argsFor(0)[1];
+ var event1 = mapCtrl.trigger.calls.argsFor(1)[1];
+ expect(event0).toEqual('event0');
+ expect(event1).toEqual('event1');
+ });
+
+
+ });
+
+
it('triggers events', function() {
var person = scope.people[0];
var position = person.location;