Simplifying and unhacking the ModifyFeature control
With two nested controls (DragFeature, SelectFeature), which - among others - activated two OpenLayers.Handler.Feature instances, the ModifyFeature control was fragile and hard to debug. The issue that @iwillig, @bartvde and myself tried to track down was that the two Feature handlers interfered with each other, making it hard to select features on mobile devices, and causing points to jump during dragging because of not updated last pixel positions. With this refactoring, there are no more nested controls. All that's left is a Drag handler. All tests pass. I had to remove one test that checked for dragging of an unselected point while another was selected, because now (and partially even before this change, thanks to the ugly drag handler hack that is now removed) dragging a point will also select it, saving the user an extra click.
This commit is contained in:
@@ -4,8 +4,7 @@
|
||||
* full text of the license. */
|
||||
|
||||
/**
|
||||
* @requires OpenLayers/Control/DragFeature.js
|
||||
* @requires OpenLayers/Control/SelectFeature.js
|
||||
* @requires OpenLayers/Handler/Drag.js
|
||||
* @requires OpenLayers/Handler/Keyboard.js
|
||||
*/
|
||||
|
||||
@@ -88,18 +87,6 @@ OpenLayers.Control.ModifyFeature = OpenLayers.Class(OpenLayers.Control, {
|
||||
*/
|
||||
virtualVertices: null,
|
||||
|
||||
/**
|
||||
* Property: selectControl
|
||||
* {<OpenLayers.Control.SelectFeature>}
|
||||
*/
|
||||
selectControl: null,
|
||||
|
||||
/**
|
||||
* Property: dragControl
|
||||
* {<OpenLayers.Control.DragFeature>}
|
||||
*/
|
||||
dragControl: null,
|
||||
|
||||
/**
|
||||
* Property: handlers
|
||||
* {Object}
|
||||
@@ -232,64 +219,50 @@ OpenLayers.Control.ModifyFeature = OpenLayers.Class(OpenLayers.Control, {
|
||||
if(!(OpenLayers.Util.isArray(this.deleteCodes))) {
|
||||
this.deleteCodes = [this.deleteCodes];
|
||||
}
|
||||
var control = this;
|
||||
|
||||
// configure the select control
|
||||
var selectOptions = {
|
||||
geometryTypes: this.geometryTypes,
|
||||
clickout: this.clickout,
|
||||
toggle: this.toggle,
|
||||
onBeforeSelect: this.beforeSelectFeature,
|
||||
onSelect: this.selectFeature,
|
||||
onUnselect: this.unselectFeature,
|
||||
scope: this
|
||||
};
|
||||
if(this.standalone === false) {
|
||||
this.selectControl = new OpenLayers.Control.SelectFeature(
|
||||
layer, selectOptions
|
||||
);
|
||||
}
|
||||
|
||||
// configure the drag control
|
||||
var dragOptions = {
|
||||
documentDrag: this.documentDrag,
|
||||
geometryTypes: ["OpenLayers.Geometry.Point"],
|
||||
onStart: function(feature, pixel) {
|
||||
control.dragStart.apply(control, [feature, pixel]);
|
||||
|
||||
// configure the drag handler
|
||||
var dragCallbacks = {
|
||||
down: function(pixel) {
|
||||
this.vertex = null;
|
||||
var feature = this.layer.getFeatureFromEvent(
|
||||
this.handlers.drag.evt);
|
||||
if (feature) {
|
||||
this.dragStart(feature);
|
||||
} else if (this.feature && this.clickout) {
|
||||
this.unselectFeature(this.feature);
|
||||
}
|
||||
},
|
||||
onDrag: function(feature, pixel) {
|
||||
control.dragVertex.apply(control, [feature, pixel]);
|
||||
move: function(pixel) {
|
||||
delete this._unselect;
|
||||
if (this.vertex) {
|
||||
this.dragVertex(this.vertex, pixel);
|
||||
}
|
||||
},
|
||||
onComplete: function(feature) {
|
||||
control.dragComplete.apply(control, [feature]);
|
||||
up: function() {
|
||||
this.handlers.drag.stopDown = false;
|
||||
if (this._unselect) {
|
||||
this.unselectFeature(this._unselect);
|
||||
delete this._unselect;
|
||||
}
|
||||
},
|
||||
featureCallbacks: {
|
||||
over: function(feature) {
|
||||
/**
|
||||
* In normal mode, the feature handler is set up to allow
|
||||
* dragging of all points. In standalone mode, we only
|
||||
* want to allow dragging of sketch vertices and virtual
|
||||
* vertices - or, in the case of a modifiable point, the
|
||||
* point itself.
|
||||
*/
|
||||
if(control.standalone !== true || feature._sketch ||
|
||||
control.feature === feature) {
|
||||
control.dragControl.overFeature.apply(
|
||||
control.dragControl, [feature]);
|
||||
}
|
||||
done: function(pixel) {
|
||||
if (this.vertex) {
|
||||
this.dragComplete(this.vertex);
|
||||
}
|
||||
}
|
||||
};
|
||||
this.dragControl = new OpenLayers.Control.DragFeature(
|
||||
layer, dragOptions
|
||||
);
|
||||
var dragOptions = {
|
||||
documentDrag: this.documentDrag,
|
||||
stopDown: false
|
||||
};
|
||||
|
||||
// configure the keyboard handler
|
||||
var keyboardOptions = {
|
||||
keydown: this.handleKeypress
|
||||
};
|
||||
this.handlers = {
|
||||
keyboard: new OpenLayers.Handler.Keyboard(this, keyboardOptions)
|
||||
keyboard: new OpenLayers.Handler.Keyboard(this, keyboardOptions),
|
||||
drag: new OpenLayers.Handler.Drag(this, dragCallbacks, dragOptions)
|
||||
};
|
||||
},
|
||||
|
||||
@@ -299,8 +272,6 @@ OpenLayers.Control.ModifyFeature = OpenLayers.Class(OpenLayers.Control, {
|
||||
*/
|
||||
destroy: function() {
|
||||
this.layer = null;
|
||||
this.standalone || this.selectControl.destroy();
|
||||
this.dragControl.destroy();
|
||||
OpenLayers.Control.prototype.destroy.apply(this, []);
|
||||
},
|
||||
|
||||
@@ -312,8 +283,8 @@ OpenLayers.Control.ModifyFeature = OpenLayers.Class(OpenLayers.Control, {
|
||||
* {Boolean} Successfully activated the control.
|
||||
*/
|
||||
activate: function() {
|
||||
return ((this.standalone || this.selectControl.activate()) &&
|
||||
this.handlers.keyboard.activate() &&
|
||||
return (this.handlers.keyboard.activate() &&
|
||||
this.handlers.drag.activate() &&
|
||||
OpenLayers.Control.prototype.activate.apply(this, arguments));
|
||||
},
|
||||
|
||||
@@ -331,26 +302,17 @@ OpenLayers.Control.ModifyFeature = OpenLayers.Class(OpenLayers.Control, {
|
||||
this.layer.removeFeatures(this.vertices, {silent: true});
|
||||
this.layer.removeFeatures(this.virtualVertices, {silent: true});
|
||||
this.vertices = [];
|
||||
this.dragControl.deactivate();
|
||||
var feature = this.feature;
|
||||
var valid = feature && feature.geometry && feature.layer;
|
||||
if(this.standalone === false) {
|
||||
if(valid) {
|
||||
this.selectControl.unselect.apply(this.selectControl,
|
||||
[feature]);
|
||||
}
|
||||
this.selectControl.deactivate();
|
||||
} else {
|
||||
if(valid) {
|
||||
this.unselectFeature(feature);
|
||||
}
|
||||
}
|
||||
this.handlers.drag.deactivate();
|
||||
this.handlers.keyboard.deactivate();
|
||||
var feature = this.feature;
|
||||
if (feature && feature.geometry && feature.layer) {
|
||||
this.unselectFeature(feature);
|
||||
}
|
||||
deactivated = true;
|
||||
}
|
||||
return deactivated;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Method: beforeSelectFeature
|
||||
* Called before a feature is selected.
|
||||
@@ -375,11 +337,19 @@ OpenLayers.Control.ModifyFeature = OpenLayers.Class(OpenLayers.Control, {
|
||||
* feature - {<OpenLayers.Feature.Vector>} the selected feature.
|
||||
*/
|
||||
selectFeature: function(feature) {
|
||||
if (this.geometryTypes && OpenLayers.Util.indexOf(this.geometryTypes,
|
||||
feature.geometry.CLASS_NAME) == -1) {
|
||||
return;
|
||||
}
|
||||
if (!this.standalone || this.beforeSelectFeature(feature) !== false) {
|
||||
if (this.feature) {
|
||||
this.unselectFeature(this.feature);
|
||||
}
|
||||
this.feature = feature;
|
||||
this.layer.selectedFeatures.push(feature);
|
||||
this.layer.drawFeature(feature, 'select');
|
||||
this.modified = false;
|
||||
this.resetVertices();
|
||||
this.dragControl.activate();
|
||||
this.onModificationStart(this.feature);
|
||||
}
|
||||
// keep track of geometry modifications
|
||||
@@ -409,8 +379,9 @@ OpenLayers.Control.ModifyFeature = OpenLayers.Class(OpenLayers.Control, {
|
||||
this.layer.destroyFeatures([this.radiusHandle], {silent: true});
|
||||
delete this.radiusHandle;
|
||||
}
|
||||
this.layer.drawFeature(this.feature, 'default');
|
||||
this.feature = null;
|
||||
this.dragControl.deactivate();
|
||||
OpenLayers.Util.removeItem(this.layer.selectedFeatures, feature);
|
||||
this.onModificationEnd(feature);
|
||||
this.layer.events.triggerEvent("afterfeaturemodified", {
|
||||
feature: feature,
|
||||
@@ -418,64 +389,48 @@ OpenLayers.Control.ModifyFeature = OpenLayers.Class(OpenLayers.Control, {
|
||||
});
|
||||
this.modified = false;
|
||||
},
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Method: dragStart
|
||||
* Called by the drag feature control with before a feature is dragged.
|
||||
* This method is used to differentiate between points and vertices
|
||||
* of higher order geometries. This respects the <geometryTypes>
|
||||
* property and forces a select of points when the drag control is
|
||||
* already active (and stops events from propagating to the select
|
||||
* control).
|
||||
* Called by the drag handler before a feature is dragged. This method is
|
||||
* used to differentiate between points and vertices
|
||||
* of higher order geometries.
|
||||
*
|
||||
* Parameters:
|
||||
* feature - {<OpenLayers.Feature.Vector>} The point or vertex about to be
|
||||
* dragged.
|
||||
* pixel - {<OpenLayers.Pixel>} Pixel location of the mouse event.
|
||||
*/
|
||||
dragStart: function(feature, pixel) {
|
||||
// only change behavior if the feature is not in the vertices array
|
||||
if(feature != this.feature && !feature.geometry.parent &&
|
||||
feature != this.dragHandle && feature != this.radiusHandle) {
|
||||
if(this.standalone === false && this.feature) {
|
||||
// unselect the currently selected feature
|
||||
this.selectControl.clickFeature.apply(this.selectControl,
|
||||
[this.feature]);
|
||||
}
|
||||
// check any constraints on the geometry type
|
||||
if(this.geometryTypes == null ||
|
||||
OpenLayers.Util.indexOf(this.geometryTypes,
|
||||
feature.geometry.CLASS_NAME) != -1) {
|
||||
// select the point
|
||||
this.standalone || this.selectControl.clickFeature.apply(
|
||||
this.selectControl, [feature]);
|
||||
/**
|
||||
* TBD: These lines improve workflow by letting the user
|
||||
* immediately start dragging after the mouse down.
|
||||
* However, it is very ugly to be messing with controls
|
||||
* and their handlers in this way. I'd like a better
|
||||
* solution if the workflow change is necessary.
|
||||
*/
|
||||
// prepare the point for dragging
|
||||
this.dragControl.overFeature.apply(this.dragControl,
|
||||
[feature]);
|
||||
this.dragControl.lastPixel = pixel;
|
||||
this.dragControl.handlers.drag.started = true;
|
||||
this.dragControl.handlers.drag.start = pixel;
|
||||
this.dragControl.handlers.drag.last = pixel;
|
||||
dragStart: function(feature) {
|
||||
var isPoint = feature.geometry.CLASS_NAME ==
|
||||
'OpenLayers.Geometry.Point';
|
||||
if (!this.standalone &&
|
||||
((!feature._sketch && isPoint) || !feature._sketch)) {
|
||||
if (this.toggle && this.feature === feature) {
|
||||
// mark feature for unselection
|
||||
this._unselect = feature;
|
||||
}
|
||||
this.selectFeature(feature);
|
||||
}
|
||||
if (feature._sketch || isPoint) {
|
||||
// feature is a drag or virtual handle or point
|
||||
this.vertex = feature;
|
||||
this.handlers.drag.stopDown = true;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Method: dragVertex
|
||||
* Called by the drag feature control with each drag move of a vertex.
|
||||
* Called by the drag handler with each drag move of a vertex.
|
||||
*
|
||||
* Parameters:
|
||||
* vertex - {<OpenLayers.Feature.Vector>} The vertex being dragged.
|
||||
* pixel - {<OpenLayers.Pixel>} Pixel location of the mouse event.
|
||||
*/
|
||||
dragVertex: function(vertex, pixel) {
|
||||
var pos = this.map.getLonLatFromViewPortPx(pixel);
|
||||
var geom = vertex.geometry;
|
||||
geom.move(pos.lon - geom.x, pos.lat - geom.y);
|
||||
this.modified = true;
|
||||
/**
|
||||
* Five cases:
|
||||
@@ -487,9 +442,6 @@ OpenLayers.Control.ModifyFeature = OpenLayers.Class(OpenLayers.Control, {
|
||||
*/
|
||||
if(this.feature.geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
|
||||
// dragging a simple point
|
||||
if(this.feature != vertex) {
|
||||
this.feature = vertex;
|
||||
}
|
||||
this.layer.events.triggerEvent("vertexmodified", {
|
||||
vertex: vertex.geometry,
|
||||
feature: this.feature,
|
||||
@@ -526,7 +478,7 @@ OpenLayers.Control.ModifyFeature = OpenLayers.Class(OpenLayers.Control, {
|
||||
this.virtualVertices = [];
|
||||
}
|
||||
this.layer.drawFeature(this.feature, this.standalone ? undefined :
|
||||
this.selectControl.renderIntent);
|
||||
'select');
|
||||
}
|
||||
// keep the vertex on top so it gets the mouseout after dragging
|
||||
// this should be removed in favor of an option to draw under or
|
||||
@@ -536,7 +488,7 @@ OpenLayers.Control.ModifyFeature = OpenLayers.Class(OpenLayers.Control, {
|
||||
|
||||
/**
|
||||
* Method: dragComplete
|
||||
* Called by the drag feature control when the feature dragging is complete.
|
||||
* Called by the drag handler when the feature dragging is complete.
|
||||
*
|
||||
* Parameters:
|
||||
* vertex - {<OpenLayers.Feature.Vector>} The vertex being dragged.
|
||||
@@ -572,16 +524,6 @@ OpenLayers.Control.ModifyFeature = OpenLayers.Class(OpenLayers.Control, {
|
||||
* Method: resetVertices
|
||||
*/
|
||||
resetVertices: function() {
|
||||
// if coming from a drag complete we're about to destroy the vertex
|
||||
// that was just dragged. For that reason, the drag feature control
|
||||
// will never detect a mouse-out on that vertex, meaning that the drag
|
||||
// handler won't be deactivated. This can cause errors because the drag
|
||||
// feature control still has a feature to drag but that feature is
|
||||
// destroyed. To prevent this, we call outFeature on the drag feature
|
||||
// control if the control actually has a feature to drag.
|
||||
if(this.dragControl.feature) {
|
||||
this.dragControl.outFeature(this.dragControl.feature);
|
||||
}
|
||||
if(this.vertices.length > 0) {
|
||||
this.layer.removeFeatures(this.vertices, {silent: true});
|
||||
this.vertices = [];
|
||||
@@ -632,11 +574,10 @@ OpenLayers.Control.ModifyFeature = OpenLayers.Class(OpenLayers.Control, {
|
||||
// check for delete key
|
||||
if(this.feature &&
|
||||
OpenLayers.Util.indexOf(this.deleteCodes, code) != -1) {
|
||||
var vertex = this.dragControl.feature;
|
||||
if(vertex &&
|
||||
OpenLayers.Util.indexOf(this.vertices, vertex) != -1 &&
|
||||
!this.dragControl.handlers.drag.dragging &&
|
||||
vertex.geometry.parent) {
|
||||
var vertex = this.layer.getFeatureFromEvent(this.handlers.drag.evt);
|
||||
if (vertex &&
|
||||
OpenLayers.Util.indexOf(this.vertices, vertex) != -1 &&
|
||||
!this.handlers.drag.dragging && vertex.geometry.parent) {
|
||||
// remove the vertex
|
||||
vertex.geometry.parent.removeComponent(vertex.geometry);
|
||||
this.layer.events.triggerEvent("vertexremoved", {
|
||||
@@ -645,8 +586,7 @@ OpenLayers.Control.ModifyFeature = OpenLayers.Class(OpenLayers.Control, {
|
||||
pixel: evt.xy
|
||||
});
|
||||
this.layer.drawFeature(this.feature, this.standalone ?
|
||||
undefined :
|
||||
this.selectControl.renderIntent);
|
||||
undefined : 'select');
|
||||
this.modified = true;
|
||||
this.resetVertices();
|
||||
this.setFeatureState();
|
||||
@@ -800,8 +740,7 @@ OpenLayers.Control.ModifyFeature = OpenLayers.Class(OpenLayers.Control, {
|
||||
* map - {<OpenLayers.Map>} The control's map.
|
||||
*/
|
||||
setMap: function(map) {
|
||||
this.standalone || this.selectControl.setMap(map);
|
||||
this.dragControl.setMap(map);
|
||||
this.handlers.drag.setMap(map);
|
||||
OpenLayers.Control.prototype.setMap.apply(this, arguments);
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user