├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── Readme.md ├── crates └── yewtil-macro │ ├── Cargo.toml │ └── src │ ├── function_component.rs │ └── lib.rs ├── examples ├── dsl │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── effect │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── fetch │ ├── .cargo │ │ └── config │ ├── Cargo.toml │ ├── Readme.md │ ├── index.html │ ├── main.js │ └── src │ │ └── lib.rs ├── function_component │ ├── Cargo.toml │ └── src │ │ ├── button.rs │ │ └── main.rs ├── futures │ ├── Cargo.toml │ ├── README.md │ ├── index.html │ ├── main.js │ └── src │ │ └── lib.rs ├── history │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── lrc │ ├── Cargo.toml │ └── src │ │ ├── child.rs │ │ └── main.rs ├── mrc_irc │ ├── Cargo.toml │ └── src │ │ ├── child.rs │ │ └── main.rs └── pure_component │ ├── Cargo.toml │ └── src │ ├── button.rs │ └── main.rs └── src ├── dsl.rs ├── dsl ├── vcomp.rs ├── vlist.rs ├── vtag.rs └── vtext.rs ├── effect.rs ├── fetch.rs ├── fetch ├── action.rs ├── error.rs ├── request.rs └── state.rs ├── future.rs ├── history.rs ├── lib.rs ├── not_equal_assign.rs ├── ptr ├── irc.rs ├── lrc.rs ├── mod.rs ├── mrc.rs ├── rc_box.rs └── takeable.rs └── pure.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | .idea 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 15 | 16 | ## ✨ **v0.2.0** *11/18/19* 17 | - #### ⚡️ Features 18 | - Add new `FetchRequest` trait, `fetch_resource()` function, and `FetchState` enum 19 | to simplify making fetch requests using futures. 20 | - Add `Default` implementations to `Irc` and `Mrc`. 21 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "yewtil" 3 | version = "0.2.0" 4 | authors = ["Henry Zimmerman "] 5 | edition = "2018" 6 | description = "Utility crate for Yew" 7 | license = "MIT/Apache-2.0" 8 | repository = "https://github.com/yewstack/yewtil" 9 | readme = "Readme.md" 10 | 11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 12 | 13 | [features] 14 | # Only stable is included by default. 15 | default = ["stable"] 16 | # Broad features 17 | all = ["stable", "experimental"] 18 | ## All features MUST be stable or experimental 19 | stable = ["neq", "pure", "history", "mrc_irc", "effect", "future"] 20 | experimental = ["dsl", "lrc", "with_callback", "fetch" ] 21 | 22 | 23 | # Some pointers are stable, some experimental. 24 | # This makes sure you get all the pointers 25 | ptr = ["lrc", "mrc_irc"] 26 | 27 | # Misc features 28 | neq = [] 29 | pure = ["neq", "yewtil-macro"] 30 | with_callback = [] 31 | history = [] 32 | dsl = [] 33 | effect = [] 34 | fetch = ["serde", "serde_json", "neq", "future"] 35 | future = ["wasm-bindgen-futures", "wasm-bindgen", "stdweb", "futures", "web-sys"] 36 | 37 | # Ptr features 38 | lrc = [] 39 | mrc_irc = [] 40 | 41 | [dependencies] 42 | yew = { git = "https://github.com/yewstack/yew", branch="master" } 43 | #yew = "0.10.0" 44 | yewtil-macro = {path = "crates/yewtil-macro", version="0.1.0", optional=true} 45 | log = "0.4.8" 46 | 47 | 48 | wasm-bindgen-futures = {version = "0.4.3", optional = true} 49 | wasm-bindgen = {version = "0.2.51", features=["serde-serialize"], optional = true} 50 | futures = {version = "0.3.1", optional = true} 51 | serde = {version= "1.0.102", optional = true} 52 | serde_json = { version = "1.0.41", optional = true } 53 | stdweb = { version = "0.4.20", features = ["futures-support", "experimental_features_which_may_break_on_minor_version_bumps"], optional = true } 54 | 55 | [dependencies.web-sys] 56 | version = "0.3.31" 57 | optional = true 58 | features = [ 59 | 'Headers', 60 | 'Request', 61 | 'RequestInit', 62 | 'RequestMode', 63 | 'Response', 64 | 'Window', 65 | 'Location', 66 | 'Storage', 67 | ] 68 | 69 | 70 | [workspace] 71 | members = [ 72 | "crates/yewtil-macro", 73 | "examples/pure_component", 74 | "examples/dsl", 75 | "examples/lrc", 76 | "examples/history", 77 | "examples/mrc_irc", 78 | "examples/effect", 79 | "examples/fetch", 80 | "examples/futures", 81 | "examples/function_component" 82 | ] 83 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # This project has been moved 2 | This project has been incorporated into the [Yew](https://github.com/yewstack/yew) repository, and can specifically be found [here](https://github.com/yewstack/yew/tree/master/yewtil). 3 | You should address all questions or improvements there instead of here. 4 | This repository will be archived in the near future. 5 | 6 | # Yewtil 7 | Utility crate for the [Yew](https://github.com/yewstack/yew) frontend web framework. 8 | 9 | ## Purpose 10 | Provide a place for commonly used utilities for Yew to reside without them having to be included in the Yew crate itself. 11 | As a consequence of this, the Yew crate is free to make changes that may cause breakages in this crate. 12 | 13 | ## Features 14 | Currently, this crate supports these features in a stable capacity: 15 | * `NeqAssign` - makes assigning props and returning a relevant ShouldRender value easier. 16 | * Pure Components - implement pure components using the `PureComponent` trait and the `Pure` Component adaptor. 17 | This should make it much easier to define simple components that don't hold state. 18 | * Function components - a macro that takes a function that returns `Html` and converts it to a pure component. 19 | * `Mrc`/`Irc` smart pointers - Rc-like pointers that are more ergonomic to use within Yew. 20 | * `History` - A wrapper that holds the history of values that have been assigned to it. 21 | * `Effect` - A way to update component state by defining what to change inside of `html!` callbacks 22 | instead of handling messages in `Component::update()`. 23 | 24 | This crate also has an experimental feature flag that enables the following features: 25 | * `Lrc` smart pointer - Rc-like pointer implemented on top of a linked list. Allows for novel state update mechanics 26 | and traversal over linked shared pointers. (This needs to be fuzz tested to make sure it doesn't leak.) 27 | * DSL for `Html` - A function-based domain-specific-language for Yew that can be used in a limited capacity instead of the `html!` macro. (Broken by recent changes in yew. Will be rewritten from scratch eventually.) 28 | 29 | These experimental features are either not sufficiently vetted, may change significantly, or may be removed. 30 | 31 | ## Example Projects 32 | Examples for every stable feature exist [here](https://github.com/hgzimmerman/yewtil/tree/master/examples). 33 | 34 | Check out the [Pure Components example](https://github.com/hgzimmerman/yewtil/tree/master/examples/demo) to see how Pure Components work. 35 | 36 | ## Example 37 | #### neq_assign: 38 | ```rust 39 | fn change(&mut self, props: Self::Properties) -> ShouldRender { 40 | self.props.neq_assign(props) 41 | } 42 | ``` 43 | 44 | ------------- 45 | 46 | #### Pure Component: 47 | ```rust 48 | pub type Button = Pure; 49 | 50 | #[derive(PartialEq, Properties)] 51 | pub struct PureButton { 52 | #[props(required)] 53 | pub callback: Callback, 54 | pub text: String, 55 | } 56 | 57 | impl PureComponent for PureButton { 58 | fn render(&self) -> VNode { 59 | html! { 60 | 61 | } 62 | } 63 | } 64 | ``` 65 | 66 | -------------- 67 | 68 | #### History 69 | ```rust 70 | pub struct Model { 71 | text: History, 72 | } 73 | 74 | // ... 75 | fn update(&mut self, msg: Self::Message) -> ShouldRender { 76 | match msg { 77 | Msg::SetText(text) => self.text.neq_set(text), 78 | Msg::Reset => self.text.reset(), 79 | Msg::Forget => { 80 | self.text.forget(); 81 | false 82 | } 83 | } 84 | } 85 | ``` 86 | 87 | ## Update Schedule 88 | This crate will target stable Yew. 89 | 90 | As new idioms are introduced to Yew, this crate may see updates, but given the rarity of those, this crate may sit unaltered for some time. 91 | 92 | ## Scoping 93 | This crate aims to be more permissive in what is allowed in than Yew, so if you have a function, type, or trait you would like to share, please open a PR or Issue. 94 | 95 | Components are welcome as well, but they must not have external dependencies, should solve some problem encountered my many users of Yew, and should allow for theming if possible, like an auto-scrolling wrapper, a RecyclerView/Infinite-scrolling component, or possibly a comprehensive Input component. 96 | 97 | Common UI elements like modals or dropdowns should probably best be left to CSS-framework component libraries, as they should often be coupled to the external CSS used to display them. 98 | 99 | ### Stability 100 | Since this crate aims to present a variety of helper types, traits, and functions, where the utility of each may be unknown at the time the feature is added, newer additions may be not be included in the default feature-set, and may be locked behind an `experimental` flag. 101 | While in early development, features marked as `experimental` may be changed more frequently or even entirely removed, while those marked as `stable` will not be removed and can be depended on to not change significantly. 102 | -------------------------------------------------------------------------------- /crates/yewtil-macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "yewtil-macro" 3 | version = "0.1.0" 4 | authors = ["Henry Zimmerman "] 5 | edition = "2018" 6 | license = "MIT/Apache-2.0" 7 | description = "Macros to be re-exported from the yewtil crate" 8 | 9 | [lib] 10 | proc-macro = true 11 | 12 | [dependencies] 13 | syn = "1.0.11" 14 | quote = "1.0.2" 15 | proc-macro2 = "1.0.6" 16 | -------------------------------------------------------------------------------- /crates/yewtil-macro/src/function_component.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{TokenStream, Ident, Span}; 2 | use proc_macro::TokenStream as TokenStream1; 3 | use syn::{Visibility, Error, Field, Stmt, Block, VisPublic, Type}; 4 | use syn::Token; 5 | use syn::token; 6 | use syn::punctuated::Punctuated; 7 | use syn::parse::{Parse, ParseBuffer}; 8 | use syn::{parenthesized, braced}; 9 | use syn::parse_macro_input; 10 | use syn::export::ToTokens; 11 | use quote::quote; 12 | 13 | pub fn function_component_handler(attr: TokenStream, item: TokenStream1) -> TokenStream1 { 14 | let component_name = attr.to_string(); 15 | assert!(!component_name.is_empty(), "you must provide a component name. eg: function_component(MyComponent)"); 16 | let component_name = Ident::new(&component_name, Span::call_site()); 17 | 18 | let item_copy = item.clone(); 19 | 20 | let function = parse_macro_input!(item_copy as Function); 21 | 22 | TokenStream1::from(FunctionComponentInfo { 23 | component_name, 24 | function 25 | }.to_token_stream()) 26 | } 27 | 28 | pub struct FunctionComponentInfo { 29 | component_name: Ident, 30 | function: Function 31 | } 32 | 33 | 34 | // TODO, support type parameters 35 | 36 | pub struct Function { 37 | pub vis: Visibility, 38 | pub fn_token: Token![fn], 39 | pub name: Ident, 40 | pub paren_token: token::Paren, 41 | pub fields: Punctuated, 42 | pub returns_token: Token![->], 43 | pub return_ty: Ident, 44 | pub brace_token: token::Brace, 45 | pub body: Vec 46 | } 47 | 48 | impl Parse for Function { 49 | fn parse(input: &ParseBuffer) -> Result { 50 | let content; 51 | let content2; 52 | Ok(Function { 53 | vis: input.parse()?, 54 | fn_token: input.parse()?, 55 | name: input.parse()?, 56 | paren_token: parenthesized!(content in input), 57 | fields: content.parse_terminated(Field::parse_named)?, 58 | returns_token: input.parse()?, 59 | return_ty: input.parse()?, 60 | brace_token: braced!(content2 in input), 61 | body: content2.call(Block::parse_within)? 62 | }) 63 | } 64 | } 65 | 66 | impl ToTokens for Function { 67 | fn to_tokens(&self, tokens: &mut TokenStream) { 68 | let Function { 69 | vis, fn_token, name, fields, returns_token, return_ty, body, .. 70 | } = self; 71 | let fields = fields.iter() 72 | .map(|field: &Field| { 73 | let mut new_field: Field = field.clone(); 74 | new_field.attrs = vec![]; 75 | new_field 76 | }) 77 | .collect::>(); 78 | 79 | tokens.extend(quote! { 80 | #vis #fn_token #name(#fields) #returns_token #return_ty { 81 | #(#body)* 82 | } 83 | }) 84 | } 85 | } 86 | 87 | impl ToTokens for FunctionComponentInfo { 88 | fn to_tokens(&self, tokens: &mut TokenStream) { 89 | let FunctionComponentInfo { 90 | component_name, function 91 | } = self; 92 | // The function tokens must be re-generated in order to strip the attributes that are not allowed. 93 | let function_token_stream = function.to_token_stream(); 94 | let Function { 95 | vis, name, fields, .. 96 | } = function; 97 | 98 | let impl_name = format!("FuncComp{}", component_name.to_string()); 99 | let impl_name = Ident::new(&impl_name, Span::call_site()); 100 | 101 | let alias = quote! { 102 | #vis type #component_name = ::yewtil::Pure<#impl_name>; 103 | }; 104 | 105 | // Set the fields to be public and strips references as necessary. 106 | // This will preserve attributes like #[props(required)], which will appear in the generated struct below. 107 | let new_fields = fields.iter() 108 | .map(|field: &Field| { 109 | let mut new_field: Field = field.clone(); 110 | let visibility = Visibility::Public(VisPublic{ pub_token: syn::token::Pub {span: Span::call_site()} }); 111 | // Strip references so the component can have a static lifetime. 112 | // TODO Handle 'static lifetimes gracefully here - allowing &'static strings instead of erroneously converting them to plain strs. 113 | let ty = match &field.ty { 114 | Type::Reference(x) => { 115 | let elem = x.elem.clone(); 116 | Type::Verbatim(quote!{ 117 | #elem 118 | }) 119 | } 120 | x => x.clone() 121 | }; 122 | new_field.vis = visibility; 123 | new_field.ty = ty; 124 | new_field 125 | }) 126 | .collect::>(); 127 | 128 | 129 | let component_struct = quote!{ 130 | #[derive(::std::clone::Clone, ::std::cmp::PartialEq, ::yew::Properties)] 131 | #vis struct #impl_name { 132 | #new_fields 133 | } 134 | }; 135 | 136 | let arguments = fields.iter().zip(new_fields.iter()) 137 | .map(|(field, new_field): (&Field, &Field)| { 138 | let field_name = field.ident.as_ref().expect("Field must have name"); 139 | 140 | // If the fields differ, then a reference was removed from the function's field's type 141 | // to make it static. 142 | // Otherwise it is assumed that the type is not a reference on the function and it 143 | // implements clone, and that when calling the function, the type should be cloned again. 144 | if field.ty != new_field.ty { 145 | quote! { 146 | &self.#field_name 147 | } 148 | } else { 149 | quote! { 150 | self.#field_name.clone() 151 | } 152 | } 153 | }) 154 | .collect::>(); 155 | 156 | let pure_component_impl = quote! { 157 | impl ::yewtil::PureComponent for #impl_name { 158 | fn render(&self) -> ::yew::Html { 159 | #name(#arguments) 160 | } 161 | } 162 | }; 163 | 164 | 165 | tokens.extend(quote!{ 166 | #function_token_stream 167 | #alias 168 | #component_struct 169 | #pure_component_impl 170 | }) 171 | } 172 | } -------------------------------------------------------------------------------- /crates/yewtil-macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro; 2 | use proc_macro::TokenStream; 3 | 4 | 5 | use crate::function_component::function_component_handler; 6 | 7 | mod function_component; 8 | #[proc_macro_attribute] 9 | pub fn function_component(attr: TokenStream, item: TokenStream) -> TokenStream { 10 | function_component_handler(attr.into(), item.into()).into() 11 | } 12 | -------------------------------------------------------------------------------- /examples/dsl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dsl" 3 | version = "0.1.0" 4 | authors = ["Henry Zimmerman "] 5 | edition = "2018" 6 | license = "MIT/Apache-2.0" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | yew = { git = "https://github.com/yewstack/yew", branch="master" } 12 | yewtil = {path="../..", features=["dsl"]} 13 | web_logger = "0.2.0" 14 | log = "0.4.8" 15 | -------------------------------------------------------------------------------- /examples/dsl/src/main.rs: -------------------------------------------------------------------------------- 1 | use yew::{Component, ComponentLink, Html, ShouldRender}; 2 | 3 | use yewtil::dsl::{list, populated_list, tag, text, BoxedVNodeProducer}; 4 | 5 | pub struct Model {} 6 | 7 | pub enum Msg { 8 | DoIt, 9 | } 10 | 11 | impl Component for Model { 12 | type Message = Msg; 13 | type Properties = (); 14 | 15 | fn create(_: Self::Properties, _: ComponentLink) -> Self { 16 | Model {} 17 | } 18 | 19 | fn update(&mut self, msg: Self::Message) -> ShouldRender { 20 | match msg { 21 | Msg::DoIt => { 22 | log::info!("got message"); 23 | true 24 | } 25 | } 26 | } 27 | 28 | fn view(&self) -> Html { 29 | BoxedVNodeProducer::from( 30 | list() 31 | .child(text("Hello there")) 32 | .child(tag("p").child(text("Paragraph content"))) 33 | .child(populated_list(vec![ 34 | tag("b").child(text("Bolded")).into(), 35 | text("Normal text").into(), 36 | ])), 37 | ) 38 | .build() 39 | } 40 | } 41 | 42 | fn main() { 43 | web_logger::init(); 44 | yew::start_app::(); 45 | } 46 | -------------------------------------------------------------------------------- /examples/effect/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "effect" 3 | version = "0.1.0" 4 | authors = ["Henry Zimmerman "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | yew = { git = "https://github.com/yewstack/yew", branch="master" } 11 | yewtil = {path="../..", features=["effect"]} 12 | web_logger = "0.2.0" 13 | log = "0.4.8" 14 | -------------------------------------------------------------------------------- /examples/effect/src/main.rs: -------------------------------------------------------------------------------- 1 | use yew::{html, Component, ComponentLink, Html, ShouldRender}; 2 | use yewtil::{Effect, effect}; 3 | 4 | pub struct Model { 5 | value: bool 6 | } 7 | 8 | 9 | impl Component for Model { 10 | type Message = Effect; 11 | type Properties = (); 12 | 13 | fn create(_: Self::Properties, _: ComponentLink) -> Self { 14 | Model { 15 | value: false 16 | } 17 | } 18 | 19 | fn update(&mut self, msg: Self::Message) -> ShouldRender { 20 | msg.call(self) 21 | } 22 | 23 | fn view(&self) -> Html { 24 | html! { 25 | <> 26 |
27 | {self.value} 28 |
29 |
30 | 38 |
39 | 40 | } 41 | } 42 | } 43 | 44 | fn main() { 45 | web_logger::init(); 46 | yew::start_app::(); 47 | } -------------------------------------------------------------------------------- /examples/fetch/.cargo/config: -------------------------------------------------------------------------------- 1 | # Allow cargo-check to work as expected. 2 | [build] 3 | target = "wasm32-unknown-unknown" 4 | -------------------------------------------------------------------------------- /examples/fetch/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fetch" 3 | version = "0.1.0" 4 | authors = ["Henry Zimmerman "] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "rlib"] 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | yew = { git = "https://github.com/yewstack/yew", branch="master" } 13 | yewtil = {path="../..", features=["fetch"]} 14 | web_logger = "0.2.0" 15 | log = "0.4.8" 16 | serde = "1.0.102" 17 | wasm-bindgen = "0.2.51" 18 | -------------------------------------------------------------------------------- /examples/fetch/Readme.md: -------------------------------------------------------------------------------- 1 | Shows off ergonomic JSON deserialization fetch abstraction. 2 | 3 | Run with: 4 | 5 | ```shell script 6 | wasm-pack build --target web && rollup ./main.js --format iife --file ./pkg/bundle.js && python -m SimpleHTTPServer 8080 7 | ``` 8 | 9 | It is expected that you have a setup with wasm-pack, rollup, and python installed. 10 | -------------------------------------------------------------------------------- /examples/fetch/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Yewtil • Fetch 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /examples/fetch/main.js: -------------------------------------------------------------------------------- 1 | import init, { run_app } from './pkg/fetch.js'; 2 | async function main() { 3 | await init('./pkg/fetch_bg.wasm'); 4 | run_app(); 5 | } 6 | main() -------------------------------------------------------------------------------- /examples/fetch/src/lib.rs: -------------------------------------------------------------------------------- 1 | use crate::Msg::SetMarkdownFetchState; 2 | use yew::{html, Component, ComponentLink, Html, ShouldRender}; 3 | use yewtil::fetch::{FetchAction, FetchRequest, MethodBody, Json, Fetch, FetchState}; 4 | use serde::{Serialize, Deserialize}; 5 | use wasm_bindgen::prelude::*; 6 | use yewtil::future::ComponentLinkFuture; 7 | 8 | #[wasm_bindgen] 9 | pub fn run_app() { 10 | yew::start_app::(); 11 | } 12 | 13 | struct Model { 14 | markdown: Fetch>, 15 | link: ComponentLink, 16 | } 17 | 18 | #[derive(Default, Debug, Clone)] 19 | pub struct Request; 20 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] 21 | pub struct Employee { 22 | id: String, 23 | employee_name: String, 24 | employee_salary: String, 25 | employee_age: String, 26 | profile_image: String 27 | } 28 | 29 | impl FetchRequest for Request { 30 | type RequestBody = (); 31 | type ResponseBody = Vec; 32 | type Format = Json; 33 | 34 | fn url(&self) -> String { 35 | // Given that this is an external resource, this may fail sometime in the future. 36 | // Please report any regressions related to this. 37 | "http://dummy.restapiexample.com/api/v1/employees".to_string() 38 | } 39 | 40 | fn method(&self) -> MethodBody { 41 | MethodBody::Get 42 | } 43 | 44 | fn headers(&self) -> Vec<(String, String)> { 45 | vec![] 46 | } 47 | 48 | fn use_cors(&self) -> bool { 49 | true 50 | } 51 | } 52 | 53 | 54 | enum Msg { 55 | SetMarkdownFetchState(FetchAction>), 56 | GetMarkdown, 57 | } 58 | 59 | impl Component for Model { 60 | // Some details omitted. Explore the examples to see more. 61 | 62 | type Message = Msg; 63 | type Properties = (); 64 | 65 | fn create(_: Self::Properties, link: ComponentLink) -> Self { 66 | Model { 67 | markdown: Default::default(), 68 | link, 69 | } 70 | } 71 | 72 | fn update(&mut self, msg: Self::Message) -> ShouldRender { 73 | match msg { 74 | Msg::SetMarkdownFetchState(fetch_state) => { 75 | self.markdown.apply(fetch_state); 76 | true 77 | } 78 | Msg::GetMarkdown => { 79 | self.link.send_future(self.markdown.fetch(Msg::SetMarkdownFetchState)); 80 | self.link.send_message(SetMarkdownFetchState(FetchAction::Fetching)); 81 | false 82 | } 83 | } 84 | } 85 | 86 | fn view(&self) -> Html { 87 | match self.markdown.as_ref().state() { 88 | FetchState::NotFetching(_) => { 89 | html! {} 90 | } 91 | FetchState::Fetching(_) => html! {"Fetching"}, 92 | FetchState::Fetched(data) => data.iter().map(render_employee).collect(), 93 | FetchState::Failed(_, err) => html! {&err}, 94 | } 95 | } 96 | } 97 | 98 | fn render_employee(e: &Employee) -> Html { 99 | html! { 100 |
101 |
102 | {"Name: "} 103 | {&e.employee_name} 104 |
105 |
106 | {"Salary: "} 107 | {&e.employee_salary} 108 |
109 | 110 |
111 | {"Age: "} 112 | {&e.employee_age} 113 |
114 |
115 |
116 | } 117 | } 118 | 119 | 120 | -------------------------------------------------------------------------------- /examples/function_component/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "function_component" 3 | version = "0.1.0" 4 | authors = ["Henry Zimmerman "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | yew = { git = "https://github.com/yewstack/yew", branch="master" } 11 | yewtil = {path="../.."} 12 | web_logger = "0.2.0" 13 | log = "0.4.8" 14 | -------------------------------------------------------------------------------- /examples/function_component/src/button.rs: -------------------------------------------------------------------------------- 1 | use yew::{html, Callback, ClickEvent, Html}; 2 | use yewtil::{function_component}; 3 | 4 | #[function_component(Button)] 5 | pub fn button( 6 | #[props(required)] 7 | callback: &Callback, 8 | text: &String, 9 | _num: usize 10 | ) -> Html { 11 | html! { 12 | 13 | } 14 | } 15 | 16 | 17 | -------------------------------------------------------------------------------- /examples/function_component/src/main.rs: -------------------------------------------------------------------------------- 1 | 2 | use yew::{html, Component, ComponentLink, Html, ShouldRender}; 3 | 4 | mod button; 5 | use crate::button::Button; 6 | 7 | pub struct Model {link: ComponentLink} 8 | 9 | pub enum Msg { 10 | DoIt, 11 | } 12 | 13 | impl Component for Model { 14 | type Message = Msg; 15 | type Properties = (); 16 | 17 | fn create(_: Self::Properties, link: ComponentLink) -> Self { 18 | Model {link} 19 | } 20 | 21 | fn update(&mut self, msg: Self::Message) -> ShouldRender { 22 | match msg { 23 | Msg::DoIt => { 24 | log::info!("got message"); 25 | true 26 | } 27 | } 28 | } 29 | 30 | fn view(&self) -> Html { 31 | html! { 32 | 110 | }, 111 | FetchState::Fetching => html! {"Fetching"}, 112 | FetchState::Success(data) => html! {&data}, 113 | FetchState::Failed(err) => html! {&err}, 114 | } 115 | } 116 | } 117 | 118 | #[wasm_bindgen] 119 | pub fn run_app() { 120 | yew::start_app::(); 121 | } 122 | -------------------------------------------------------------------------------- /examples/history/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "history" 3 | version = "0.1.0" 4 | authors = ["Henry Zimmerman "] 5 | edition = "2018" 6 | license = "MIT/Apache-2.0" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | yew = { git = "https://github.com/yewstack/yew", branch="master" } 12 | yewtil = {path="../.."} 13 | web_logger = "0.2.0" 14 | log = "0.4.8" 15 | -------------------------------------------------------------------------------- /examples/history/src/main.rs: -------------------------------------------------------------------------------- 1 | use yew::{html, Component, ComponentLink, Html, ShouldRender}; 2 | use yewtil::History; 3 | 4 | pub struct Model { 5 | text: History, 6 | } 7 | 8 | pub enum Msg { 9 | SetText(String), 10 | Reset, 11 | Forget, 12 | } 13 | 14 | impl Component for Model { 15 | type Message = Msg; 16 | type Properties = (); 17 | 18 | fn create(_: Self::Properties, _: ComponentLink) -> Self { 19 | Model { 20 | text: History::new("".to_string()), 21 | } 22 | } 23 | 24 | fn update(&mut self, msg: Self::Message) -> ShouldRender { 25 | match msg { 26 | Msg::SetText(text) => self.text.neq_set(text), 27 | Msg::Reset => self.text.reset(), 28 | Msg::Forget => { 29 | self.text.forget(); 30 | false 31 | } 32 | } 33 | } 34 | 35 | fn view(&self) -> Html { 36 | html! { 37 | <> 38 |
39 | {&*self.text} 40 |
41 |
42 | 47 | 48 | 49 |
50 | 51 | } 52 | } 53 | } 54 | 55 | fn main() { 56 | web_logger::init(); 57 | yew::start_app::(); 58 | } 59 | -------------------------------------------------------------------------------- /examples/lrc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lrc" 3 | version = "0.1.0" 4 | authors = ["Henry Zimmerman "] 5 | edition = "2018" 6 | license = "MIT/Apache-2.0" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | yew = { git = "https://github.com/yewstack/yew", branch="master" } 12 | yewtil = {path="../..", features = ["lrc"]} 13 | web_logger = "0.2.0" 14 | log = "0.4.8" 15 | -------------------------------------------------------------------------------- /examples/lrc/src/child.rs: -------------------------------------------------------------------------------- 1 | use yew::{ 2 | events::InputData, html, Callback, Component, ComponentLink, Html, Properties, ShouldRender, 3 | }; 4 | use yewtil::ptr::Lrc; 5 | use yewtil::NeqAssign; 6 | 7 | #[derive(PartialEq, Properties)] 8 | pub struct Props { 9 | #[props(required)] 10 | pub text: Lrc, 11 | #[props(required)] 12 | pub callback: Callback<()>, 13 | } 14 | 15 | pub struct Child { 16 | props: Props, 17 | } 18 | 19 | pub enum Msg { 20 | UpdateText(InputData), 21 | SendCallback, 22 | } 23 | 24 | impl Component for Child { 25 | type Message = Msg; 26 | type Properties = Props; 27 | 28 | fn create(props: Self::Properties, _: ComponentLink) -> Self { 29 | Child { props } 30 | } 31 | 32 | fn update(&mut self, msg: Self::Message) -> ShouldRender { 33 | match msg { 34 | Msg::UpdateText(input) => { 35 | // Only update the Lrc if the new value is different. 36 | self.props.text.neq_set(input.value); 37 | true 38 | } 39 | Msg::SendCallback => { 40 | self.props.callback.emit(()); 41 | false 42 | } 43 | } 44 | } 45 | 46 | fn change(&mut self, props: Self::Properties) -> ShouldRender { 47 | self.props.neq_assign(props) 48 | } 49 | 50 | fn view(&self) -> Html { 51 | html! { 52 | <> 53 | 58 | 59 | 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /examples/lrc/src/main.rs: -------------------------------------------------------------------------------- 1 | use yew::{html, Component, ComponentLink, Html, ShouldRender}; 2 | use yewtil::ptr::Lrc; 3 | 4 | mod child; 5 | use crate::child::Child; 6 | 7 | pub struct Model { 8 | text: Lrc, 9 | } 10 | 11 | pub enum Msg { 12 | UpdateTextAtADistance, 13 | } 14 | 15 | impl Component for Model { 16 | type Message = Msg; 17 | type Properties = (); 18 | 19 | fn create(_: Self::Properties, _: ComponentLink) -> Self { 20 | Model { 21 | text: Lrc::new("".to_string()), 22 | } 23 | } 24 | 25 | fn update(&mut self, msg: Self::Message) -> ShouldRender { 26 | match msg { 27 | Msg::UpdateTextAtADistance => self.text.update(), 28 | } 29 | } 30 | 31 | fn view(&self) -> Html { 32 | html! { 33 | <> 34 |
35 | {&*self.text} 36 |
37 | // Either of the children's update buttons will cause this component's text 38 | // to update to the most recently edited text. 39 |
40 | 41 |
42 |
43 | 44 |
45 | 46 | } 47 | } 48 | } 49 | 50 | fn main() { 51 | web_logger::init(); 52 | yew::start_app::(); 53 | } 54 | -------------------------------------------------------------------------------- /examples/mrc_irc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mrc_irc" 3 | version = "0.1.0" 4 | authors = ["Henry Zimmerman "] 5 | edition = "2018" 6 | license = "MIT/Apache-2.0" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | yew = { git = "https://github.com/yewstack/yew", branch="master" } 12 | yewtil = {path="../.."} 13 | web_logger = "0.2.0" 14 | log = "0.4.8" 15 | -------------------------------------------------------------------------------- /examples/mrc_irc/src/child.rs: -------------------------------------------------------------------------------- 1 | use yew::{ 2 | events::InputData, html, Callback, Component, ComponentLink, Html, Properties, ShouldRender, 3 | }; 4 | use yewtil::ptr::Irc; 5 | use yewtil::NeqAssign; 6 | 7 | #[derive(PartialEq, Properties)] 8 | pub struct Props { 9 | /// This value can't be altered. 10 | #[props(required)] 11 | pub text: Irc, 12 | /// This heavily implies the only way to update the text field is to send a message back 13 | /// to the parent to have the parent component update it. 14 | #[props(required)] 15 | pub callback: Callback, 16 | } 17 | 18 | pub struct Child { 19 | props: Props, 20 | } 21 | 22 | pub enum Msg { 23 | UpdateText(InputData), 24 | } 25 | 26 | impl Component for Child { 27 | type Message = Msg; 28 | type Properties = Props; 29 | 30 | fn create(props: Self::Properties, _: ComponentLink) -> Self { 31 | Child { props } 32 | } 33 | 34 | fn update(&mut self, msg: Self::Message) -> ShouldRender { 35 | match msg { 36 | Msg::UpdateText(input) => { 37 | self.props.callback.emit(input.value); 38 | false 39 | } 40 | } 41 | } 42 | 43 | fn change(&mut self, props: Self::Properties) -> ShouldRender { 44 | self.props.neq_assign(props) 45 | } 46 | 47 | fn view(&self) -> Html { 48 | html! { 49 | <> 50 | 55 | 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /examples/mrc_irc/src/main.rs: -------------------------------------------------------------------------------- 1 | use yew::{html, Component, ComponentLink, Html, ShouldRender}; 2 | use yewtil::ptr::Mrc; 3 | 4 | mod child; 5 | use crate::child::Child; 6 | use yewtil::NeqAssign; 7 | 8 | pub struct Model { 9 | text: Mrc, 10 | } 11 | 12 | pub enum Msg { 13 | UpdateText(String), 14 | } 15 | 16 | impl Component for Model { 17 | type Message = Msg; 18 | type Properties = (); 19 | 20 | fn create(_: Self::Properties, _: ComponentLink) -> Self { 21 | Model { 22 | text: Mrc::new("".to_string()), 23 | } 24 | } 25 | 26 | fn update(&mut self, msg: Self::Message) -> ShouldRender { 27 | match msg { 28 | Msg::UpdateText(text) => { 29 | // Because Mrc implements BorrowMut, neq assign can be used here. 30 | self.text.neq_assign(text) 31 | } 32 | } 33 | } 34 | 35 | fn view(&self) -> Html { 36 | html! { 37 | <> 38 |
39 | {&*self.text} 40 |
41 |
42 | // By passing an `Irc`, we strongly imply that the value should not be updated 43 | // by the child. An effort to modify the value downstream is easily identified 44 | // as subverting the contract implied by using `Irc`s. 45 | 46 |
47 | 48 | } 49 | } 50 | } 51 | 52 | fn main() { 53 | web_logger::init(); 54 | yew::start_app::(); 55 | } 56 | -------------------------------------------------------------------------------- /examples/pure_component/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pure_component" 3 | version = "0.1.0" 4 | authors = ["Henry Zimmerman "] 5 | edition = "2018" 6 | license = "MIT/Apache-2.0" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | #yew = "0.9.0" 12 | yew = { git = "https://github.com/yewstack/yew", branch="master" } 13 | yewtil = {path="../.."} 14 | web_logger = "0.2.0" 15 | log = "0.4.8" 16 | -------------------------------------------------------------------------------- /examples/pure_component/src/button.rs: -------------------------------------------------------------------------------- 1 | use yew::virtual_dom::VNode; 2 | use yew::{html, Callback, Properties, ClickEvent}; 3 | use yewtil::{Pure, PureComponent}; 4 | 5 | /// Alias to make usability better. 6 | pub type Button = Pure; 7 | 8 | #[derive(PartialEq, Properties)] 9 | pub struct PureButton { 10 | #[props(required)] 11 | pub callback: Callback, 12 | pub text: String, 13 | } 14 | 15 | impl PureComponent for PureButton { 16 | fn render(&self) -> VNode { 17 | html! { 18 | 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/pure_component/src/main.rs: -------------------------------------------------------------------------------- 1 | use yew::{html, Component, ComponentLink, Html, ShouldRender}; 2 | 3 | mod button; 4 | use crate::button::Button; 5 | 6 | pub struct Model {link: ComponentLink} 7 | 8 | pub enum Msg { 9 | DoIt, 10 | } 11 | 12 | impl Component for Model { 13 | type Message = Msg; 14 | type Properties = (); 15 | 16 | fn create(_: Self::Properties, link: ComponentLink) -> Self { 17 | Model {link} 18 | } 19 | 20 | fn update(&mut self, msg: Self::Message) -> ShouldRender { 21 | match msg { 22 | Msg::DoIt => { 23 | log::info!("got message"); 24 | true 25 | } 26 | } 27 | } 28 | 29 | fn view(&self) -> Html { 30 | html! { 31 |