koute / stdweb

A standard library for the client-side Web

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

DOMContentLoaded event?

mankinskin opened this issue · comments

What is the equivalent of the DOMContentLoaded IEvent in stdweb?

In my case I want to add an event listener to the window that should listen to this event:

let callback = |_: event::DOMContentLoaded| {
    ...
};
window().add_event_listener(callback);
commented

There isn't currently one. You can either:

  1. define it yourself in your app with stdweb-derive (just copy-paste one of the already defined events and modify; it's should be trivial),
  2. contribute one to stdweb,
  3. or set the event through the js! macro.

I will try to implement it in a fork and will issue a pull request :) Thank you for your advice

I added one to src/webapi/events/dom.rs by just copying another event and modified it accordingly, then pub used it in src/lib.rs, I can use it in my app, but the callback is still not being called... this is how the event is defined now:

#[derive(Clone, Debug, PartialEq, Eq, ReferenceType)]
#[reference(instance_of = "Event")]
#[reference(event = "DOMContentLoaded")]
#[reference(subclass_of(Event))]
pub struct DOMContentLoaded( Reference );

impl IEvent for DOMContentLoaded {}
commented

If you add the event listener after DOMContentLoaded has already fired, then it won't fire.

So if you want a robust detection for when the page is loaded, use this:

fn wait_for_ready<F>(done: F) where F: FnOnce() {
    let ready_state: String = js!( return document.readyState; ).try_into().unwrap();

    if ready_state == "loading" {
        let done = Some(done);

        window().add_event_listener(move |_: DOMContentLoaded| {
            let done = done.take().unwrap();
            done();
        });

    } else {
        done();
    }
}

@Pauan Thanks a lot, I have found this solution aswell, but it seems that I have a different problem. I am basically just trying to get a canvas by its id, but even when document.readyState == "complete", the element is still not found (getElementByID returns null)
I am using yew to generate the HTML DOM, but this should not influence when the canvas is being loaded in relation to when the document.readyState is set to "complete", right?

fn view(&self) -> yew::Html<Self> {
    html!{
        <div>
            <h1>{"Hello World"}</h1>
            <p>{"My first paragraph!!!"}</p>
            <script>{
                js!{
                    var draw = function() {
                        alert("draw!");
                        var c = document.getElementById("MyCanvas");
                        if (c == null) {
                            alert("cannot find MyCanvas");
                        } else {
                            alert("drawing..");
                            var ctx = c.getContext("2d");
                            ctx.moveTo(0, 0);
                            ctx.lineTo(200, 200);
                            ctx.stroke();
                        }
                    };
                    if (document.readyState == "loading") {
                        window.addEventListener("DOMContentLoaded", draw);
                    } else {
                        draw();
                    }
                };
                ""
            }</script>
            <canvas id="MyCanvas" width=200 height=200 style="border:1px solid #000000"></canvas>
            <button onclick=|_| Msg::Click>{ "Click" }</button>
        </div>
    }
}
commented

I am using yew to generate the HTML DOM, but this should not influence when the canvas is being loaded in relation to when the document.readyState is set to "complete", right?

Your <script> tag is before the <canvas>, so it will run before the <canvas> is inserted into the DOM. So you need to move the <script> tag to be after the <canvas>.

Your <script> tag is before the <canvas>, so it will run before the <canvas> is inserted into the DOM. So you need to move the <script> tag to be after the <canvas>.

No, that does not help either.. but that should not even change anything, since the DOMContentLoaded event should be emitted after the whole DOM has been parsed. I don't even understand how it can be emitted before the handler has been registered, because that registration has to happen while loading the DOM, right?

I will see if I can try my example in pure javascript somehow (am a noob though) and see if this is even supposed to work.

commented

but that should not even change anything, since the DOMContentLoaded event should be emitted after the whole DOM has been parsed.

That's not how it works. This is the process the browser uses:

  1. It parses the .html file and reads the DOM nodes from there.

  2. It runs any <script> tags which exist in the .html file.

  3. It triggers the DOMContentLoaded event.

Note the key part is "in the .html file". Any DOM nodes or JavaScript which is added dynamically does not have any effect on the DOMContentLoaded.

DOMContentLoaded only cares about DOM nodes in the .html file, nothing else.

When you use Yew, you are not adding DOM nodes to the .html file, you're creating them dynamically, so that means DOMContentLoaded has already triggered before Yew has added any DOM nodes.

Also, Yew has full control over when and how it inserts the DOM nodes. It's entirely possible for Yew to wait until after DOMContentLoaded before inserting the DOM nodes, or wait until requestAnimationFrame, or wait for an arbitrary 10 seconds, etc.

So you can't rely upon a browser feature like DOMContentLoaded, instead you need a way for Yew to notify you after it's inserted the DOM nodes, since only Yew knows when that is.

Also, I don't think you should be embedding <script> tags into Yew anyways. For your problem, there is an issue filed about adding in canvas support, though there is a workaround:

Create an Init message for your component, and in the update function do all of your canvas stuff:

pub enum Msg {
    Init,
}

impl Component for Model {
    type Message = Msg;
    type Properties = ();

    fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
        ...
    }

    fn update(&mut self, msg: Self::Message) -> ShouldRender {
        match msg {
            Msg::Init => {
                let canvas: CanvasElement = document()
                    .get_element_by_id("MyCanvas")
                    .unwrap()
                    .try_into()
                    .unwrap();

                let context: CanvasRenderingContext2d = canvas.get_context().unwrap();

                ...

                false
            }
        }
    }
}

impl Renderable<Model> for Model {
    fn view(&self) -> Html<Self> {
        html!{
            <div>
                <h1>{"Hello World"}</h1>
                <p>{"My first paragraph!!!"}</p>
                <canvas id="MyCanvas" width=200 height=200 style="border:1px solid #000000"></canvas>
                <button onclick=|_| Msg::Click>{ "Click" }</button>
            </div>
        }
    }
}

Then when you mount the component, send it the Init message:

App::<Model>::new()
    .mount(mount_point)
    .send_message(Msg::Init);

This will cause Yew to render the DOM nodes into the DOM, and then afterwards trigger the Init message, which then causes it to render the canvas.

This guarantees that no matter what Yew does, your canvas code will run after the DOM nodes are in the DOM.

@Pauan Thank you so much, this led me on the right track. I have not written javascript before and it is a really rich and complex stack to learn.

commented

I have not written javascript before and it is a really rich and complex stack to learn.

That is certainly true! Even experts struggle with it.