Skip to content
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

Diff does not capture on{event} handlers #132

Open
cowboyd opened this issue Nov 21, 2023 · 6 comments
Open

Diff does not capture on{event} handlers #132

cowboyd opened this issue Nov 21, 2023 · 6 comments

Comments

@cowboyd
Copy link

cowboyd commented Nov 21, 2023

If the target DOM node contains event handler properties, they are not captured in the diff and therefore not subsequently applied.

https://codepen.io/cowboyd/pen/ExrEpwe?editors=1011

@johanneswilm
Copy link
Member

You are welcome to provide a patch to offer such an option, but its not something I would have expected to be captured by a dom diffing library. So I'll close this for now but if you are willing to provide a patch, I'm willing to reopen this. If you do provide a patch, it should be done in such a way that it is not being done by default.

@cowboyd
Copy link
Author

cowboyd commented Nov 21, 2023

Hi @johanneswilm! Thanks for the prompt reply. If you can provide guidance and feedback, I'm definitely willing to submit a PR for this!

@johanneswilm johanneswilm reopened this Nov 22, 2023
@johanneswilm
Copy link
Member

@cowboyd Writing this code would be a challenge. There are a number of questions one would need an answer for several questions such as:

  • How does one compare two functions that have been added through event listeners?
  • How does one serialize the function attached by eventlistener so that it can be stored or sent?
  • A lot of uses of diffDOM have to do with changes being replicated on a different computer and the changes being sent over the net. How does one ensure that all the variables used in the function also exist on the receiving computer?

I don't have a good answer myself. These are some of the reasons why I always attached event listeners at elements outside the dom element that was being diffed rather than inside. The specific element that the event started on could then be found by looking at event.target. That works well for elements that are bubbling.

@cowboyd
Copy link
Author

cowboyd commented Nov 24, 2023

@johanneswilm I agree, there are definitely some challenges here, and I think scoping the capability to a limited context is the only option. In my particular case, I'm only interested in transformations that happen in a single browsing context.

Given that as a precursor:

How does one compare two functions that have been added through event listeners?

Since callbacks added via EventTarget.addEvenListerer are not represented at all via the properties on an Element, they would be out of scope for this feature. I.e. there is no element.listeners (that I know of) to either compare or even copy.

How does one serialize the function attached by eventlistener so that it can be stored or sent?

I don't think that would be possible, but again, would be out of scope. The DOM contains objects that simply cannot be serialized into HTML which is the key difference between DOM properties and attributes. In fact, unless a DOM property is explicitly bound to an HTML attribute, they will exist in completely different spaces; the attribute living in that which can be serialized into HTML and sent across the wire, and the property living purely within the realm of transient client state.

let button = document.createElement("button")
button.hello = "World";
button.setAttribute("hello", "Planet");

button.hello //=> "World"
button.outerHTML //=> <button hello="Planet"></button>

A lot of uses of diffDOM have to do with changes being replicated on a different computer and the changes being sent over the net. How does one ensure that all the variables used in the function also exist on the receiving computer?

I would imagine that a feature like this would require the addition of an operation such as addProperty, and no attempt would be made to serialize the content of addProperty. By the same token, applying a diff that contains any addProperty operations would be either a warning or a straight up error unless you add a specific allowAddProperty as an option to apply()

In other words, because there is no expectation that DOM properties can be serialized into HTML, there would be no expectation that diffs of DOM properties would be applicable outside the context that generated them.

Given these constraints, do you think I'm on the right track with a separate addProperty operation? If so, what would you see as the best start to implementing it?

@johanneswilm
Copy link
Member

Given these constraints, do you think I'm on the right track with a separate addProperty operation? If so, what would you see as the best start to implementing it?

@cowboyd I'm a bit confused. If you are not actually diffing these and you just manually have to create addProperty operations, what would be the point of integrating it into this library? Would it not be easier to just add your manually created event listeners after applying a diff without using diffDOM? Maybe it would help with some pseudo code to better explain what you are hoping to do and why it makes sense to have it be part of diffDOM?

@cowboyd
Copy link
Author

cowboyd commented Nov 24, 2023

Yes, without an example, it is all very abstract. So some context:

I'm writing an interactive UI library using the Structured Concurrency library Effection and rather than have stateful html components that manage side effects, I'm toying with the idea of having the effects manage stateless html.

Here is an actual example of a counter component, which is an inversion of the normal style. The effects "own" the html, not the other way around which is what we're used to.

import { action, type Operation } from "effection"

export default function* Counter(): Operation<void> {
  let count = 0;

  while (true) {
    yield* action(function*(resolve) {
      yield* <button onclick={resolve}>Clicks: {count}</button>;
    })
    count++;
  }
}

As you can see, I'm using the action effect to capture a continuation, and then using the jsx button value (which is just a value here; no react, no preact, nothing stateful, just a plain old javascript object) which is then used as the input to the render effect which does a Dom diff and apply against the current element.

When theresolve is invoked, the action returns, the loop continues, the count variable is incremented, and a new action is created and the next version of the html is rendered.

In short, I'd like to be able to pass a continuation to a running effect directly as an event handler.

I actually have a version of this code working without diffDOM, but it is creating a new element every time and replacing the old one wholesale. I'd much rather mutate the existing tree in place.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants