Move from return false
to m.capture(event)
for event handlers
#2920
Replies: 8 comments
-
Update: I have an alternative that might also be worth considering. We could instead just detect promises and, if they resolve to a truthy value or
|
Beta Was this translation helpful? Give feedback.
-
@dead-claudia we've had this conversation numerous times over the past 6 years, and I struggle to find a way to re-articulate the arguments in a meaningful way because they are perpetually ignored and then forgotten. There is a logical contradiction in the proposal: we are proposing on the one hand a net value in removing a tiny amount of inexpensive code which implements conventionally expected behaviour; but then introducing more code to introduce new semantics ex-nihilo. As regards the specifics of the value of // Current
var result
if (typeof handler === "function") result = handler.call(ev.currentTarget, ev)
else if (typeof handler.handleEvent === "function") handler.handleEvent(ev)
if (this._ && ev.redraw !== false) (0, this._)()
if (result === false) {
ev.preventDefault()
ev.stopPropagation()
}
// Better
var result
if (typeof handler === "function") result = handler.call(ev.currentTarget, ev)
else if (typeof handler.handleEvent === "function") result = handler.handleEvent(ev)
if (this._ && ev.redraw !== false) (0, this._)()
return result If fulfilling native API parity required complex handling to account for contradictions with Mithril API overrides we deemed more valuable, we would be making a bold assertion! But as it is the handling is trivial — in this case, over-complicated by our own misapprehensions — and what we're currently seeing is the tentative development of such home-grown contradictions in order to justify that second-guessing of platform expectations. The debate about special Promise interpretation is also old. Historically, v1's first iteration had the strong opinion that Mithril ought to extend the Promise implementation to try to determine when a Promise chain had eventually settled, and only redraw at such point: As I argued at length at the time, Promise chains can fork, and can do so asynchronously — meaning it is not determinable in the space of one tick whether or how such chains would ever fully settle, since they can be extended at any given point as they are still referenceable in application code — and it is in any case foolhardy to try to second-guess the semantics of any given async transaction as regards when the author of such code might expect the 'one true redraw' to occur. Taking the example from the original post: // As copied from the original post
async onclick() {
await doStuff()
return false // Doesn't work like it looks like it does!
} An author running into this confusion will need to understand that the native APIs are no longer intercepting a false return but a promise with an eventual settlement of false; their argument is then between themselves and the platform. But I think the confusion is less with the imagined author than on our side: // Native return semantics trumps imperative async ergonomics
onclick() {
arbitraryFutureWithSideEffects().then(() => m.redraw())
return false
}
onclick() {
m.request(/**/).then(data => {
Object.assign(closuredState, {data})
})
return false
}
// Imperative async sequence trumps native return ergonomics
async onclick(e) {
e.preventDefault()
e.stopImmediatePropagation()
closuredState.loading = true
await arbitraryFutureWithSideEffects()
closuredState.loading = false
m.redraw()
}
async onclick(e) {
e.preventDefault()
e.stopImmediatePropagation()
closuredState.loading = true
Object.assign(closuredState, {data : await m.request(/**/)})
closuredState.loading = false
} There's a bunch of things going on here, and different considerations for each.
These are relatively simple scenarios, but they show that the Mithril user needs to have some grasp of how low-level native DOM & Mithril APIs work for event & redraw semantics so resolve with precision. My contention is that our historic attempts to do away with DOM lvl 1 semantics and special case async side-effects are the result of internal confusion projected upon a conjectured naive user, but the paternalistic instinct is misguided: The mindset behind these proposals assumes the user is too confused to understand what they are doing with predictable low-level APIs, and seeks to create special cases to ease that confusion: but the solutions complicate behaviour in ways whose conjectured 'simplicity' can't account for mundane variations in real world complexity — this makes things even more confusing for users who would credibly encounter such complexity but can no longer rely on the predictability of low-level APIs to account for such. This is not to say that real Mithril users won't be confused by the essentials of event cancellation mechanics & async logic — everybody is at some point & to some degree and we should fully expect people bumping into thwarted expectations and asking for help — but the solution to such confusion is not to bake complexity into the library and litter the code and documentation with comments about 'intuition' and 'what you might expect' but to give simple & reliable controls whose behaviour is easily described, logically predictable, and flexible enough to build solutions depending on a variety of emergent use cases. |
Beta Was this translation helpful? Give feedback.
-
@dead-claudia What benefit would From @barneycarroll's comment (emphasis added):
My understanding is that returning I created a Flems playground that shows that outside jQuery, returning
Mithril uses |
Beta Was this translation helpful? Give feedback.
-
@mtsknn Consider it in light of #2862 (I had that idea well before that PR, obviously). Event listeners must synchronously prevent their actions. When using an Consider it a compromise of sorts given the problem constraints. Edit: fixed function name |
Beta Was this translation helpful? Give feedback.
-
Barney's referring to the |
Beta Was this translation helpful? Give feedback.
-
Yes, I understand this.
True, I have no direct control over the return value, but I do have access to the event object, so the easiest way is to call
I can see how such a helper function can be useful, but I don't see anything special in
But wait, what do you mean by a higher order function mean in this context? The proposed Ah, I guess you mean something like this: m('button', {
onclick: capture((ev) => {
// ...
}),
}, 'click me')
function capture(handler) {
return ev => {
ev.preventDefault()
ev.stopPropagation()
handler(ev)
}
} And later when I found myself needing conditional capturing, I'd change the higher order function to a first-order function: m('button', {
onclick: (ev) => {
if (foo) captureEvent(ev)
// ...
},
}, 'click me')
function capture(ev) {
ev.preventDefault()
ev.stopPropagation()
} (FWIW I'd probably skip the higher order function.)
Ah, I see. I also see two discrepancies:
|
Beta Was this translation helpful? Give feedback.
-
This is true.
Typo fixed. It's late. 😅
It's not overly special, but also not unique. We already have a couple other relatively small helpers:
Yeah, that's the higher order function I was referring to, and you went through the same thought process I did when I abandoned the idea.
Yeah I kinda glossed that over in "edge cases", should've listed that explicitly.
Good catch. IMHO Mithril's action is the more intuitive one, stopping both. |
Beta Was this translation helpful? Give feedback.
-
Fair enough. 👍
It's also how jQuery does it. I'm not sure which is more intuitive to me, and it's been ages since I last used jQuery. Personally, I might prefer being explicit and calling Anyway, Barney talked about "conventionally expected behaviour" and "the way the platform works," but event handlers in Mithril (and jQuery) don't work like native event handlers when it comes to returning By the way, a third discrepancy (though maybe this too is deemed an "edge case"; after all, you said that Mithril's event listener design is based on
Thanks for the conversation! 😊 |
Beta Was this translation helpful? Give feedback.
-
Mithril version:
Platform and OS:
Project:
Is this something you're interested in implementing yourself?
Description
And also remove the return value handling logic from this section.
Why
I want to see this idiom die, just as it's largely died elsewhere:
Also, it's not compatible with async functions as event handlers.
Oh, and it'll likely result in a small but measurable net reduction in size compared to what we already do to support
return false
.Possible Implementation
See description.
Open Questions
I think the real discussion is should we continue to support the
return false
that standards bodies have effectively declared as legacy?Beta Was this translation helpful? Give feedback.
All reactions