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

Add routing service #187

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ http = "0.1"
serde = "1"
serde_json = "1"
stdweb = "0.4"
url = "1.7.0"

[dev-dependencies]
serde_derive = "1"
Expand Down
7 changes: 7 additions & 0 deletions examples/routing/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "routing"
version = "0.1.0"
authors = ["Henry Zimmerman <zimhen7@gmail.com>"]

[dependencies]
yew = { path = "../.." }
63 changes: 63 additions & 0 deletions examples/routing/src/button.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use yew::prelude::*;

pub struct Button {
title: String,
onsignal: Option<Callback<()>>,
}

pub enum Msg {
Clicked,
}

#[derive(PartialEq, Clone)]
pub struct Props {
pub title: String,
pub onsignal: Option<Callback<()>>,
}

impl Default for Props {
fn default() -> Self {
Props {
title: "Send Signal".into(),
onsignal: None,
}
}
}

impl<CTX: 'static> Component<CTX> for Button {
type Msg = Msg;
type Properties = Props;

fn create(props: Self::Properties, _: &mut Env<CTX, Self>) -> Self {
Button {
title: props.title,
onsignal: props.onsignal,
}
}

fn update(&mut self, msg: Self::Msg, _: &mut Env<CTX, Self>) -> ShouldRender {
match msg {
Msg::Clicked => {
if let Some(ref mut callback) = self.onsignal {
callback.emit(());
}
}
}
false
}

fn change(&mut self, props: Self::Properties, _: &mut Env<CTX, Self>) -> ShouldRender {
self.title = props.title;
self.onsignal = props.onsignal;
true
}
}

impl<CTX: 'static> Renderable<CTX, Button> for Button {
fn view(&self) -> Html<CTX, Self> {
html! {
<button onclick=|_| Msg::Clicked,>{ &self.title }</button>
}
}
}

77 changes: 77 additions & 0 deletions examples/routing/src/forum_router.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@

use yew::prelude::*;
use Context;
use yew::services::route::RouteInfo;
use yew::services::route::RouteSection;
use yew::services::route::Router;

use button::Button;

use Model;
use Msg;
use Route as MainRoute;

// Oftentimes the route doesn't need to hold any state or react to any changes, so it doesn't need to be a component.
#[derive(Clone, Debug, PartialEq)]
pub enum Route {
CatForum,
DogForum,
ForumsList
}

// It can be seen that the operations for this could possibly be derived
impl Router for Route {
fn from_route(route: &mut RouteInfo) -> Option<Self> {
if let Some(RouteSection::Node{segment}) = route.next() {
match segment.as_str() {
"cat" => Some(Route::CatForum),
"dog" => Some(Route::DogForum),
_ => Some(Route::ForumsList) // If the route can't be resolved, return None to let the parent router know that it should redirect to a failed route.
}
} else {
Some(Route::ForumsList)
}
}
fn to_route(&self) -> RouteInfo {
match *self {
Route::CatForum => RouteInfo::parse("/cat").unwrap(), // TODO I would like to refactor this into a macro that will fail at compile time if the parse fails
Route::DogForum => RouteInfo::parse("/dog").unwrap(),
Route::ForumsList => RouteInfo::parse("/").unwrap()
}
}
}

// Renderable needs to have the generic signature of the parent component, in this case, Model.
impl Renderable<Context, Model> for Route {
fn view(&self) -> Html<Context, Model> {
match *self {
Route::CatForum => {
html! {
// Conceptually, these could also be components to which routing props can be passed
<>
{"I'm the forum for talking about cats"}
</>
}
}
Route::DogForum => {
html! {
<>
{"I'm the forum for talking about dogs"}
</>
}
}
Route::ForumsList => {
html!{
<div>
<div>
<Button: title="Dog forum", onsignal=|_| Msg::Navigate(MainRoute::Forums(Route::DogForum)) ,/>
</div>
<div>
<Button: title="Cat forum", onsignal=|_| Msg::Navigate(MainRoute::Forums(Route::CatForum)) ,/>
</div>
</div>
}
}
}
}
}
171 changes: 171 additions & 0 deletions examples/routing/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@

#[macro_use]
extern crate yew;

mod forum_router;
mod button;

use yew::prelude::*;
//use yew::html::Scope;
use yew::services::route::*;

use yew::html::Renderable;

use button::Button;

use yew::services::route::Router;

use forum_router::Route as ForumRoute;


pub struct Context {
routing: RouteService
}

struct Model {
route: Route
}

#[derive(Clone, Debug)]
enum Route {
Forums(ForumRoute),
PageNotFoundRoute
}


enum Msg {
Navigate(Route),
}

impl From<RouteResult> for Msg {
fn from( result: RouteResult) -> Self {
match result {
Ok(mut route_info) => {
Msg::Navigate(Route::from_route_main(&mut route_info))
}
Err(e) => {
eprintln!("Couldn't route: '{:?}'", e);
Msg::Navigate(Route::PageNotFoundRoute)
}
}
}
}


impl Router for Route {
// For the top level case, this _MUST_ return Some.
fn from_route(route: &mut RouteInfo) -> Option<Self> {
Some(Self::from_route_main(route))
}
fn to_route(&self) -> RouteInfo {
match *self {
// You can add RouteInfos together to combine paths in logical order.
// The fragment and query of the rhs take precedence over any fragment or query set by the lhs.
Route::Forums(ref forum_route)=> RouteInfo::parse("/forums").unwrap() + forum_route.to_route(),
Route::PageNotFoundRoute => RouteInfo::parse("/PageNotFound").unwrap(),
}
}
}

impl MainRouter for Route {
fn from_route_main(route: &mut RouteInfo) -> Self {
if let Some(RouteSection::Node{segment}) = route.next() {
match segment.as_str() {
"forums" => {
// If the child can't be resolved, redirect to the right page here.
if let Some(child) = ForumRoute::from_route(route) { // Pass the route info to the child for it to figure itself out.
Route::Forums(child)
} else {
Route::PageNotFoundRoute
}
},
_ => Route::PageNotFoundRoute
}
} else {
Route::PageNotFoundRoute
}
}
}


impl Component<Context> for Model {
type Msg = Msg;
type Properties = ();

fn create(_: Self::Properties, context: &mut Env<Context, Self>) -> Self {

let callback = context.send_back(|route_result: RouteResult| {
Msg::from(route_result)
});
// When the user presses the back or forward button, an event will file and cause the callback to fire
context.routing.register_router(callback);


let route: Route = Route::from_route_main(&mut context.routing.get_current_route_info());
context.routing.replace_url(route.clone()); // sets the url to be dependent on what the route_info was resolved to

Model {
route
}
}

fn update(&mut self, msg: Msg, context: &mut Env<Context, Self>) -> ShouldRender {
match msg {
Msg::Navigate(route) => {
println!("Main route: Navigating");
context.routing.set_route(route.clone());
self.route = route;
true
}
}
}
}

impl Renderable<Context, Model> for Model {
fn view(&self) -> Html<Context, Self> {
html! {
<div>
{"This could be some html that will be on every page, like a header."}
<Button: title="GoToForums", onsignal=|_| Msg::Navigate(Route::Forums(ForumRoute::ForumsList) ) ,/>
<div>
{self.route.view()}
</div>
</div>
}
}
}


impl Renderable<Context, Model> for Route {
fn view(&self) -> Html<Context, Model> {
match *self {
Route::Forums(ref forum_route) => {
html! {
<>
{forum_route.view()}
</>
}
}
Route::PageNotFoundRoute => {
html! {
<div>
{"Page not found"}
</div>
}
}
}
}
}


fn main() {
yew::initialize();
let context = Context {
routing: RouteService::new()
};
// We use `Scope` here for demonstration.
// You can also use `App` here too.
let app: App<Context, Model> = App::new(context);
app.mount_to_body();
yew::run_loop();
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@

#[macro_use]
extern crate failure;
extern crate url;
extern crate http;
extern crate serde;
extern crate serde_json;
Expand Down
1 change: 1 addition & 0 deletions src/services/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub mod interval;
pub mod storage;
pub mod timeout;
pub mod websocket;
pub mod route;

use std::time::Duration;

Expand Down
Loading