diff --git a/.vscode/settings.json b/.vscode/settings.json index c7ce540f..a3b8a0b7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,4 @@ { - "rust-analyzer.inlayHints.enable": true, + "rust-analyzer.inlayHints.enable": false, "rust-analyzer.cargo.allFeatures": true } diff --git a/Cargo.toml b/Cargo.toml index 3689966f..abd76ec8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ dioxus-mobile = { path = "./packages/mobile", optional = true } [features] # core -default = ["core", "ssr"] +default = ["core", "ssr", "web"] core = ["macro", "hooks", "html"] macro = ["dioxus-core-macro"] hooks = ["dioxus-hooks"] diff --git a/examples/crm.rs b/examples/crm.rs new file mode 100644 index 00000000..9426ff41 --- /dev/null +++ b/examples/crm.rs @@ -0,0 +1,77 @@ +/* +Tiny CRM: A port of the Yew CRM example to Dioxus. +*/ +use dioxus::prelude::*; + +fn main() { + dioxus::web::launch(App, |c| c); +} + +enum Scene { + ClientsList, + NewClientForm, + Settings, +} + +#[derive(Clone, Debug, Default)] +pub struct Client { + pub first_name: String, + pub last_name: String, + pub description: String, +} + +static App: FC<()> = |cx, _| { + let scene = use_state(cx, || Scene::ClientsList); + let clients = use_ref(cx, || vec![] as Vec); + + let firstname = use_state(cx, || String::new()); + let lastname = use_state(cx, || String::new()); + let description = use_state(cx, || String::new()); + + match *scene { + Scene::ClientsList => { + rsx!(cx, div { class: "crm" + h1 { "List of clients" } + div { class: "clients" {clients.read().iter().map(|client| rsx!( + div { class: "client" style: "margin-bottom: 50px" + p { "First Name: {client.first_name}" } + p { "Last Name: {client.last_name}" } + p {"Description: {client.description}"} + }))} + } + button { onclick: move |_| scene.set(Scene::NewClientForm), "Add New" } + button { onclick: move |_| scene.set(Scene::Settings), "Settings" } + }) + } + Scene::NewClientForm => { + rsx!(cx, div { class: "crm" + h1 {"Add new client"} + div { class: "names" + input { class: "new-client firstname" placeholder: "First name" + onchange: move |e| firstname.set(e.value()) + } + input { class: "new-client lastname" placeholder: "Last name" + onchange: move |e| lastname.set(e.value()) + } + textarea { class: "new-client description" placeholder: "Description" + onchange: move |e| description.set(e.value()) + } + } + button { disabled: "false", onclick: move |_| clients.write().push(Client { + description: (*description).clone(), + first_name: (*firstname).clone(), + last_name: (*lastname).clone(), + + }), "Add New" } + button { onclick: move |_| scene.set(Scene::ClientsList), "Go Back" } + }) + } + Scene::Settings => { + rsx!(cx, div { + h1 {"Settings"} + button { onclick: move |_| clients.write().clear() "Remove all clients" } + button { onclick: move |_| scene.set(Scene::ClientsList), "Go Back" } + }) + } + } +}; diff --git a/packages/html/src/lib.rs b/packages/html/src/lib.rs index 294ce326..71496900 100644 --- a/packages/html/src/lib.rs +++ b/packages/html/src/lib.rs @@ -778,6 +778,7 @@ builder_constructors! { sizes: String, // FIXME title: String, // FIXME r#type: Mime, + integrity: String, }; /// Build a @@ -1715,6 +1716,12 @@ impl option { } } +impl textarea { + pub fn value<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> { + cx.attr("value", val, None, true) + } +} + pub trait SvgAttributes { aria_trait_methods! { accent_height: "accent-height", diff --git a/packages/web/examples/crm2.rs b/packages/web/examples/crm2.rs new file mode 100644 index 00000000..77224fab --- /dev/null +++ b/packages/web/examples/crm2.rs @@ -0,0 +1,115 @@ +/* +Tiny CRM: A port of the Yew CRM example to Dioxus. +*/ + +use dioxus_core as dioxus; +use dioxus_core::prelude::*; +use dioxus_hooks::*; +use dioxus_html as dioxus_elements; + +fn main() { + // Setup logging + wasm_logger::init(wasm_logger::Config::new(log::Level::Debug)); + console_error_panic_hook::set_once(); + + // Run the app + static WrappedApp: FC<()> = |cx, _| { + rsx!(cx, body { + link { + rel: "stylesheet" + href: "https://unpkg.com/purecss@2.0.6/build/pure-min.css" + integrity: "sha384-Uu6IeWbM+gzNVXJcM9XV3SohHtmWE+3VGi496jvgX1jyvDTXfdK+rfZc8C1Aehk5" + crossorigin: "anonymous" + } + margin_left: "35%" + h1 {"Dioxus CRM Example"} + App {} + }) + }; + dioxus_web::launch(WrappedApp, |c| c) +} + +enum Scene { + ClientsList, + NewClientForm, + Settings, +} + +#[derive(Clone, Debug, Default)] +pub struct Client { + pub first_name: String, + pub last_name: String, + pub description: String, +} + +static App: FC<()> = |cx, _| { + let scene = use_state(cx, || Scene::ClientsList); + let clients = use_ref(cx, || vec![] as Vec); + + let firstname = use_state(cx, || String::new()); + let lastname = use_state(cx, || String::new()); + let description = use_state(cx, || String::new()); + + match *scene { + Scene::ClientsList => { + rsx!(cx, div { class: "crm" + h2 { "List of clients" margin_bottom: "10px" } + div { class: "clients" margin_left: "10px" + {clients.read().iter().map(|client| rsx!( + div { class: "client" style: "margin-bottom: 50px" + p { "First Name: {client.first_name}" } + p { "Last Name: {client.last_name}" } + p {"Description: {client.description}"} + }) + )} + } + button { class: "pure-button pure-button-primary" onclick: move |_| scene.set(Scene::NewClientForm), "Add New" } + button { class: "pure-button" onclick: move |_| scene.set(Scene::Settings), "Settings" } + }) + } + Scene::NewClientForm => { + let add_new = move |_| { + clients.write().push(Client { + description: (*description).clone(), + first_name: (*firstname).clone(), + last_name: (*lastname).clone(), + }); + description.set(String::new()); + firstname.set(String::new()); + lastname.set(String::new()); + }; + rsx!(cx, div { class: "crm" + h2 {"Add new client" margin_bottom: "10px" } + form { class: "pure-form" + input { class: "new-client firstname" placeholder: "First name" value: "{firstname}" + oninput: move |e| firstname.set(e.value()) + } + input { class: "new-client lastname" placeholder: "Last name" value: "{lastname}" + oninput: move |e| lastname.set(e.value()) + } + textarea { class: "new-client description" placeholder: "Description" value: "{description}" + oninput: move |e| description.set(e.value()) + } + } + button { class: "pure-button pure-button-primary", onclick: {add_new}, "Add New" } + button { class: "pure-button", onclick: move |_| scene.set(Scene::ClientsList), "Go Back" } + }) + } + Scene::Settings => { + rsx!(cx, div { + h2 {"Settings" margin_bottom: "10px" } + button { + background: "rgb(202, 60, 60)" + class: "pure-button pure-button-primary" + onclick: move |_| clients.write().clear(), + "Remove all clients" + } + button { + class: "pure-button pure-button-primary" + onclick: move |_| scene.set(Scene::ClientsList), + "Go Back" + } + }) + } + } +}; diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index c022a586..0fa6ec39 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -10,7 +10,7 @@ use fxhash::FxHashMap; use wasm_bindgen::{closure::Closure, JsCast, JsValue}; use web_sys::{ window, Attr, CssStyleDeclaration, Document, Element, Event, HtmlElement, HtmlInputElement, - HtmlOptionElement, Node, NodeList, UiEvent, + HtmlOptionElement, HtmlTextAreaElement, Node, NodeList, UiEvent, }; use crate::{nodeslab::NodeSlab, WebConfig}; @@ -302,38 +302,52 @@ impl WebsysDom { fn set_attribute(&mut self, name: &str, value: &str, ns: Option<&str>) { let node = self.stack.top(); - if let Some(el) = node.dyn_ref::() { - match ns { - // inline style support - Some("style") => { - let el = el.dyn_ref::().unwrap(); - let style_dc: CssStyleDeclaration = el.style(); - style_dc.set_property(name, value).unwrap(); + if ns == Some("style") { + if let Some(el) = node.dyn_ref::() { + let el = el.dyn_ref::().unwrap(); + let style_dc: CssStyleDeclaration = el.style(); + style_dc.set_property(name, value).unwrap(); + } + } else { + let fallback = || { + let el = node.dyn_ref::().unwrap(); + el.set_attribute(name, value).unwrap() + }; + match name { + "value" => { + if let Some(input) = node.dyn_ref::() { + /* + if the attribute being set is the same as the value of the input, then don't bother setting it. + This is used in controlled components to keep the cursor in the right spot. + + this logic should be moved into the virtualdom since we have the notion of "volatile" + */ + if input.value() != value { + input.set_value(value); + } + } else if let Some(node) = node.dyn_ref::() { + if name == "value" { + node.set_value(value); + } + } else { + fallback(); + } } - _ => el.set_attribute(name, value).unwrap(), - } - } - - if let Some(input) = node.dyn_ref::() { - if name == "value" { - /* - if the attribute being set is the same as the value of the input, then don't bother setting it. - This is used in controlled components to keep the cursor in the right spot. - - this logic should be moved into the virtualdom since we have the notion of "volatile" - */ - if input.value() != value { - input.set_value(value); + "checked" => { + if let Some(input) = node.dyn_ref::() { + input.set_checked(true); + } else { + fallback(); + } } - } - if name == "checked" { - input.set_checked(true); - } - } - - if let Some(node) = node.dyn_ref::() { - if name == "selected" { - node.set_selected(true); + "selected" => { + if let Some(node) = node.dyn_ref::() { + node.set_selected(true); + } else { + fallback(); + } + } + _ => fallback(), } } }