diff --git a/src/directives/gmMarkers.js b/src/directives/gmMarkers.js index 6483128..fcf8a0c 100644 --- a/src/directives/gmMarkers.js +++ b/src/directives/gmMarkers.js @@ -9,7 +9,10 @@ * To use, you specify an array of custom objects and tell the directive how to * extract an id and position from them. A marker will be created for each of * your objects. If you assign a new array to your scope variable or change the - * array's length, the markers will also update. + * array's length (i.e. add or remove an object), the markers will also update. + * The one case where `gmMarkers` can not automatically detect changes to your + * objects is when you mutate objects in the array. To inform the directive of + * such changes, see the `gmMarkersUpdate` event below. * * Only the `gm-objects`, `gm-id` and `gm-position` attributes are required. * @@ -17,7 +20,6 @@ * These can be any objects you wish to attach to markers, the only requirement * is that they have a uniform method of accessing an id and a position. * - * * @param {expression} gm-id an angular expression that given an object from * `gm-objects`, evaluates to a unique identifier for that object. Your object * can be accessed through the variable `object`. See `gm-position` below for @@ -86,18 +88,40 @@ * 'position_changed', write it as 'gm-on-position-changed'. */ +/** + * @ngdoc event + * @name angulargm.directive:gmMarkers#gmMarkersUpdate + * @eventOf angulargm.directive:gmMarkers + * @eventType listen on current gmMarkers scope + * + * @description Manually tell the `gmMarkers` directive to update the markers. + * This is useful to tell the directive when an object from `gm-objects` is + * mutated--`gmMarkers` can not pick up on such changes automatically. + * + * @param {string} objects Not required. The name of the scope variable which + * holds the objects to update markers for, i.e. what you set `gm-objects` to. + * It is useful because there may be multiple instances of the `gmMarkers` + * directive. If not specified, all instances of `gmMarkers` which are child + * scopes will update their markers. + * + * @example + * ```js + * $scope.$broadcast('gmMarkersUpdate', 'myObjects'); + * ``` + */ + /** * @ngdoc event * @name angulargm.directive:gmMarkers#gmMarkersRedraw * @eventOf angulargm.directive:gmMarkers * @eventType listen on current gmMarkers scope * - * @description Force the gmMarkers directive to clear and redraw all markers. + * @description Force the `gmMarkers` directive to clear and redraw all markers. * * @param {string} objects Not required. The name of the scope variable which * holds the objects to redraw markers for, i.e. what you set `gm-objects` to. * It is useful because there may be multiple instances of the `gmMarkers` - * directive. If not specified, all instances of gmMarkers which are child + * directive. If not specified, all instances of `gmMarkers` which are child * scopes will redraw their markers. * * @example @@ -115,7 +139,7 @@ * @description Emitted when markers are updated. * * @param {string} objects the name of the scope variable which holds the - * objects the gmMarkers directive was constructed with. This is what + * objects the `gmMarkers` directive was constructed with. This is what * `gm-objects` was set to. * * @example diff --git a/src/directives/gmPolylines.js b/src/directives/gmPolylines.js index bd9f1c4..c7745ae 100644 --- a/src/directives/gmPolylines.js +++ b/src/directives/gmPolylines.js @@ -9,7 +9,10 @@ * To use, you specify an array of custom objects and tell the directive how to * extract location data from them. A polyline will be created for each of your * objects. If you assign a new array to your scope variable or change the - * array's length, the polylines will also update. + * array's length, the polylines will also update. The one case where + * `gmPolylines` can not automatically detect changes to your objects is when + * you mutate objects in the array. To inform the directive of such changes, + * see the `gmPolylinesUpdate` event below. * * Only the `gm-objects`, `gm-id` and `gm-path` attributes are required. * @@ -17,7 +20,6 @@ * These can be any objects you wish to attach to polylines, the only requirement * is that they have a uniform method of accessing an id and a path. * - * * @param {expression} gm-path an angular expression that given an object * from `gm-objects`, evaluates to an array of objects with lat and lng * properties. Your object can be accessed through the variable `object`. For @@ -78,6 +80,28 @@ * 'position_changed', write it as 'gm-on-position-changed'. */ +/** + * @ngdoc event + * @name angulargm.directive:gmPolylines#gmPolylinesUpdate + * @eventOf angulargm.directive:gmPolylines + * @eventType listen on current gmPolylines scope + * + * @description Manually tell the `gmPolylines` directive to update the polylines. + * This is useful to tell the directive when an object from `gm-objects` is + * mutated--`gmPolylines` can not pick up on such changes automatically. + * + * @param {string} objects Not required. The name of the scope variable which + * holds the objects to update polylines for, i.e. what you set `gm-objects` to. + * It is useful because there may be multiple instances of the `gmPolylines` + * directive. If not specified, all instances of `gmPolylines` which are child + * scopes will update their polylines. + * + * @example + * ```js + * $scope.$broadcast('gmPolylinesUpdate', 'myObjects'); + * ``` + */ + /** * @ngdoc event * @name angulargm.directive:gmPolylines#gmPolylinesRedraw diff --git a/src/services/angulargmShape.js b/src/services/angulargmShape.js index ed4d8ca..1d5c73b 100644 --- a/src/services/angulargmShape.js +++ b/src/services/angulargmShape.js @@ -45,14 +45,16 @@ */ function _addNewElements(type, scope, controller, handlers, objectCache, optionsFn) { angular.forEach(objectCache, function(object, id) { - var elementExists = controller.hasElement(type, scope.$id, id); + var element = controller.getElement(type, scope.$id, id); - if (!elementExists) { - var options = optionsFn(object); - if (options == null) { - return; - } + var options = optionsFn(object); + if (options == null) { + return; + } + if (element) { + controller.updateElement(type, scope.$id, id, options); + } else { controller.addElement(type, scope.$id, id, options); var element = controller.getElement(type, scope.$id, id); @@ -141,6 +143,12 @@ updateElements(scope, scope.gmObjects()); } }); + + scope.$on(_formatEventName('gmShapesUpdate', type), function(event, objectsName) { + if (objectsName == null || objectsName === attrs.gmObjects) { + updateElements(scope, scope.gmObjects()); + } + }); } /** diff --git a/test/unit/directives/gmMarkersSpec.js b/test/unit/directives/gmMarkersSpec.js index 413907f..d74ca70 100644 --- a/test/unit/directives/gmMarkersSpec.js +++ b/test/unit/directives/gmMarkersSpec.js @@ -43,6 +43,7 @@ describe('gmMarkers', function() { mapCtrl = elm.controller('gmMap'); spyOn(mapCtrl, 'addElement').andCallThrough(); + spyOn(mapCtrl, 'updateElement').andCallThrough(); spyOn(mapCtrl, 'removeElement').andCallThrough(); spyOn(mapCtrl, 'trigger').andCallThrough(); spyOn(mapCtrl, 'addListener').andCallThrough(); @@ -142,7 +143,7 @@ describe('gmMarkers', function() { it('updates markers with removed objects', function() { var person = scope.people.pop(); scope.$digest(); - var position = new google.maps.LatLng(person.lat, person.lng); + var position = objToLatLng(person); expect(mapCtrl.removeElement).toHaveBeenCalledWith('marker', markersScopeId, '3'); }); @@ -155,6 +156,17 @@ describe('gmMarkers', function() { }); + it('updates markers with changed objects when update triggered', function() { + var person = scope.people[0] + person.lat = person.lat + 5; + person.lng = person.lng + 5; + var newPosition = objToLatLng(person); + scope.$broadcast('gmMarkersUpdate', 'people'); + expect(mapCtrl.updateElement).toHaveBeenCalledWith('marker', markersScopeId, + jasmine.any(String), {key: 'value', title: jasmine.any(String), position: newPosition}); + }); + + it('does not add null objects', function() { var origLength = scope.people.length; scope.people.push(null); @@ -175,7 +187,7 @@ describe('gmMarkers', function() { it('triggers events', function() { var person = scope.people[0]; - var position = new google.maps.LatLng(person.lat, person.lng); + var position = objToLatLng(person); var id = person.name scope.markerEvents = [{ event: 'click', @@ -192,8 +204,9 @@ describe('gmMarkers', function() { it('triggers events on multiple markers', function() { - var position0 = new google.maps.LatLng(scope.people[0].lat, scope.people[0].lng); - var position1 = new google.maps.LatLng(scope.people[1].lat, scope.people[1].lng); + + var position0 = objToLatLng(scope.people[0]); + var position1 = objToLatLng(scope.people[1]); var id0 = scope.people[0].name var id1 = scope.people[1].name scope.markerEvents = [{ @@ -210,7 +223,7 @@ describe('gmMarkers', function() { it('triggers multiple events on markers', function() { - var position = new google.maps.LatLng(scope.people[0].lat, scope.people[0].lng); + var position = objToLatLng(scope.people[0]); var id = scope.people[0].name scope.markerEvents = [ { @@ -308,6 +321,25 @@ describe('gmMarkers', function() { {key: 'differentValue', title: jasmine.any(String), id: jasmine.any(String), position: jasmine.any(Object)}); }); + + it('listens for marker update event', function() { + var position1 = objToLatLng(scope.people[0]); + var position2 = objToLatLng(scope.people[1]); + scope.getOpts = function(person) { + return { + key: 'differentValue', + title: person.name + }; + }; + scope.$broadcast('gmMarkersUpdate', 'people'); + + expect(mapCtrl.updateElement).toHaveBeenCalledWith('marker', markersScopeId, + jasmine.any(String), {key: 'differentValue', title: jasmine.any(String), position: position1}); + expect(mapCtrl.updateElement).toHaveBeenCalledWith('marker', markersScopeId, + jasmine.any(String), {key: 'differentValue', title: jasmine.any(String), position: position2}); + }); + + it('emits marker update event when markers updated', function() { var count = 0; scope.$on('gmMarkersUpdated', function(event, objects) {