Skip to content

Commit c99763c

Browse files
Emit events on API call with suppressAPIEvents: false (#1264)
--------- Co-authored-by: Aleksandr Shoronov <[email protected]>
1 parent 2dceab3 commit c99763c

11 files changed

+212
-41
lines changed

docs/API.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ All of the following options are optional.
3636
- `modes`, Object: over ride the default modes with your own. `MapboxDraw.modes` can be used to see the default values. More information on custom modes [can be found here](https://github.com/mapbox/mapbox-gl-draw/blob/main/docs/MODES.md).
3737
- `defaultMode`, String (default: `'simple_select'`): the mode (from `modes`) that user will first land in.
3838
- `userProperties`, boolean (default: `false`): properties of a feature will also be available for styling and prefixed with `user_`, e.g., `['==', 'user_custom_label', 'Example']`
39+
- `suppressAPIEvents`, boolean (default: `true`): Whether or not to emit events when calling Draw API methods. If `false`, events will be emitted.
3940

4041
## Modes
4142

@@ -449,7 +450,7 @@ This event will *not* fire when a feature is created or deleted. To track those
449450
The event data is an object with the following shape:
450451

451452
```js
452-
{
453+
{
453454
features: Array<Feature>, // Array of features that were updated
454455
action: string // Name of the action that triggered the update
455456
}
@@ -493,7 +494,7 @@ This event is fired just after the current mode stops and just before the next m
493494
The event data is an object with the following shape:
494495

495496
```js
496-
{
497+
{
497498
mode: string // The next mode, i.e. the mode that Draw is changing to
498499
}
499500
```

src/api.js

+20-18
Original file line numberDiff line numberDiff line change
@@ -21,26 +21,28 @@ const featureTypes = {
2121
};
2222

2323
export default function(ctx, api) {
24-
2524
api.modes = Constants.modes;
2625

26+
// API doesn't emit events by default
27+
const silent = ctx.options.suppressAPIEvents !== undefined ? !!ctx.options.suppressAPIEvents : true;
28+
2729
api.getFeatureIdsAt = function(point) {
2830
const features = featuresAt.click({ point }, null, ctx);
2931
return features.map(feature => feature.properties.id);
3032
};
3133

32-
api.getSelectedIds = function () {
34+
api.getSelectedIds = function() {
3335
return ctx.store.getSelectedIds();
3436
};
3537

36-
api.getSelected = function () {
38+
api.getSelected = function() {
3739
return {
3840
type: Constants.geojsonTypes.FEATURE_COLLECTION,
3941
features: ctx.store.getSelectedIds().map(id => ctx.store.get(id)).map(feature => feature.toGeoJSON())
4042
};
4143
};
4244

43-
api.getSelectedPoints = function () {
45+
api.getSelectedPoints = function() {
4446
return {
4547
type: Constants.geojsonTypes.FEATURE_COLLECTION,
4648
features: ctx.store.getSelectedCoordinates().map(coordinate => ({
@@ -72,7 +74,7 @@ export default function(ctx, api) {
7274
return newIds;
7375
};
7476

75-
api.add = function (geojson) {
77+
api.add = function(geojson) {
7678
const featureCollection = JSON.parse(JSON.stringify(normalize(geojson)));
7779

7880
const ids = featureCollection.features.map((feature) => {
@@ -89,14 +91,14 @@ export default function(ctx, api) {
8991
throw new Error(`Invalid geometry type: ${feature.geometry.type}.`);
9092
}
9193
const internalFeature = new Model(ctx, feature);
92-
ctx.store.add(internalFeature);
94+
ctx.store.add(internalFeature, { silent });
9395
} else {
9496
// If a feature of that id has already been created, and we are swapping it out ...
9597
const internalFeature = ctx.store.get(feature.id);
9698
const originalProperties = internalFeature.properties;
9799
internalFeature.properties = feature.properties;
98100
if (!isEqual(originalProperties, feature.properties)) {
99-
ctx.store.featureChanged(internalFeature.id);
101+
ctx.store.featureChanged(internalFeature.id, { silent });
100102
}
101103
if (!isEqual(internalFeature.getCoordinates(), feature.geometry.coordinates)) {
102104
internalFeature.incomingCoords(feature.geometry.coordinates);
@@ -110,7 +112,7 @@ export default function(ctx, api) {
110112
};
111113

112114

113-
api.get = function (id) {
115+
api.get = function(id) {
114116
const feature = ctx.store.get(id);
115117
if (feature) {
116118
return feature.toGeoJSON();
@@ -125,11 +127,11 @@ export default function(ctx, api) {
125127
};
126128

127129
api.delete = function(featureIds) {
128-
ctx.store.delete(featureIds, { silent: true });
130+
ctx.store.delete(featureIds, { silent });
129131
// If we were in direct select mode and our selected feature no longer exists
130132
// (because it was deleted), we need to get out of that mode.
131133
if (api.getMode() === Constants.modes.DIRECT_SELECT && !ctx.store.getSelectedIds().length) {
132-
ctx.events.changeMode(Constants.modes.SIMPLE_SELECT, undefined, { silent: true });
134+
ctx.events.changeMode(Constants.modes.SIMPLE_SELECT, undefined, { silent });
133135
} else {
134136
ctx.store.render();
135137
}
@@ -138,11 +140,11 @@ export default function(ctx, api) {
138140
};
139141

140142
api.deleteAll = function() {
141-
ctx.store.delete(ctx.store.getAllIds(), { silent: true });
143+
ctx.store.delete(ctx.store.getAllIds(), { silent });
142144
// If we were in direct select mode, now our selected feature no longer exists,
143145
// so escape that mode.
144146
if (api.getMode() === Constants.modes.DIRECT_SELECT) {
145-
ctx.events.changeMode(Constants.modes.SIMPLE_SELECT, undefined, { silent: true });
147+
ctx.events.changeMode(Constants.modes.SIMPLE_SELECT, undefined, { silent });
146148
} else {
147149
ctx.store.render();
148150
}
@@ -156,7 +158,7 @@ export default function(ctx, api) {
156158
if (stringSetsAreEqual((modeOptions.featureIds || []), ctx.store.getSelectedIds())) return api;
157159
// And if we are changing the selection within simple_select mode, just change the selection,
158160
// instead of stopping and re-starting the mode
159-
ctx.store.setSelected(modeOptions.featureIds, { silent: true });
161+
ctx.store.setSelected(modeOptions.featureIds, { silent });
160162
ctx.store.render();
161163
return api;
162164
}
@@ -166,7 +168,7 @@ export default function(ctx, api) {
166168
return api;
167169
}
168170

169-
ctx.events.changeMode(mode, modeOptions, { silent: true });
171+
ctx.events.changeMode(mode, modeOptions, { silent });
170172
return api;
171173
};
172174

@@ -175,22 +177,22 @@ export default function(ctx, api) {
175177
};
176178

177179
api.trash = function() {
178-
ctx.events.trash({ silent: true });
180+
ctx.events.trash({ silent });
179181
return api;
180182
};
181183

182184
api.combineFeatures = function() {
183-
ctx.events.combineFeatures({ silent: true });
185+
ctx.events.combineFeatures({ silent });
184186
return api;
185187
};
186188

187189
api.uncombineFeatures = function() {
188-
ctx.events.uncombineFeatures({ silent: true });
190+
ctx.events.uncombineFeatures({ silent });
189191
return api;
190192
};
191193

192194
api.setFeatureProperty = function(featureId, property, value) {
193-
ctx.store.setFeatureProperty(featureId, property, value);
195+
ctx.store.setFeatureProperty(featureId, property, value, { silent });
194196
return api;
195197
};
196198

src/constants.js

+1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ export const events = {
6868

6969
export const updateActions = {
7070
MOVE: 'move',
71+
CHANGE_PROPERTIES: 'change_properties',
7172
CHANGE_COORDINATES: 'change_coordinates'
7273
};
7374

src/modes/mode_interface_accessors.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,8 @@ ModeInterface.prototype.deleteFeature = function(id, opts = {}) {
107107
* @name this.addFeature
108108
* @param {DrawFeature} feature - the feature to add
109109
*/
110-
ModeInterface.prototype.addFeature = function(feature) {
111-
return this._ctx.store.add(feature);
110+
ModeInterface.prototype.addFeature = function(feature, opts = {}) {
111+
return this._ctx.store.add(feature, opts);
112112
};
113113

114114
/**

src/options.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ const defaultOptions = {
1414
styles,
1515
modes,
1616
controls: {},
17-
userProperties: false
17+
userProperties: false,
18+
suppressAPIEvents: true
1819
};
1920

2021
const showControls = {

src/store.js

+42-6
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,36 @@ Store.prototype.setDirty = function() {
8484
* @param {string} featureId
8585
* @return {Store} this
8686
*/
87-
Store.prototype.featureChanged = function(featureId) {
87+
Store.prototype.featureCreated = function(featureId, options = {}) {
8888
this._changedFeatureIds.add(featureId);
89+
90+
const silent = options.silent != null ? options.silent : this.ctx.options.suppressAPIEvents;
91+
if (silent !== true) {
92+
const feature = this.get(featureId);
93+
this.ctx.events.fire(Constants.events.CREATE, {
94+
features: [feature.toGeoJSON()]
95+
});
96+
}
97+
98+
return this;
99+
};
100+
101+
/**
102+
* Sets a feature's state to changed.
103+
* @param {string} featureId
104+
* @return {Store} this
105+
*/
106+
Store.prototype.featureChanged = function(featureId, options = {}) {
107+
this._changedFeatureIds.add(featureId);
108+
109+
const silent = options.silent != null ? options.silent : this.ctx.options.suppressAPIEvents;
110+
if (silent !== true) {
111+
this.ctx.events.fire(Constants.events.UPDATE, {
112+
action: options.action ? options.action : Constants.updateActions.CHANGE_COORDINATES,
113+
features: [this.get(featureId).toGeoJSON()]
114+
});
115+
}
116+
89117
return this;
90118
};
91119

@@ -117,13 +145,15 @@ Store.prototype.getAllIds = function() {
117145
/**
118146
* Adds a feature to the store.
119147
* @param {Object} feature
148+
* @param {Object} [options]
149+
* @param {Object} [options.silent] - If `true`, this invocation will not fire an event.
120150
*
121151
* @return {Store} this
122152
*/
123-
Store.prototype.add = function(feature) {
124-
this.featureChanged(feature.id);
153+
Store.prototype.add = function(feature, options = {}) {
125154
this._features[feature.id] = feature;
126155
this._featureIds.add(feature.id);
156+
this.featureCreated(feature.id, {silent: options.silent});
127157
return this;
128158
};
129159

@@ -312,13 +342,19 @@ Store.prototype.isSelected = function(featureId) {
312342
* @param {string} featureId
313343
* @param {string} property property
314344
* @param {string} property value
345+
* @param {Object} [options]
346+
* @param {Object} [options.silent] - If `true`, this invocation will not fire an event.
315347
*/
316-
Store.prototype.setFeatureProperty = function(featureId, property, value) {
348+
Store.prototype.setFeatureProperty = function(featureId, property, value, options = {}) {
317349
this.get(featureId).setProperty(property, value);
318-
this.featureChanged(featureId);
350+
351+
this.featureChanged(featureId, {
352+
silent: options.silent,
353+
action: Constants.updateActions.CHANGE_PROPERTIES
354+
});
319355
};
320356

321-
function refreshSelectedCoordinates(store, options) {
357+
function refreshSelectedCoordinates(store, options = {}) {
322358
const newSelectedCoordinates = store._selectedCoordinates.filter(point => store._selectedFeatureIds.has(point.feature_id));
323359
if (store._selectedCoordinates.length !== newSelectedCoordinates.length && !options.silent) {
324360
store._emitSelectionChange = true;

test/api.test.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -139,11 +139,11 @@ test('Draw.set', () => {
139139
'Draw.add called with new collection');
140140
assert.equal(deleteSpy.callCount, 1,
141141
'Draw.delete called');
142-
assert.deepEqual(deleteSpy.getCall(0).args, [[
142+
assert.deepEqual(deleteSpy.getCall(0).args[0], [
143143
pointId,
144144
lineId,
145145
polygonId
146-
]], 'Draw.delete called with old features');
146+
], 'Draw.delete called with old features');
147147

148148
// Then set to another that contains a feature
149149
// with an already-used id

test/feature.test.js

+1-3
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,7 @@ test('Feature#changed', () => {
5050
ctx.store.featureChanged.resetHistory();
5151
feature.changed();
5252
assert.equal(ctx.store.featureChanged.callCount, 1, 'called function on store');
53-
assert.deepEqual(ctx.store.featureChanged.getCall(0).args, [featureGeoJson.id], 'with correct args');
54-
55-
53+
assert.deepEqual(ctx.store.featureChanged.getCall(0).args[0], featureGeoJson.id, 'with correct args');
5654
});
5755

5856
test('Feature#incomingCoords', () => {

0 commit comments

Comments
 (0)