diff --git a/src/api.js b/src/api.js index 0a99dc406..aa1bd3fcd 100644 --- a/src/api.js +++ b/src/api.js @@ -21,26 +21,28 @@ const featureTypes = { }; export default function(ctx, api) { - api.modes = Constants.modes; + // API doesn't emit events by default + const silent = ctx.options.silent !== undefined ? !!ctx.options.silent : true; + api.getFeatureIdsAt = function(point) { const features = featuresAt.click({ point }, null, ctx); return features.map(feature => feature.properties.id); }; - api.getSelectedIds = function () { + api.getSelectedIds = function() { return ctx.store.getSelectedIds(); }; - api.getSelected = function () { + api.getSelected = function() { return { type: Constants.geojsonTypes.FEATURE_COLLECTION, features: ctx.store.getSelectedIds().map(id => ctx.store.get(id)).map(feature => feature.toGeoJSON()) }; }; - api.getSelectedPoints = function () { + api.getSelectedPoints = function() { return { type: Constants.geojsonTypes.FEATURE_COLLECTION, features: ctx.store.getSelectedCoordinates().map(coordinate => ({ @@ -72,7 +74,7 @@ export default function(ctx, api) { return newIds; }; - api.add = function (geojson) { + api.add = function(geojson) { const featureCollection = JSON.parse(JSON.stringify(normalize(geojson))); const ids = featureCollection.features.map((feature) => { @@ -89,7 +91,7 @@ export default function(ctx, api) { throw new Error(`Invalid geometry type: ${feature.geometry.type}.`); } const internalFeature = new Model(ctx, feature); - ctx.store.add(internalFeature); + ctx.store.add(internalFeature, { silent }); } else { // If a feature of that id has already been created, and we are swapping it out ... const internalFeature = ctx.store.get(feature.id); @@ -110,7 +112,7 @@ export default function(ctx, api) { }; - api.get = function (id) { + api.get = function(id) { const feature = ctx.store.get(id); if (feature) { return feature.toGeoJSON(); @@ -125,11 +127,11 @@ export default function(ctx, api) { }; api.delete = function(featureIds) { - ctx.store.delete(featureIds, { silent: true }); + ctx.store.delete(featureIds, { silent }); // If we were in direct select mode and our selected feature no longer exists // (because it was deleted), we need to get out of that mode. if (api.getMode() === Constants.modes.DIRECT_SELECT && !ctx.store.getSelectedIds().length) { - ctx.events.changeMode(Constants.modes.SIMPLE_SELECT, undefined, { silent: true }); + ctx.events.changeMode(Constants.modes.SIMPLE_SELECT, undefined, { silent }); } else { ctx.store.render(); } @@ -138,11 +140,11 @@ export default function(ctx, api) { }; api.deleteAll = function() { - ctx.store.delete(ctx.store.getAllIds(), { silent: true }); + ctx.store.delete(ctx.store.getAllIds(), { silent }); // If we were in direct select mode, now our selected feature no longer exists, // so escape that mode. if (api.getMode() === Constants.modes.DIRECT_SELECT) { - ctx.events.changeMode(Constants.modes.SIMPLE_SELECT, undefined, { silent: true }); + ctx.events.changeMode(Constants.modes.SIMPLE_SELECT, undefined, { silent }); } else { ctx.store.render(); } @@ -156,7 +158,7 @@ export default function(ctx, api) { if (stringSetsAreEqual((modeOptions.featureIds || []), ctx.store.getSelectedIds())) return api; // And if we are changing the selection within simple_select mode, just change the selection, // instead of stopping and re-starting the mode - ctx.store.setSelected(modeOptions.featureIds, { silent: true }); + ctx.store.setSelected(modeOptions.featureIds, { silent }); ctx.store.render(); return api; } @@ -166,7 +168,7 @@ export default function(ctx, api) { return api; } - ctx.events.changeMode(mode, modeOptions, { silent: true }); + ctx.events.changeMode(mode, modeOptions, { silent }); return api; }; @@ -175,17 +177,17 @@ export default function(ctx, api) { }; api.trash = function() { - ctx.events.trash({ silent: true }); + ctx.events.trash({ silent }); return api; }; api.combineFeatures = function() { - ctx.events.combineFeatures({ silent: true }); + ctx.events.combineFeatures({ silent }); return api; }; api.uncombineFeatures = function() { - ctx.events.uncombineFeatures({ silent: true }); + ctx.events.uncombineFeatures({ silent }); return api; }; diff --git a/src/modes/mode_interface_accessors.js b/src/modes/mode_interface_accessors.js index 1aeeb2669..ee5eed86f 100644 --- a/src/modes/mode_interface_accessors.js +++ b/src/modes/mode_interface_accessors.js @@ -107,8 +107,8 @@ ModeInterface.prototype.deleteFeature = function(id, opts = {}) { * @name this.addFeature * @param {DrawFeature} feature - the feature to add */ -ModeInterface.prototype.addFeature = function(feature) { - return this._ctx.store.add(feature); +ModeInterface.prototype.addFeature = function(feature, opts = {}) { + return this._ctx.store.add(feature, opts); }; /** diff --git a/src/store.js b/src/store.js index 8b649d529..d32a41c3d 100644 --- a/src/store.js +++ b/src/store.js @@ -117,13 +117,22 @@ Store.prototype.getAllIds = function() { /** * Adds a feature to the store. * @param {Object} feature + * @param {Object} [options] + * @param {Object} [options.silent] - If `true`, this invocation will not fire an event. * * @return {Store} this */ -Store.prototype.add = function(feature) { +Store.prototype.add = function(feature, options = {}) { this.featureChanged(feature.id); this._features[feature.id] = feature; this._featureIds.add(feature.id); + + if (options.silent != null && options.silent === false) { + this.ctx.map.fire(Constants.events.CREATE, { + features: [this._features[feature.id].toGeoJSON()] + }); + } + return this; }; diff --git a/test/api.test.js b/test/api.test.js index 34388a656..5f4a1c0e6 100644 --- a/test/api.test.js +++ b/test/api.test.js @@ -139,11 +139,11 @@ test('Draw.set', () => { 'Draw.add called with new collection'); assert.equal(deleteSpy.callCount, 1, 'Draw.delete called'); - assert.deepEqual(deleteSpy.getCall(0).args, [[ + assert.deepEqual(deleteSpy.getCall(0).args[0], [ pointId, lineId, polygonId - ]], 'Draw.delete called with old features'); + ], 'Draw.delete called with old features'); // Then set to another that contains a feature // with an already-used id diff --git a/test/interaction_events.test.js b/test/interaction_events.test.js index 9e8b8d513..0c245681b 100644 --- a/test/interaction_events.test.js +++ b/test/interaction_events.test.js @@ -982,3 +982,73 @@ test('ensure user interactions fire right events', async (t) => { assert.deepEqual(flushDrawEvents(), [], 'no unexpected draw events'); }); }); + +test('ensure API fire right events', async () => { + const container = document.createElement('div'); + document.body.appendChild(container); + const map = createMap({ container }); + const fireSpy = spy(map, 'fire'); + const afterNextRender = setupAfterNextRender(map); + const Draw = new MapboxDraw({ silent: false }); + + map.addControl(Draw); + await map.on('load'); + + document.body.removeChild(container); + + function flushDrawEvents() { + const drawEvents = []; + for (let i = 0; i < fireSpy.callCount; i++) { + const eventName = fireSpy.getCall(i).args[0]; + if (typeof eventName !== 'string' || eventName.indexOf('draw.') !== 0) continue; + // Ignore draw.render events for now + if (eventName === 'draw.render') continue; + // Ignore draw.actionable events for now + if (eventName === 'draw.actionable') continue; + drawEvents.push(eventName); + } + fireSpy.resetHistory(); + return drawEvents; + } + + Draw.add({ + type: 'Feature', + id: 'point', + properties: {}, + geometry: { + type: 'Point', + coordinates: [10, 10] + } + }); + + Draw.deleteAll(); + + await afterNextRender(); + + Draw.add({ + type: 'Feature', + id: 'line', + properties: {}, + geometry: { + type: 'LineString', + coordinates: [[10, 10], [20, 20]] + } + }); + + await afterNextRender(); + + Draw.changeMode('draw_point'); + Draw.changeMode('draw_line_string'); + Draw.changeMode('draw_polygon'); + Draw.changeMode('simple_select'); + + await afterNextRender(); + + Draw.delete('line'); + + await afterNextRender(); + + assert.deepEqual(flushDrawEvents(), [ + 'draw.create', 'draw.delete', 'draw.create', 'draw.modechange', 'draw.modechange', 'draw.modechange', 'draw.modechange', 'draw.delete' + ], 'no unexpected draw events'); +});