New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
event.stopPropagation() doesn't stop event propagating to view #995
Comments
I agree that this is a bug. Regarding the wrong target on view, could you clarify what you expect to receive? |
@lehni I would expect to see the highest z-index Item or View at the event point, so it can still be view sometimes (if it isn't a propagated event), the same way it is supposed to behave in your code but doesn't because its outside of the closure. |
At the moment, for reasons of performance I only hit-test for items if there are any item events installed... This would break that. It's intentional, not a problem of closures. The event on view never receives another target than the view, I believe... |
@lehni Sorry I should of said the highest one with a listener. Its not an event on view though its an event on item that propagated to view... if an event propagates from one item to another it stores the original item it propagated from, why should view be different? If you bound the event to something then you know what it was as you had a reference to it at bind time, the real useful information is where did that event come from. |
The issue is that It should be the same event that propagates and an events target never changes is the real point. |
That should handle the propagation: while (obj && obj !== stopItem) {
if (emit(obj, type))
break;
obj = obj._parent || obj.view;
} But I'm not entirely sure what's going on in the big boolean return statement in So it looks like the first |
I have already fixed your issue, I just haven't pushed it yet. There is nothing wrong with the propagation the way it's currently written, there was just a mix-up between preventing the default and stopping propagation. There are good reasons for the current separation of items and view (handled by that boolean statement), the lack of a need to hit-test for items when all you want is an event listener on the view being one of them, which is why I am passing the view as the target, not running hit-tests when you most probably never need them (or can run your own if you do). There is no should or shouldn't, really. This is not the DOM, we can make up our own rules. People expect all kinds of things, though. There is never going to be one solution that makes everybody happy. |
PS: Your proposal only works if you actually hit an item. If there is none, then the view would not be receiving the event either. |
PPS: If you are curious about the code, I'd recommend looking a the sources, which are full of explaining comments, e.g.: https://github.com/paperjs/paper.js/blob/develop/src/view/View.js#L1197 |
Reopening, since there are open questions still:
|
@lehni Internally it makes sense to differentiate the view and items but as a user a common interface would be far more useful I think. To me the view is just a part of the scene hierarchy, why should it behave any different events wise? What benefit does Ah I was looking at the My proposal is only for when you land on an item. You still need alternative code for a 0-match hit-test which I believe should work just as it does now. |
I think you should give the code another go since my fix above. It does exactly what you expect it to. The only difference right now is target. |
I don't want to run hit-tests that people might not even use, which is why there currently is the optimization that counts the amount of mouse events installed on items, and only runs hit-tests when there are items that expect events to be called on them. This is a good thing, and works really well (see |
@lehni I have just installed it and it is working great. The I don't understand why my proposal needs you to run additional hit-tests? All I'm proposing is that if an event was propagated by something else then keep that as the target even when it hits view, you've already done the hit test otherwise you couldn't of fired the event to propagate anyway. Right now view always receives view as the target even if it wasn't the original receiver. |
For the simple reason that if I am not installing any events on items and only on the view, no hit-tests are run at all at the moment, the actual item target doesn't have to be determined, and that's a good thing. If you want the event target, you'd have to run a hit-test to find out. This could be masked behind a This is what I've been trying to explain in all my comments here: This is the reason why things are implemented the way they are, and that's by design, and not a bug. |
@lehni but thats fine if there are no events on any items then the target would be the view because the event didn't propagate. I think you are misunderstanding what I'm suggesting. There is no need for additional hit-tests you just set the target as the first thing that emits and then ensure it never changes. My issue is that there is an event on an item if you trigger an event on that when the event also triggers on view the target should be the original item and not the view. |
No, I don't think so... But I think it's wrong that you get a different target on view events based on whether you have item events installed or not. |
@lehni You just get whatever triggered the first emit. No need for any additional hit-tests. If you can call You aren't getting a different target because there's no events installed, you would also get the view as a target if you had events installed and you didn't emit them. Its got nothing to do with what is installed but what is emitting. |
You are wrong. This has nothing to do with closures. You make wrong assumptions about the code I've written, and I'm getting tired of trying to explain it to you. |
@lehni I'm not assuming. Right now it doesn't propagate to the view... so when you call var target = obj,
prevented = false,
mouseEvent;
function emit(obj, type) {
if (obj.responds(type)) {
if (!mouseEvent) {
mouseEvent = new MouseEvent(type, event, point, target,
prevPoint ? point.subtract(prevPoint) : null);
}
mouseEvent.id = point;
if (obj.emit(type, mouseEvent)) {
called = true;
if (mouseEvent.prevented)
prevented = true;
if (mouseEvent.stopped)
return true;
}
} else {
var fallback = fallbacks[type];
if (fallback)
return emit(obj, fallback);
}
}
I think you think I'm talking about the hit-item which I am not. I am saying that target should always be the first thing the event triggers on. As it already is for everything else except the view. |
There really is no need to explain the code to me, I've written it, I know what it does. And all of it is intentional : ) What I am saying is is this:
From here the rest should make sense. |
@lehni but I'm not talking about whats under the mouse:
The target is the first item, you've already done the hitTest because you've already called The whole issue is contained with To explain with code if you took:
and made it:
then you would have: return (dragItem && emitMouseEvent(dragItem, dragItem, type, event, point,
prevPoint)
|| item && item !== dragItem && !item.isDescendant(dragItem)
&& emitMouseEvent(item, item, fallbacks[type] || type, event, point,
prevPoint, dragItem)
|| emitMouseEvent(view, dragItem || item || view, type, event, point, prevPoint)); and it would work as expected. But I expect you want it to write it more succinctly than that. |
And I'm saying that the target shouldn't be different based on whether an item has an event installed or not. We're looping. |
@lehni But what purpose does always having the view as a Nothing else treats events like this, it doesn't do anything but force me to write edge cases. Why do Items get different targets. It's nothing to do with whether events are installed but whether the event propagated. I don't understand why the event has to be different when it reaches the view... that is the weirder behaviour. What if I want to put catch-all listeners on the view that do things if certain types of elements triggered stuff (a circle reacted to a click event... now do this). Impossible if the |
An API that changes its behavior based on whether some / any item (not even the item under the mouse, necessarily) has an event installed or not, is erratic and to be avoided at all cost. But that's what your proposal would do. As soon as any item has an event, the hit-test is run and the target item is found, whether it has the event installed or not. The discussion whether view as the target of view events makes sense or not is a separate one, and I said that there is a way to make target work differently (through the accessor), but I didn't see the need for it and decided it didn't merit the added code. If you want, you can just think of view events as not having a target currently, and be fine with that. Or you can argue it should have a target and it should be an item or the view if there is no item under the mouse, and that would require new code, as your proposal wouldn't solve that either in all situations. |
@lehni it already changes its behaviour... the current behaviour is the erratic one. Currently the definition of Perhaps the code is wrong but the principle isn't. HTML events are erratic? They don't do that, |
Here we loop again. |
@lehni Can you tell me the definition of My definition: The first object to receive an emitted event. |
We could probably add it in a really simple way, through |
If that's too extreme (probably), then maybe the |
Sounds good. One question now I've had time to think about it. Are we being consistent with the hitTest as sometimes its the Perhaps we should just set click on rectangle1:
click on rectangle2:
click on group:
click on view:
Unless I'm misunderstanding |
Yeah that's what I was wondering about too. So this whole |
Ha sorry I can't really remember what we were going over before it might have already been suggested, was just running it over my head last night and seems like the most elegant without degrading performance. Also I messed up the naming a bit in that sketch this is a better one: |
Yeah I think I had a misconception about what |
@lehni Not at all. I think we both just had a stressful day and felt we were right I was making assumptions on snippets of code, I know you were fed up with the events just having refactored them and rightly so, I know the feeling. I think we are on to a winner here though. |
@georeith yeah I do remember some preexisting levels of stress that day, unrelated : ) I do like where we ended up here, code looks good! I wasn't keen on the It would be good to have unit tests in place for all this mouse event stuff... I guess we could just emulate prerecorded mouse sequences by the use of |
That's what we've settled on doing with our unit tests. Here's what we've been using to dispatch events:
|
@elliotbonneville that's great! We should do this too. What are you testing for this way? |
@georeith now with |
@georeith I've been juggling too many things this week, didn't realize we made a wrong assumption, and this was masked by the accidental removal of a code optimization that only executes the hit-test when it's sure to be needed. I've added this optimization back now (ab24f92), and it shows that we do need the https://jsfiddle.net/lehni/ou13xkzs/ The event is installed on the outmost I hope we can finally clear this confusion up : ) I think we have two options: Keep the code simple and always run a hit-test, wether we need it or not (slow), or bring back my |
@lehni Indeed that is the HTML way. I was thinking we would only do top of stack if it subscribes to the event (which deviates from HTML but saves additional hit-testing) leaving it up to the developer to do it or not. So what I was thinking was the first emitted callback sets Although I'm happy with if we do match the HTML spec too. Just worried it will make mousemove listeners slow for scenes with a large number of items. Not sure how optimised hit-test itself is. Under what circumstances does the hit-test already exist? Originally I imagined you ran a hit-test only on items with listeners for that event. Edit: It could be possible to run the full hit-test once, cache it and invalidate it whenever there is a geometrical change. Is that what the hit-test optimisation does? That would still be slow for what I imagine are common cases like mousemove based translation or resize which would invalidate it on every event though. |
The way I had written is, the hit-test is only run when it's directly required (e.g. because of |
@lehni Ah ok I like that it is in a getter on |
I guess it would, but I guess we could live with that? : ) But yeah,you've got a point. Damn,... |
@lehni Hmmm I'm just worried it would create more issues. Its a bit of a strange side effect to include in code, having to remember to read |
Yeah I agree. |
Always run the test? |
@lehni I guess we have to to match HTML spec. |
@georeith matching the spec means always running the hit-test. We could also activate it by default but offer a flag that turns it off? |
What I mean is: There could be a need for no "correct" item-level event support. It could be nice to be able to install events on the view only, and not have paper.js take care of event#target, for performance reasons. |
@lehni Sounds good, I would just return |
Calling
event.stopPropagation()
does not stop an event propagating to the view.Also the event on the view has the target set as the view, although the original target was the item, is this done on purpose?
Edit: The issue is here:
Propagation to view seems to be handled by
emitMouseEvents
if the first two OR statements are false. However propagation detection is inemitMouseEvent
and uses a closuredMouseEvent
so the view receives a different one with the wrong target and without thestopped
property.The text was updated successfully, but these errors were encountered: