links icon indicating copy to clipboard operation
links copied to clipboard

l:onload doesn't work

Open ezrakilty opened this issue 3 years ago • 3 comments

When I use l:onload in an XHTML element, I expect the corresponding code to get run, client-side after the DOM is loaded. That doesn't happen (see attached minimal example) and I suspect the reason is that the event listeners are all attached as part of an onload handler, so it's too late for them to get run. I have a crude fix for it, although I think it needs a little more work to be correct.

Minimal example:

fun renderView(spam, eggs) {
  <div id="result">
    <h1>spam: {stringToXml(spam)}</h1>
    <p>eggs: {stringToXml(eggs)}</p>
  </div>
}

fun updateView() {
  replaceNode(
    renderView(getInputValue("spam"), getInputValue("eggs")),
    getNodeById("result"))
}

fun mainPage() {
  <html>
    <body class="playingCards" l:onload="{updateView()}" l:onchange="{updateView()}">
      <div class="container">
        <input name="spam" id="spam" />
        <input name="eggs" id="eggs" />
        <div id="result" />
      </div>
    </body>
  </html>
}

fun main() {
  addRoute("/", fun (_) { page <html> {mainPage()} </html> });
  servePages()
}

main()

If you use the above program, you will notice it has the following misbehavior. After entering text into the boxes, the page updates as expected, but if you reload the page the text values are still there yet the "result" div won't be rendered until you update something (i.e., trigger another event). Of course my intention here is really that "result" is treated as an always-updated function of the form elements.

Here's my proof-of-concept fix, although it has problems: (1) it should probably defer running the handlers through setTimeout or the like, and (2) it probably needs a real event object passed to the handler.

--- a/lib/js/jslib.js
+++ b/lib/js/jslib.js
@@ -1294,6 +1294,9 @@ const LINKS = new (function() {
           Object.keys(handlers || { }).forEach(function (event) {
             const target = event.match(/page$/) ? document.documentElement : node;
             const eventName = event.replace(/page$/, "").replace(/^on/, "");
+            if (event == "load") {
+              handlers[event]();
+            }
             return target.addEventListener(eventName, function (e) {
               _formkey = key;

I may endeavour to make a real fix later in my vacation, if I get a chance.

ezrakilty avatar Jul 06 '21 01:07 ezrakilty

Perhaps someone can say what the semantics should be: If an object having an l:onload handler is added to the DOM, after the first page load, should the onload handler run (because the object itself has just loaded) or not run (because the page is not being loaded at that time)?

ezrakilty avatar Jul 19 '21 01:07 ezrakilty

It is a good question. I think originally every l:onload were run when the page was loaded, however, this broke with the introduction of "real pages" as they consume the l:onload event now.

dhil avatar Jul 19 '21 08:07 dhil

I tagged this as a bug because I think the original behavior reported is wrong (compared to what the equivalent HTML+JavaScript would do). But I don't know offhand that this is the case, e.g. if one reloads a page that contains forms that have values that were input by the user, whether they are normally still there in the DOM to be read by an onload event, or they just look like they are. I think the answer to the second question is probably "only run when page is actually being reloaded" but again don't know for sure what an HTML+JavaScript version does or if the intended behavior is standard or idiosyncratic to different browsers.

This relates to an ongoing discussion we've been having about whether / how to clean up the l: attribute approach to web interaction, given that cleaner alternatives now exist like @SimonJF's MVU library. So one could argue for getting rid of the l: attribute approach entirely, and @slindley has. On the other hand it does seem useful for new people since it's not too far away from normal HTML+JS. As far as I can tell l:onload is not handled any different from the other l: attributes (at least based on a quick search through the code).

I think the Right Way to have a continually updated, reload-proof update behavior in Links at this point would probably be to use MVU and have an event that deals with the page being reloaded, but don't know if there is a good or idiomatic way of accomplishing that at the moment.

jamescheney avatar Jul 19 '21 13:07 jamescheney