In this example, called ch05_modify, we will customize the remaining feature editing interactions before we go on and create our own. Firstly, we create some further CSS rules for our new controls:
.toolbar .ol-modifyfeat button {
background-image: url(../../res/button_modifyfeat.png);
}
.toolbar .ol-snap button {
background-image: url(../../res/button_snap.png);
}Now that we have the required rules, we can extend our addEditingToolBar method with two additional interactions. As both the modify and the snap interactions accept a collection of features to work with, we go that way and store the selected layer's features in a new collection object:
toolBar.prototype.addEditingToolBar = function () {
[…]
this.activeFeatures = new ol.Collection();
var modifyFeature = new ol.control.Interaction({
label: ' ',
tipLabel: 'Modify features',
className: 'ol-modifyfeat ol-unselectable ol-control',
interaction: new ol.interaction.Modify({
features: this.activeFeatures
})
}).setDisabled(true);
this.editingControls.push(modifyFeature);
var snapFeature = new ol.control.Interaction({
label: ' ',
tipLabel: 'Snap to paths, and vertices',
className: 'ol-snap ol-unselectable ol-control',
interaction: new ol.interaction.Snap({
features: this.activeFeatures
})
}).setDisabled(true);
snapFeature.unset('type');
this.editingControls.push(snapFeature);We created two new custom controls with two interactions. As the snapping control can be and must be active along another editing control, we manually remove its type attribute before adding it to the toolbar. We also linked our new collection to the interactions, making it possible to dynamically modify the features they will work with. Next, we make sure to correctly update our activeFeatures object on the layer change and add our new controls to the toolbar:
layertree.selectEventEmitter.on('change', function () {
[…]
var _this = this;
setTimeout(function () {
_this.activeFeatures.clear();
_this.activeFeatures.extend(layer.getSource().getFeatures());
}, 0);
[…]
}, this); this.addControl(drawPoint).addControl(drawLine).addControl(drawPolygon)
.addControl(modifyFeature).addControl(snapFeature);
return this;
};As creating a copy from our selected layer's features takes some time, rendering our layer tree unresponsive for a few seconds even with a smaller dataset, we make it asynchronous by calling it in a setTimeout function set to zero milliseconds. Note that this method will not speed up our code, but makes our application more responsive.
A responsive application is technically a matter of chunking time-consuming code blocks into smaller and faster ones, and timing their executions when the user doesn't notice. The physical reaction of the human body is significantly slower than visual reaction; thus, by placing the few seconds loading time after the visual feedback with an asynchronous call, the users won't even notice that there was any loading time. Of course, this theorem only applies to small datasets.
If you have to work with large datasets, this will definitely be a bottleneck of your application. In this case, you can construct vector layers with a new collection object and call the source's getFeaturesCollection method on the layer change, avoiding the need for duplicating features. However, as the inner collection of the interactions cannot be changed, you have to construct a new interaction and remove the old one from the map every time.
Finally, we only have to add every newly created feature to our collection, so the interactions can be applied on them without changing the active layer:
toolBar.prototype.handleEvents = function (interaction, type) {
[…]
interaction.on('drawend', function (evt) {
[…]
this.activeFeatures.push(evt.feature);
[…]
};If you save and load up the example, you can see our new controls in action:
