Merge pull request #3307 from Leaflet/map-domevent

Always fire DOM event on the map too
This commit is contained in:
Vladimir Agafonkin 2015-07-06 14:57:56 +03:00
commit 62c71f5c1f
6 changed files with 281 additions and 21 deletions

View File

@ -0,0 +1,107 @@
<!DOCTYPE html>
<html>
<head>
<title>Leaflet debug page</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="stylesheet" href="../../dist/leaflet.css" />
<link rel="stylesheet" href="../css/screen.css" />
<style>
.mybox {
background-color: red;
}
td {
border: transparent solid 2px;
}
td.red {
border: red solid 2px;
}
td.updated {
border: transparent solid 2px;
animation-name: borderfade;
animation-duration: 0.5s;
}
@keyframes borderfade {
from {
border: red solid 2px;
}
to {
border: transparent solid 2px;
}
}
</style>
<script type="text/javascript" src="../../build/deps.js"></script>
<script src="../leaflet-include.js"></script>
<table>
<tr><td> </td><td>Enter </td><td>Move </td><td>Exit </td><td>Click </td></tr>
<tr><td>Triangle 1:</td><td id='enter1'></td><td id='move1'></td><td id='exit1'></td><td id='click1'></td></tr>
<tr><td>Triangle 2:</td><td id='enter2'></td><td id='move2'></td><td id='exit2'></td><td id='click2'></td></tr>
<tr><td>Map: </td><td id='enter3'></td><td id='move3'></td><td id='exit3'></td><td id='click3'></td></tr>
</table>
</head>
<body>
<div id="map"></div>
<script type="text/javascript">
var osmUrl = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
osmAttrib = '&copy; <a href="http://openstreetmap.org/copyright">OpenStreetMap</a> contributors',
osm = L.tileLayer(osmUrl, {maxZoom: 18, attribution: osmAttrib}),
latlng = new L.LatLng(39.05, 8.40);
var map = new L.Map('map', {center: latlng, zoom: 12, layers: [osm]});
function update(domid) {
return function(){
document.getElementById(domid).innerHTML = Date.now();
document.getElementById(domid).className = 'red';
window.setTimeout(function(){
document.getElementById(domid).className = 'updated';
},1);
}
}
var polygon = (new L.Polygon([
[39, 8.40],
[39.10, 8.50],
[39.05, 8.30]
])).addTo(map).on('mouseover',update('enter1'))
.on('mousemove',update('move1'))
.on('mouseout',update('exit1'))
.on('click',update('click1'))
.bindPopup('Triangle 1');
var polygon2 = (new L.Polygon([
[39.03, 8.30],
[39.10, 8.40],
[39.00, 8.30]
])).addTo(map).on('mouseover',update('enter2'))
.on('mousemove',update('move2'))
.on('mouseout',update('exit2'))
.on('click',update('click2'))
.bindPopup('Triangle 2');
var marker = new L.Marker(latlng, {draggable: true})
.bindPopup('Marker');;
map.addLayer(marker);
// map.on('mousemove', function (e) {
// marker.setLatLng(e.latlng);
// });
map.on('mouseover',update('enter3'))
.on('mousemove',update('move3'))
.on('mouseout',update('exit3'))
.on('click',update('click3'));
// We should be able to move marker around in a fluid way,
// plus going over the polygon with no issue.
</script>
</body>
</html>

View File

@ -29,7 +29,8 @@
for (var i = 0, latlngs = [], len = route.length; i < len; i++) { for (var i = 0, latlngs = [], len = route.length; i < len; i++) {
latlngs.push(new L.LatLng(route[i][0], route[i][1])); latlngs.push(new L.LatLng(route[i][0], route[i][1]));
} }
var path = new L.Polyline(latlngs); var canvas = L.canvas();
var path = new L.Polyline(latlngs, {renderer: canvas});
var map = new L.Map('map', {layers: [osm]}); var map = new L.Map('map', {layers: [osm]});
@ -41,7 +42,8 @@
circleOptions = { circleOptions = {
color: 'red', color: 'red',
fillColor: 'yellow', fillColor: 'yellow',
fillOpacity: 0.7 fillOpacity: 0.7,
renderer: canvas
}; };
var circle = new L.Circle(circleLocation, 500000, circleOptions), var circle = new L.Circle(circleLocation, 500000, circleOptions),
@ -79,7 +81,8 @@
var polygon = new L.Polygon([polygonPoints, holePoints], { var polygon = new L.Polygon([polygonPoints, holePoints], {
fillColor: "#333", fillColor: "#333",
color: 'green' color: 'green',
renderer: canvas
}); });
group.addLayer(polygon); group.addLayer(polygon);

View File

@ -619,4 +619,132 @@ describe("Map", function () {
}); });
describe('#DOM events', function () {
var c, map;
beforeEach(function () {
c = document.createElement('div');
c.style.width = '400px';
c.style.height = '400px';
map = new L.Map(c);
map.setView(new L.LatLng(0, 0), 0);
document.body.appendChild(c);
});
afterEach(function () {
document.body.removeChild(c);
});
it("DOM events propagate from polygon to map", function () {
var spy = sinon.spy();
map.on("mousemove", spy);
var layer = new L.Polygon([[1, 2], [3, 4], [5, 6]]).addTo(map);
happen.mousemove(layer._path);
expect(spy.calledOnce).to.be.ok();
});
it("DOM events propagate from canvas polygon to map", function () {
var spy = sinon.spy();
map.on("mousemove", spy);
var layer = new L.Polygon([[1, 2], [3, 4], [5, 6]], {rendered: L.canvas()}).addTo(map);
happen.mousemove(layer._path);
expect(spy.calledOnce).to.be.ok();
});
it("DOM events propagate from marker to map", function () {
var spy = sinon.spy();
map.on("mousemove", spy);
var layer = new L.Marker([1, 2]).addTo(map);
happen.mousemove(layer._icon);
expect(spy.calledOnce).to.be.ok();
});
it("DOM events fired on marker can be cancelled before being caught by the map", function () {
var mapSpy = sinon.spy();
var layerSpy = sinon.spy();
map.on("mousemove", mapSpy);
var layer = new L.Marker([1, 2]).addTo(map);
layer.on("mousemove", L.DomEvent.stopPropagation).on("mousemove", layerSpy);
happen.mousemove(layer._icon);
expect(layerSpy.calledOnce).to.be.ok();
expect(mapSpy.called).not.to.be.ok();
});
it("DOM events fired on polygon can be cancelled before being caught by the map", function () {
var mapSpy = sinon.spy();
var layerSpy = sinon.spy();
map.on("mousemove", mapSpy);
var layer = new L.Polygon([[1, 2], [3, 4], [5, 6]]).addTo(map);
layer.on("mousemove", L.DomEvent.stopPropagation).on("mousemove", layerSpy);
happen.mousemove(layer._path);
expect(layerSpy.calledOnce).to.be.ok();
expect(mapSpy.called).not.to.be.ok();
});
it("DOM events fired on canvas polygon can be cancelled before being caught by the map", function () {
var mapSpy = sinon.spy();
var layerSpy = sinon.spy();
map.on("mousemove", mapSpy);
var layer = new L.Polygon([[1, 2], [3, 4], [5, 6]], {rendered: L.canvas()}).addTo(map);
layer.on("mousemove", L.DomEvent.stopPropagation).on("mousemove", layerSpy);
happen.mousemove(layer._path);
expect(layerSpy.calledOnce).to.be.ok();
expect(mapSpy.called).not.to.be.ok();
});
it("mouseout is only forwared if fired on the original target", function () {
var mapSpy = sinon.spy(),
layerSpy = sinon.spy(),
otherSpy = sinon.spy();
var layer = new L.Polygon([[1, 2], [3, 4], [5, 6]]).addTo(map);
var other = new L.Polygon([[10, 20], [30, 40], [50, 60]]).addTo(map);
map.on("mouseout", mapSpy);
layer.on("mouseout", layerSpy);
other.on("mouseout", otherSpy);
happen.mouseout(layer._path);
expect(mapSpy.called).not.to.be.ok();
expect(otherSpy.called).not.to.be.ok();
expect(layerSpy.calledOnce).to.be.ok();
});
it("mouseout is not forwared to layers if fired on the map", function () {
var mapSpy = sinon.spy(),
layerSpy = sinon.spy(),
otherSpy = sinon.spy();
var layer = new L.Polygon([[1, 2], [3, 4], [5, 6]]).addTo(map);
var other = new L.Polygon([[10, 20], [30, 40], [50, 60]]).addTo(map);
map.on("mouseout", mapSpy);
layer.on("mouseout", layerSpy);
other.on("mouseout", otherSpy);
happen.mouseout(map._container);
expect(otherSpy.called).not.to.be.ok();
expect(layerSpy.called).not.to.be.ok();
expect(mapSpy.calledOnce).to.be.ok();
});
it("preclick is fired before click on marker and map", function () {
var called = 0;
var layer = new L.Marker([1, 2]).addTo(map);
layer.on("preclick", function (e) {
expect(called++).to.eql(0);
expect(e.latlng).to.ok();
});
layer.on("click", function (e) {
expect(called++).to.eql(2);
expect(e.latlng).to.ok();
});
map.on("preclick", function (e) {
expect(called++).to.eql(1);
expect(e.latlng).to.ok();
});
map.on("click", function (e) {
expect(called++).to.eql(3);
expect(e.latlng).to.ok();
});
happen.click(layer._icon);
});
});
}); });

View File

@ -130,6 +130,8 @@ L.DomEvent = {
if (e.stopPropagation) { if (e.stopPropagation) {
e.stopPropagation(); e.stopPropagation();
} else if (e.originalEvent) { // In case of Leaflet event.
e.originalEvent._stopped = true;
} else { } else {
e.cancelBubble = true; e.cancelBubble = true;
} }

View File

@ -233,7 +233,7 @@ L.Canvas = L.Renderer.extend({
}, },
_fireEvent: function (layer, e, type) { _fireEvent: function (layer, e, type) {
this._map._fireDOMEvent(layer, e, type || e.type); this._map._fireDOMEvent(e, type || e.type, [layer]);
}, },
// TODO _bringToFront & _bringToBack, pretty tricky // TODO _bringToFront & _bringToBack, pretty tricky

View File

@ -601,6 +601,7 @@ L.Map = L.Evented.extend({
if (!L.DomEvent) { return; } if (!L.DomEvent) { return; }
this._targets = {}; this._targets = {};
this._targets[L.stamp(this._container)] = this;
var onOff = remove ? 'off' : 'on'; var onOff = remove ? 'off' : 'on';
@ -623,62 +624,81 @@ L.Map = L.Evented.extend({
this._container.scrollLeft = 0; this._container.scrollLeft = 0;
}, },
_findEventTarget: function (src) { _findEventTargets: function (src, bubble) {
var targets = [], target;
while (src) { while (src) {
var target = this._targets[L.stamp(src)]; target = this._targets[L.stamp(src)];
if (target) { if (target) {
return target; targets.push(target);
if (!bubble) { break; }
} }
if (src === this._container) { if (src === this._container) {
break; break;
} }
src = src.parentNode; src = src.parentNode;
} }
return null; return targets;
}, },
_handleDOMEvent: function (e) { _handleDOMEvent: function (e) {
if (!this._loaded || L.DomEvent._skipped(e)) { return; } if (!this._loaded || L.DomEvent._skipped(e)) { return; }
// find the layer the event is propagating from // find the layer the event is propagating from and its parents
var target = this._findEventTarget(e.target || e.srcElement), var type = e.type === 'keypress' && e.keyCode === 13 ? 'click' : e.type;
type = e.type === 'keypress' && e.keyCode === 13 ? 'click' : e.type;
// special case for map mouseover/mouseout events so that they're actually mouseenter/mouseleave if (e.type === 'click') {
if (!target && (type === 'mouseover' || type === 'mouseout') && // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
!L.DomEvent._checkMouse(this._container, e)) { return; } var synth = L.Util.extend({}, e);
synth.type = 'preclick';
this._handleDOMEvent(synth);
}
// prevents outline when clicking on keyboard-focusable element
if (type === 'mousedown') { if (type === 'mousedown') {
// prevents outline when clicking on keyboard-focusable element
L.DomUtil.preventOutline(e.target || e.srcElement); L.DomUtil.preventOutline(e.target || e.srcElement);
} }
this._fireDOMEvent(target || this, e, type); this._fireDOMEvent(e, type);
}, },
_fireDOMEvent: function (target, e, type) { _fireDOMEvent: function (e, type, targets) {
if (!target.listens(type, true) && (type !== 'click' || !target.listens('preclick', true))) { return; }
if (type === 'contextmenu') { if (type === 'contextmenu') {
L.DomEvent.preventDefault(e); L.DomEvent.preventDefault(e);
} }
var isHover = type === 'mouseover' || type === 'mouseout';
targets = (targets || []).concat(this._findEventTargets(e.target || e.srcElement, !isHover));
if (!targets.length) {
targets = [this];
// special case for map mouseover/mouseout events so that they're actually mouseenter/mouseleave
if (isHover && !L.DomEvent._checkMouse(this._container, e)) { return; }
}
var target = targets[0];
// prevents firing click after you just dragged an object // prevents firing click after you just dragged an object
if (e.type === 'click' && !e._simulated && this._draggableMoved(target)) { return; } if (e.type === 'click' && !e._simulated && this._draggableMoved(target)) { return; }
var data = { var data = {
originalEvent: e originalEvent: e
}; };
if (e.type !== 'keypress') { if (e.type !== 'keypress') {
data.containerPoint = target instanceof L.Marker ? data.containerPoint = target instanceof L.Marker ?
this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e); this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
data.layerPoint = this.containerPointToLayerPoint(data.containerPoint); data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
data.latlng = this.layerPointToLatLng(data.layerPoint); data.latlng = this.layerPointToLatLng(data.layerPoint);
} }
if (type === 'click') {
target.fire('preclick', data, true); for (var i = 0; i < targets.length; i++) {
if (targets[i].listens(type, true)) {
targets[i].fire(type, data, true);
if (data.originalEvent._stopped) { return; }
}
} }
target.fire(type, data, true);
}, },
_draggableMoved: function (obj) { _draggableMoved: function (obj) {