Pauan / rust-dominator

Zero-cost ultra-high-performance declarative DOM library using FRP signals for Rust!
MIT License
967 stars 62 forks source link

Routing #4

Closed davidhewitt closed 6 years ago

davidhewitt commented 6 years ago

Sorry I have so many questions! (Can you tell I'm excited to see where dominator goes 😄)

I was playing around with the concept of routing using a signal containing an enum. I was hoping that I could map that signal to a Signal<Item=Vec<Dom>>, which would essentially swap out the page contents on navigation. I put in some top-level app state too to make things hard for myself.

The idea was that this might pave the way for building myself a little Router struct or trait.

Sample of code for a simple todo app with a login route is below; I hit a couple of stumbling blocks and wondered how you would change what I've attempted to work around them:

  1. signal_vec::unsync::Reciever recieves self by value for Map. It's also not Clone. Thus connecting the signal_vec into Dom is one-shot only. I agree this is correct, but it gets in the way of what I want to do here where it needs to potentially be bound and dropped multiple times as the user navigates 😢
  2. Dynamic<Signal<Item=Vec<Dom>> used to impl Children, but I that impl had to be killed as it conflicted with the signal_vec impl. Possibly not much can be done about that until specialisation comes to Rust?

enum Route {
  Login,
  Todos
};

fn todolist_app() -> Dom {

    let (sender_route, receiver_route) = signal::unsync::mutable(Route::Login);

    let (sender_todos, receiver_todos) = signal_vec::unsync::mutable::<String>();
    let sender_todos = Rc::new(RefCell::new(sender_todos));
    let receiver_todos = Rc::new(receiver_todos);

    html! {
        "div", {
            children(
              receiver_route.map(move |route| match route {
                    Route::Login => vec![
                      // this is just my stopgap view-only component with a callback while we figure out better
                      login_page(clone!(
                          {router, sender_todos}
                          move || {                             // callback if login is successful
                              router.navigate(Route::Todos);
                              sender_todos.borrow_mut().push("First Todo");
                          }
                      )) 
                    ],

                    Route::Todos => vec! [
                        html!("h1", { children(&mut [text("Notebook")]); }),
                        html!("div", {
                            children(receiver_todos.map(|todo| html!("p", { text(todo); })).dynamic());
                                     // (problem 1)
                        })  
                    ]
                })
            ]);
        }
    }
}
Pauan commented 6 years ago

I had removed the Children for Signal<...> implementation since (as you said) it conflicts with SignalVec, and SignalVec is strictly more general (and faster in many cases).

I have plans to add in broadcasting for SignalVec.

Routing is rather tricky at the moment, since it's still missing a lot of functions (e.g. the ability to have a SignalVec which depends upon the value of a Signal

As a potential solution, I can make a to_signal_vec method which lets you convert a Signal into a SignalVec. It won't be very efficient, but it will solve the immediate problem.

Pauan commented 6 years ago

Okay, I added in a to_signal_vec method.

As for your example, I think it's weird to use the same state for all the routes: generally each page will have state which is specific to that page.

In the specific case of state which needs to be "sent" between routes, I think that should be part of the routing system, and it probably shouldn't use Signals either.

davidhewitt commented 6 years ago

Okay, I added in a to_signal_vec method.

Amazing, thanks!

In the specific case of state which needs to be "sent" between routes, I think that should be part of the routing system, and it probably shouldn't use Signals either.

Hmm so in this example above I made the state shared and global because I considered adding a third page which would be a detail page for a single todo. I agree it's quite coupled to the routing; I'll keep exploring designs...

davidhewitt commented 6 years ago

I think I'm going to close this as a) my terribly inefficient experiment now works with to_signal_vec() and b) if dominator gets routing, it might be nice to provide it as an external crate. Thanks.

Pauan commented 6 years ago

I feel like routing is important enough that I wouldn't mind it being a part of this crate.

I view this crate as being a general DOM app solution, not just a thin layer on top of stdweb.

Pauan commented 5 years ago

Just a heads up that I added in a new routing module. It's currently very bare-bones, but it provides the low-level functionality which is needed for routing.

To be more specific, it provides 4 functions:

All route changes should be done with go_to_url, on_click_go_to_url, or link, since that prevents it from reloading the page (i.e. it is a Single Page Application).

External links (heading outside of your app) should not use go_to_url, on_click_go_to_url, or link; instead they should use a normal <a href="http://external-site.com">

The routing system is pretty simple, but it's at least good enough for the TodoMVC example (which has been updated to use the routing system).

Let me know what you think, and if you have any ideas for a higher-level routing system (built on top of this low-level system).