Coder Social home page Coder Social logo

yew_router's People

Contributors

carlosdp avatar dependabot-preview[bot] avatar hgzimmerman avatar jetli avatar jfbilodeau avatar jplatte avatar tarkah avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

yew_router's Issues

Derive routing abilities on enums

Based on saschagrunert/yew-router#1


The current nested routing solution has its place, but due to constraints of Yew, it has its drawbacks - namely that it can't support callbacks. Instead, keeping a Route Agent attached to a component could allow a RouteInfo acquired from the agent to create an enum variant, which can then be matched on.

Proposed code could look like:

// 'Switch' because there are already enough things named 'Router'
#[derive(Switch)] 
enum AppRoute {
    #[at = "/"] 
    Home,
    #[at = "/feed"] // route with no members
    Feed,
    #[at = "/profile/{user_id}"]
    Profile { user_id: i64 }, // members matched to path segments by name
    #[at = "/foo/{name}/{id}"]
    Foo { name: String, id: i64 }, // multiple members matched to path segments by name
    #[at = "/post/{id}"]
    Post(i64), // unnamed members also work
    #[at = "/bar/{name}/{id}"]
    Bar(String, i64), // even multiple unnamed members - an IndexMap can be used in these cases to preserve order.

    #[at = "/settings"] // sub-routes - Should these be absolute, or relative? Possibly another macro attribute?
    Settings(SettingsRoute),
}

and

fn view(&self) -> Html<Self> {
    match AppRoute::switch(self.route_info) {
        Some(AppRoute::Home) => html!{},
        Some(AppRoute::Profile{user_id)}=> html!{}
        None => html!{"404"}
        _ => panic!("I'm not implementing every one of these in an example")
    }
}

Switch trait definition could look like:

pub trait Switch {
    fn switch<T>(route_info: RouteInfo<T>)  -> Option<Self>; 
}

Edit: maybe Routable as a possible name.

Implement RouterLink and RouterButton components.

While having the Router agent will always be necessary for some tasks, it would be nice to be able to avoid having to attach it to the Component when all the component does is handle button or link clicks.

Having components that wrap <a> and <button> tags that will send messages to the router when clicked would be a huge ergonomics boost.

Macro grammar

So a macro will be needed to write implementations for converting a string into a function that extracts fields for a props struct.

So, setting expectations:

  • Target props must be a populated Struct (eg: pub struct {}).

Grammar

  • / - segment delimiter. Matches will not be allowed across multiple delimiters under normal circumstances
  • * - Match anything for a given segment`.
  • ** - Match anything for any number of segments`.
  • :ident - For a given segment, capture its contents and store them in the props. The ident must match a field in the props. No duplicate idents allowed.
  • ::ident - capture the contents across multiple segments and store them (as a vector of strings?) in the props. No duplicate idents.
  • ident - match this "ident" exactly or reject routing.
  • # - Begin of hash/fragment, must come last. Same rules apply, sans the double-match operators.
  • ? - Begin of query. This will have additional grammar rules. Namely, the pattern for ?ident=:ident or ?ident=ident.

Problems

  • I foresee deduction of which Props to create as being impossible without a hint within the macro itself.
    So the macro may need to look like route!(componentA::Properties, "articles/:number"
  • It isn't immediately obvious how to change types to those demanded by the Props. This solution should support any type in the Props as long as it implements TryFrom<String>.

Solutions

  • The macro can produce, instead of a function, a simple AST, which a derive macro on each prop can create a function that will consume the AST and produce the prop.

So the process for matching is as follows:

  • Try to match every AST until a match is found.
  • Maybe, matching an AST returns an Option<HashMap<String, String>> (Or btree?)
  • Then see if props can be deduced from the matched HashMap<String,String>.
  • If that fails, go back to matching the remaining ASTs.

End goal

Ideally it should look like after a first pass:

<Router
    routes = vec![
    Route<ComponentA>::new(route!("/articles/:number"))
]
/>

And as a final ideal, final state:

<Router
    <Route<ComponentA>: route = route!("/articles/:number"), exact=true />
/>

Resources

https://cprimozic.net/blog/writing-a-hashmap-to-struct-procedural-macro-in-rust/

Improve FromMatches

Use some intermediary trait with a blanket impl to effectively implement FromStr for Option<T: FromStr> and Result<T: FromStr, E>.

I feel like the logic should be that if the value for the key exists, but can't be parsed, then return None, but if the value does not exist for the key, then store None/Err for the field.

Add prelude

The prelude should just be the output of the alias macro and maybe a couple of traits.

Yew 0.10.0 tracking issue.

Yew 0.10.0 is anticipated to cause a few breaking changes that will require updates to this crate.
This issue will track those changes.

  • Remove Transferable
  • Renderable blanket impl for T: Component

A next branch should be created to keep up with breaking changes in Yew. At least for the 0.10.0 release.

Interface design brainstorm

The current implementation works, but is rather inflexible.
It would be nice to be able to allow the component to specify its render target via nested children instead of the route!() macro. Currently though, I believe that this would mean giving up capturing sections of the url-route.


I see that there is a checkbox for the following feature in the PR for nested components: Investigate passing required properties from (P) -> (C).

I don't have a full understanding of what this means, but if would allow for something like:

<Parent>
    <ArbitraryChild prop=parent.hashmap["key"] />
</Parent>

Then this would allow the contents of a Route to access the captures parts of the url.

Simplify router options using macros

instead of:

vec![
RouterOption::new::<AModel, _>(|route| a_component::Props::try_from(route).ok()),
...
]

Something like:

vec![
route!(AModel, |route| a_component::Props::try_from(route).ok() ),
...
]

Or more aspirational:

routes![
AModel => |route| a_component::Props::try_from(route).ok(),
...
]

Or as an end goal once flexible RouteOption backends are implemented:

routes![
AModel => {"/a/*", case_insensitive},
...
]

Change routing condition

  • Get rid of will_try_to_route().
  • Replace the functionality that is currently mediated via that function with emit_error_if_no_routes_match flag in the yew_router's props, which defaults to true.

This way, it is much easier to implement something as routable, while still allowing the yew_router to display nothing even if it is under another router capable of displaying a routing error.

Component that injects an is_active value into its children

Ideally, a wrapper component could exist that for children which implement a specific trait, it could set a property indicating if they are active or not depending on the current route string.

This wrapper would act similarly to the Router that currently exists, but instead of choosing a specific route, it would call a function to set the is_active prop for any of its children that match the route.

This feature would allow the creation of navbars with buttons that indicate which page is currently active.


The trait would have two functions, one to expose a mutable reference to an Item (Associated type?) representing the is_active value, and another one to expose a reference to the matcher. Components would have to implement this trait in order to be children of this ActiveWrapper.

Add another type parameter to the YewRouterBase

This other type parameter will control the scoping of the messages sent between the PageNotFoundRouters and SimpleRouters.

By allowing the specification of other message types for the routers, routers with different types can't send messages to each other. This allows there to be a default router at the top level that won't receive error messages from other routers at lower levels; meaning that you can have multiple PageNotFoundRouters where only the lower one displays anything.

Consider removing RouteString

RouteString, currently residing at https://github.com/yewstack/yew_router/blob/master/src/route_info.rs#L21, is a compatibility enum used to provide access to individual parts of a route (path/query/fragment). The library currently doesn't make use of having access to individual sections of a route string, and sometimes having the ability to do so is confusing and possibly misleading.

It should be removed and instead, if needed, be replaced with a parser that can extract the path, query and fragment from a plain string. The only downside to this would be including another library to do the parsing or a general parsing library would be required to always be included in order to hand-roll a parser to do this.

Join efforts with saschagrunert/yew-router

Hey ๐Ÿ‘‹,

@jstarry reached me out via mail and asked if we want to join our efforts to keep the yew ecosystem homogeneous. I would suggest that we join the repositories into one and use the GitHub CODEOWNERS to collaborate effectively. We also have some ideas about future improvements in mind. WDYT?

cc @Boscop

Determine better naming convensions

We have a RoutingService, a Router, a YewRouter. I don't know if that is acceptable, but it is a little confusing at first glance. Maybe rename the Router to RouterAgent? YewRouter to RouterComponent?

Then there is the confusion around RESOLVER and tune_props_from_route(). I think that these should use the same naming scheme. I also don't like either of these names. A better word is needed to represent optionally creating an entity from another depending on user-defined rules.

Find a way to pass properties to a component through a YewRouter

When using <YewRouter: routes=routes![ComponentA, ComponentB],> I would like to be able to forward state from the parent component through to ComponentA and/or ComponentB. As far as I understand, the current option is to store information via the History API, i.e. implement the RouteState trait and then construct the component's properties from the Route in resolve_props.

Is there a more idiomatic way to do this, such that I could pass properties to routable components as properties of the YewRouter component? e.g. something along the lines of <YewRouter: routes=routes![ComponentA, ComponentB], properties=..., />?

Bug: YewRouter never Dropped

Yew currently doesn't drop its components when the are no longer needed.
Because of this, YewRouters sit around after another route change has invalidated them.
When the route changes again, these phantom YewRouters instantiate their respective child, but because the YewRouter isn't attached to the DOM, the child isn't either.

This child is created, and may make network requests.
If too many children are created at once due to this problem, the browser won't be able to make all the required requests at once, and will queue some of them until existing requests finish.
This is particularly bad for the responsiveness of the site.

The solution is to have Yew call drop() on the componetns, so the bridges are dropped as well, causing the links to the RouterAgent to be disconnected.
This change will be made in yewstack/yew#288, but the router system will have strange behavior like this until a PR related to the issue is merged.

Remove RouteInfo and associated infrastructure.

RouteInfo is overengineered. There isn't a need to keep all portions of the route separate.

Its replacement should have String, T as its fields, and ideally &strs should be usable in cases where T = (). so setting paths could have no syntax overhead.

Add optional sections

Allow ( ) to appear in the path matcher string. any section captured by that becomes optional when the matcher parser is built.

Simple Routing Syntax

Mime the react router syntax of /thing/:capture(choice|choice).

This might be possible to do using the existing MatcherTokens.

Simplify names.

I have stuff like RouteBase and Route as an alias to it.
I don't like this. Instead, it should be switched to Route and SimpleRoute = Route<()>.

Add limited choices to route-parser syntax

Currently, the route parser uses () to encode optional sections.
This Issue proposes changing optional sections to use [] instead.
As part of this change () would be freed up to be used in capture sections to limit what strings are able to be successfully captured.
Valid syntax would be as follows: /path/{(this|that)} and /path/{name(this|that)} and /path/{*:name(this|that)}

Add options to relax or make more strict the parsing rules.

Permissive -> will not error on the parser pass. This can be used to grab routes for props that have Options in FromMatches, so all captures don't need to be present.
I'm not super keen on this one. I want to brainstorm on if there is a better way to represent optional query and fragment sections.

Strict -> Won't handle trailing '/' characters.

CaseInsensitive -> Don't care about case when matching.

Add documentation

  • Make note of the need to have routed components start with <div> instead of <>, as they won't be cleaned up properly when re-routing if you use fragments.

Allow non-route components to nest within Router.

Someone might want to define their matcher somewhere other than in the Router body itself.

This should enable code like

<Router>
    <AComponent />
    <BComponent />
    <Route matcher=route!(), render=component::<CComponent>() />
</Router>

If a component's props implement FromMatches and implement a new trait that allows them to get a matcher for them, then it should be possible to just nest components within a Router.

A possible concequence of this would be that the router itself isn't rendering items anymore, but is choosing a single child to delegate rendering to. This would mean that Renderable would have to be implemented on Route.

Simplify types

The new router doesn't use the blanket traits that make definitions more terse. It should be switched to that.

Add an external test crate

Macros can't be tested from the main crate due to ::crate-name vs ::crate name mismatches. Creating a separate crate to put macro related tests into would be better than stuffing them in doc comments to get around this restriction at the moment.

Implement a PageNotFoundYewRouter

A PageNotFoundYewRouter component would display the 404 page the normal YewRouter does currently. The difference here would be the use of the directional message bus described here to forward a message to the PageNotFoundYewRouter when a YewRouter couldn't resolve a route to any of its components.

This would facilitate error handling where the top level router could route to a specific page when any other router fails to resolve its children.

By using a standard YewRouter as the top level router, you can still specify custom 404 handlers for any depth of the route hierarchy, by placing PageNotFoundYewRouter below it.

Consider removing or reworking the nested router component.

With the introduction of the Enum-based routing derive macro, the appeal of the Router component seems to be diminished.

Using the Router component currently represents a trade-off: you don't have to touch the RouteAgent, and in exchange, you can't use callbacks inside of it.

Currently a Switched enum requires that you have a proper update loop in place for the router agent, but allows callbacks without having to go through two component's worth of indirection.

As a proposal, I think it makes sense to combine them, taking the positive attributes from both. Resulting "html" could look like:

html! {
    <Router<State, Msg, RouteEnum> 
        callback = From::from, 
        render = Router::render(|route: Option<RouteEnum>| {
            match route {
                Some(RouteEnum::Variant) => html!{...}
                ...
            }
        })
    />
}

The router component could keep track of the agent and the event loop, while you still get to specify the match block for your route enum.

Create an "unstable" feature

Users can opt into the unstable feature in order to gain access to items and modules that may not be fully baked yet.
A good candidate for this would be the RouteInjector component, because that has usability issues at the moment that might require changes.

Rename Crate

yew-router is taken, invalidating yew_router.
Alternatives:

  • yewstack-router
  • yew-routing

Allow any component with a reference to the router to send a routing failed message

This routing failed message would be picked up by Yew routers with the 404 role, and the default route would be displayed.

It would be a novel idea if components didn't need to keep the router around for this. Conceptually, the router never needs to be attached to the model if all it does is send things, as an instance could quickly be created and destroyed.

Code like this should be possible:

if response.did_404() {
    TemporaryRouter::send(self.link, RouterRequest::RoutingFailed);
}

Add type parameters back to Router

As part of getting the initial router implementation, I stripped away type parameters from the Router to get it to compile with a slightly defective yew build that had nested-children features. This was fixed quickly and I should reinstate the ability of Routers to take type parameters.


On a related note, I think the name of the Router should remain Router, but a type alias should be reexported at the crate root of type Router = Router<()> because storing state along with the route should be a rare use case - users can still import the base impl.

Add CI

CI can be added after a 0.1 release.

Also, I probably need access to this project's settings tab, which I lost when I migrated it to yewstack.

TODO -> ask jstarry if TravisCi would be good for homogenity's sake with the main crate, or if trying out github actions would be a worthwhile endeavor.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.