diffDOM icon indicating copy to clipboard operation
diffDOM copied to clipboard

Diff does not capture `on{event}` handlers

Open cowboyd opened this issue 2 years ago • 6 comments

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

cowboyd avatar Nov 21 '23 17:11 cowboyd

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.

johanneswilm avatar Nov 21 '23 18:11 johanneswilm

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

cowboyd avatar Nov 21 '23 18:11 cowboyd

@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.

johanneswilm avatar Nov 22 '23 06:11 johanneswilm

@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?

cowboyd avatar Nov 24 '23 18:11 cowboyd

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?

johanneswilm avatar Nov 24 '23 18:11 johanneswilm

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.

cowboyd avatar Nov 24 '23 21:11 cowboyd