Home

Script event order using Util.HtmlHead.AddScript

Hi,

I'm trying to run a script on DOMContentLoaded where I reference a DOM element in the body. My (incorrect) assumption was that the script would not be run until something is Dumped, but this isn't the case.

When doSomeProcessing below runs, the console in devtools shows an uncaught TypeError on the originalParagraph.appendChild(s); line, as originalParagraph is null - it's not been loaded at this stage.

Is there a better event to listen to so that the I can know that the main content (via Util.RawHtml) has been added?

Best regards

John

void Main()
{
    var sc = new SpecialControl();
    sc.Dump();
}

public class SpecialControl
{
    const string js = """
        if(document.readyState !== 'loading') {
            doSomeProcessing();
        }
        else {
            document.addEventListener('DOMContentLoaded', function () {
                doSomeProcessing()
            });
        }
        function doSomeProcessing() {
            let originalParagraph = document.querySelector("#firstMsg");
            let s = document.createElement("span");
            s.textContent = " DOM fully loaded and parsed";
            originalParagraph.appendChild(s);
            let p = document.createElement("p");
            p.textContent = "Other processing happening here";
            document.body.appendChild(p);
        };
        """;


    object ToDump()
    {
        Util.HtmlHead.AddScript(js);
        return Util.RawHtml($"<p id=\"firstMsg\">Initial message</p>");
    }
}

Comments

  • Have just discovered Util.InvokeScript and that allows me to remove the event listener and use the Util method to call the script.

    To wrap this in a ToDump method I'm calling .Dump() inside it, which feels a bit clumsy, but I can't see an obvious way of ensure the the expected html is going to be present, prior to calling the js function, without it. I also notice that there's a div of id 'final' that other dump output looks like it gets appended to.

    Is there an option for some kind of Util.HtmlBody.AddRawHtml method that would add markup at the moment of calling in a similar way to Util.HtmlHead.AddScript?

    void Main()
    {
        var sc = new SpecialControl();
        sc.Dump();
    }
    
    public class SpecialControl
    {
        const string js = """
            function doSomeProcessing() {
                let originalParagraph = document.querySelector("#firstMsg");
                let s = document.createElement("span");
                s.textContent = " DOM fully loaded and parsed";
                originalParagraph.appendChild(s);
                let p = document.createElement("p");
                p.textContent = "Other processing happening here";
                document.body.appendChild(p);
            };
            """;
    
        object ToDump()
        {
            Util.HtmlHead.AddScript(js);
            Util.RawHtml($"<p id=\"firstMsg\">Initial message</p>").Dump();
            Util.InvokeScript(needReturnValue: false, "doSomeProcessing");
            return string.Empty;
        }
    }
    

    [Just fyi and background, my use case here is to output a table row of characters, measure the table columns (hence needing access) and then add a series of svg markers to identify various run types and their indices. My hope is to dump out as a standalone table and or as an object in the standard LINQPad object tables.]

  • edited March 19

    It sounds like you need an OnLoad function on an arbitrary DOM element, which JavaScript doesn't support. You could try the hack documented here: https://stackoverflow.com/a/41685045

    Here's a simple example using LINQPad's controls. MyControl is a composite element that contains two controls - the target that you want to manipulate with JavaScript, and an Image control whose onerror function will act as a load trigger for the target:

    using LINQPad.Controls;
    
    void Main()
    {
        new MyControl().Dump();
    
        // This also works when the control is added later to the DOM:
        Thread.Sleep(1000);
        new MyControl().Dump();
    
        // ... and used inside something else:
        Enumerable.Range (0, 10).Select (e => new MyControl()).Dump();
    }
    
    class MyControl : Control
    {
        public MyControl()
        {
            var target = new Target();
            VisualTree.Add (target);
            VisualTree.Add (new LoadTrigger (target.HtmlElement.ID));
        }
    
        class Target : Control   // This is the element you wish to customize
        {
            public Target() : base ("p")
            {
                this.HtmlElement.InnerText = "test";
            }
        }
    
        class LoadTrigger : Image
        {
            public LoadTrigger (string targetID)
            {
                this.Src = "";
                Styles ["display"] = "none";
                HtmlElement ["onerror"] = $"document.getElementById('{targetID}').style.color = 'red'";
            }
        }
    }
    
  • That's great. Thanks very much Joe. I don't think I've looked at Controls enough. I've read the Controls tutorials section, but have you done any talks/videos on their implementation? Would love to watch / listening to the thinking behind them if that exists.

    Anyway, thank you again. This solves my issue and in a cleaner way than I was attempting.

    Best regards

    John

Sign In or Register to comment.