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

HtmlTextAreaElement and mogwai 0.5.0 #89

Closed
paulalesius opened this issue Nov 19, 2021 · 9 comments
Closed

HtmlTextAreaElement and mogwai 0.5.0 #89

paulalesius opened this issue Nov 19, 2021 · 9 comments

Comments

@paulalesius
Copy link

Hi, is it possible to send content to a <textarea> element somehow?

The <textarea> element requires a call to HtmlTextAreaElement::value() attribute but I haven't found a way to do this from the view or logic code.

I can't find a way to cast an element inside a builder! {} ViewBuilder to HtmlTextAreaElement to call the value() method, and I can't seem to send messages to it in any way that calls the value() method.

Note this is not the <textarea value= /> attribute, which you can send messages to fine, but it's a method in javascript on the element.

Some help with how to do this would be highly appreciated!

Thank you!

@paulalesius
Copy link
Author

So far, I've managed to acquire a HtmlTextAreaElement from post:build:

<textarea post:build=move |dom: &mut Dom| {
  match dom.inner_read() {
    Either::Left(rwguard) => {
      let area = rwguard.clone().dyn_into::<HtmlTextAreaElement>();
        loop {
          rxv.clone() // ??
       }
    }
    Either::Right(ssr) = {} } }>

But I can't find a way to poll a stream from there. And that's a read-write guard that I don't think that one should be holding on to indefinitely in a loop anyway.

@schell
Copy link
Owner

schell commented Nov 19, 2021

The way that I've been dealing with this is to use the post:build operation to send a clone of the Dom to my logic loop. That way I can hold on to the Dom (which has a clone of the HtmlTextAreaElement if compiled for wasm) for as long as I need:

fn view(tx: Sender<Dom>) -> ViewBuilder<Dom> {
    builder! {
        <textarea post:build=move |dom: &mut Dom| {
            tx.try_send(dom.clone()).unwrap();
        }></textarea>
    }
}

async fn logic(mut rx: Receiver<Dom>) {
    let text_area_dom = rx.next().await.unwrap();
    {
        if let Some(text_area) = text_area_dom.clone_as::<HtmlTextAreaElement>() {
            // ... do `web_sys` stuff with text_area
        }
    }
    
    loop { // ... loop as normal }
}

There's an entry in the mogwai cookbook about capturing parts of the view that may help. There are also a few examples of this in the examples dir.

Let me know what you think :)

@paulalesius
Copy link
Author

Was using a ::broadcast channel to send things to the logic, and a ::mpmc to send things to the view, after the examples in the cookbook.

Your example uses tx.try_send() from a ::mpmc channel.

So in the logic

183 | let mut sources = mogwai::futures::stream::select_all(vec![rx_broadcast.boxed(), rx_mpmc.boxed()]);
    |                                                                                          ^^^^^ `*mut u8` cannot be shared between threads safely

So I'm trying to refactor the component architecture I'm trying to establish, to only use ::mpmc to send things to the logic.

It's very tricky to figure out an architecture that is consistent to focus on the business logic.

@paulalesius
Copy link
Author

paulalesius commented Nov 20, 2021

I don't think it makes sense for everything to be asynchronous. The complexities involved in performing basic logic will have one fiddling with channels for days, and we haven't even begun on the business logic of the application.

Asynchronous programming should be a tool that you use to achieve something, instead of for interacting with the API which serves no purpose from what I can tell, but to prevent one from programming.

Given the asynchronous nature of 0.5.0 it looks like we should only use ::broadcast channels exclusively, so that we don't run into issues later.

But now I can't send a mogwai::prelude::Dom through a ::broadcast channel in WASM because its wasm_bindgen::JsValue isn't std::marker::Send.

I'm going back to refactoring again using ::mpmc channels, but the logic already has a loop reading ::broadcast messages, and blocking on .await. Where do you handle the ::mpmc messages?

let mut sources = mogwai::futures::stream::select_all(vec![rxl.boxed()]);
loop {
  match sources.next().await { }
}

@schell
Copy link
Owner

schell commented Nov 20, 2021

Hopefully I can address all your concerns.

It's very tricky to figure out an architecture that is consistent to focus on the business logic.

This can be true. Even if you're used to working with the various channels it's easy to get confused. This is something I'll be working on in the future. It would be easier if channel implementations' Sender was Sink as well as their Receiver being Stream, but most Senders are not Sink.

I don't think it makes sense for everything to be asynchronous.

I agree, though I think the logic loops should be asynchronous because you often have to await things like requests or delays, etc. It's much less error prone to await the changes you expect than it is to send_async back into the logic loop and pick stuff back up at a later point, which is what mogwai < 0.5 did.

Given the asynchronous nature of 0.5.0 it looks like we should only use ::broadcast channels exclusively, so that we don't run into issues later.

This is mostly correct. If you don't have a specific reason not to use a broadcast, then you probably should. It should be the channel you reach for first, because it guarantees that all downstream Receivers get a clone of the message. This is especially important when making ViewBuilders because we often give the builder a stream in the form oftx_view.clone().filter_map(...) - and if there are more than one of those sites and you're using the mpmc channel they will receive messages in round robin. For this reason I'll probably remove the mpmc channel in favor of futures::channel::mpsc (which is Sink).

But now I can't send a mogwai::prelude::Dom through a ::broadcast channel in WASM because its wasm_bindgen::JsValue isn't std::marker::Send.

You are correct that JsValue is !Send, but Dom is Sendable, which means it is Send when the target is not wasm32. Mogwai goes to a lot of trouble to make sure that on WASM the constraints are relaxed so you can pass around JsValue inside a Dom regardless of what target you're compiling for.

The reason to use mpmc instead of broadcast is because ViewBuilder is not Clone, which is a requirement for broadcast. You should be fine sending your Dom nodes through broadcast because clones are cheap. Then in your logic loop keep them as Dom until you need the underlying JsCast type (HtmlElement, etc) - at which point you can use visit_as or clone_as. Just make sure not to keep the JsCast type around across an await point or else your future will no longer be Send.

Thanks for exposing the confusion in channels. I'll have to do something to fix that. I'll probably drop async_channel altogether (which is the mpmc channel) in favor of using futures::mpsc.

@paulalesius
Copy link
Author

And with regards to send_async and safety, all of my code that interacts with javascript is kept in a separate mod, and that still requires:

wasm_bindgen_futures::spawn_local(async move {
});

Because they use JsValue objects from Javascript that are not "Send", so you cannot JsFuture::from(promise_with_jsvalue_objects).await from mogwai with_logic() loop.

So again all of my code is async everywhere for seemingly no purpose, but all interaction with javascript requires wasm_bindgen_futures::spawm_local.

@schell
Copy link
Owner

schell commented Nov 20, 2021

I see the confusion. Currently each time you call with_logic the Component replaces its logic with the given future. So your first future is replaced with your second future and subsequently dropped, including rxn, closing the channel.

It does make sense that you would think it appends the logic though, and the implementation could do that, if desired. I suppose that would be preferable to using types to ensure that this confusion never happens (which would mean Component::with_logic returns a new type that that does not have a with_logic method). I like that, actually - because then you can split the updates of different parts of the same view between separate futures using different messages as you've done here. I will probably change to this behavior as a result of this conversation.

I understand your frustration with the new changes. Trying to anticipate some that frustration I included the IsElmComponent trait, which works almost exactly the same as 0.4's Component trait. I decided to move away from the Elm style composition because there are already good alternatives like sauron there. mogwai is geared a bit more towards flexibility and speed, I guess. But with IsElmComponent you get your cake and can eat most of it too.

Given some more time I'd like to rewrite your example to help you out, but I won't be able to until tomorrow morning (NZ time).

@paulalesius
Copy link
Author

Thank you, closing issue.

I understand how to use it now, deleted my whining posts, my bad. Great framework! ♥️

@schell
Copy link
Owner

schell commented Nov 20, 2021

Thanks for the issue - it has illuminated some of the areas of mogwai that I haven't thought enough about. 🙇

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

No branches or pull requests

2 participants