diff --git a/Cargo.toml b/Cargo.toml index 19889ce55..6fbe11318 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,9 @@ members = [ "yewtil/examples/futures", "yewtil/examples/function_component", + # dsl + "yew-dsl", + # Examples "examples/counter", "examples/crm", diff --git a/yew-dsl/Cargo.toml b/yew-dsl/Cargo.toml new file mode 100644 index 000000000..303aae679 --- /dev/null +++ b/yew-dsl/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "yew-dsl" +version = "0.1.0" +authors = ["Teymour Aldridge "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +yew = {path="../yew"} \ No newline at end of file diff --git a/yew-dsl/src/lib.rs b/yew-dsl/src/lib.rs new file mode 100644 index 000000000..5e24f792f --- /dev/null +++ b/yew-dsl/src/lib.rs @@ -0,0 +1,69 @@ +//! yew_dsl provides an Rust-based syntax for creating DOM elements. +//! It provides five basic functions with which you should be able to create complex layouts +//! (these are `tag`, `comp`, `text`, `populated_list` and `list`). + +pub use crate::vcomp::VCompProducer; +use crate::vlist::VListProducer; +pub use crate::vtag::VTagProducer; +pub use crate::vtext::VTextProducer; +use yew::virtual_dom::VNode; +use yew::Component; + +mod vcomp; +mod vlist; +mod vtag; +mod vtext; + +use std::cell::RefCell; +use std::rc::Rc; +use yew::html::Scope; + +/// A `ScopeHolder` contains a reference to the scope of the parent component. +type ScopeHolder = Rc>>>; + +/// `BoxedVNodeProducer` is a wrapper around a function which produces a `VNode`. +pub struct BoxedVNodeProducer(Box) -> VNode>); + +impl BoxedVNodeProducer { + fn wrap(f: impl FnOnce(ScopeHolder) -> VNode + 'static) -> Self { + BoxedVNodeProducer(Box::new(f)) + } + fn execute(self, scope: &ScopeHolder) -> VNode { + (self.0)(scope.clone()) + } + pub fn build(self) -> VNode { + let scope = ScopeHolder::default(); + self.execute(&scope) + } +} + +impl Into for BoxedVNodeProducer { + fn into(self) -> VNode { + self.build() + } +} + +/// Creates HTML tags (e.g. 'span', 'div', etc). +pub fn tag(tag: &'static str) -> VTagProducer { + VTagProducer::new(tag) +} + +/// Creates child components. +pub fn comp(props: CHILD::Properties) -> VCompProducer { + VCompProducer::new::(props) +} + +/// Creates text nodes. +pub fn text + 'static>(text: TEXT) -> VTextProducer { + VTextProducer::new::(text) +} + +/// Creates new lists populatated with the data supplied to the function. +pub fn populated_list(list: Vec>) -> VListProducer { + VListProducer::populated_new(list) +} + +/// Creates new (empty) lists. +pub fn list() -> VListProducer { + VListProducer::new() +} diff --git a/yewtil/src/dsl/vcomp.rs b/yew-dsl/src/vcomp.rs similarity index 51% rename from yewtil/src/dsl/vcomp.rs rename to yew-dsl/src/vcomp.rs index e324b2df1..2224975ec 100644 --- a/yewtil/src/dsl/vcomp.rs +++ b/yew-dsl/src/vcomp.rs @@ -1,14 +1,19 @@ -use crate::dsl::BoxedVNodeProducer; -use yew::virtual_dom::vcomp::ScopeHolder; +use crate::BoxedVNodeProducer; use yew::virtual_dom::VComp; use yew::{Component, NodeRef}; -pub struct VCompProducer(Box) -> VComp>); +use crate::ScopeHolder; + +/// `VCompProducer` returns instances of virtual components. It implements the `From` trait +/// for `BoxedVNodeProducer` through which it can be used to return virtual nodes. +pub struct VCompProducer(Box) -> VComp>); impl VCompProducer { pub fn new(props: CHILD::Properties) -> Self { - // TODO allow getting the noderef as a parameter somewhere. - VCompProducer(Box::new(move |scope| VComp::new::(props, scope, NodeRef::default()))) + // TODO: allow getting the NodeRef as a parameter somewhere. + VCompProducer(Box::new(move |_| { + VComp::new::(props, NodeRef::default(), None) + })) } } diff --git a/yewtil/src/dsl/vlist.rs b/yew-dsl/src/vlist.rs similarity index 76% rename from yewtil/src/dsl/vlist.rs rename to yew-dsl/src/vlist.rs index d551a50f7..d753ba9d2 100644 --- a/yewtil/src/dsl/vlist.rs +++ b/yew-dsl/src/vlist.rs @@ -1,14 +1,21 @@ -use crate::dsl::BoxedVNodeProducer; +use crate::BoxedVNodeProducer; use yew::virtual_dom::VList; use yew::Component; +/// pub struct VListProducer { children: Vec>, } +impl Default for VListProducer { + fn default() -> Self { + VListProducer:: { children: vec![] } + } +} + impl VListProducer { pub fn new() -> Self { - VListProducer { children: vec![] } + VListProducer:: { children: vec![] } } pub fn child>>(mut self, child: T) -> Self { @@ -17,7 +24,7 @@ impl VListProducer { } pub fn populated_new(children: Vec>) -> Self { - VListProducer { children } + VListProducer:: { children } } } diff --git a/yewtil/src/dsl/vtag.rs b/yew-dsl/src/vtag.rs similarity index 68% rename from yewtil/src/dsl/vtag.rs rename to yew-dsl/src/vtag.rs index 086747109..fb3f0d937 100644 --- a/yewtil/src/dsl/vtag.rs +++ b/yew-dsl/src/vtag.rs @@ -1,31 +1,33 @@ -use crate::dsl::BoxedVNodeProducer; -use yew::virtual_dom::vcomp::ScopeHolder; +use crate::BoxedVNodeProducer; +use crate::ScopeHolder; use yew::virtual_dom::{Listener, VTag}; use yew::{Classes, Component}; pub struct Effect(Box) -> T>); + impl Effect { fn new(f: impl FnOnce(T, &ScopeHolder) -> T + 'static) -> Self { - Effect(Box::new(f)) + Effect::(Box::new(f)) } } pub struct VTagProducer { tag_type: &'static str, - effects: Vec, COMP>>, + effects: Vec>, } impl VTagProducer { pub fn new(tag_type: &'static str) -> Self { - VTagProducer { + VTagProducer:: { tag_type, effects: vec![], } } - // TODO, consider making this T: Into - The whole dsl doesn't need to be lazy. - although being generic over an additional argument that is either () OR Scope is problematic. + // TODO, consider making this T: Into - The whole dsl doesn't need to be lazy. + // - although being generic over an additional argument that is either () OR Scope is problematic. pub fn child> + 'static>(mut self, child: T) -> Self { - let effect = Effect::new(move |mut vtag: VTag, scope: &ScopeHolder| { + let effect = Effect::new(move |mut vtag: VTag, scope: &ScopeHolder| { let child = child.into().execute(scope); vtag.add_child(child); vtag @@ -35,7 +37,7 @@ impl VTagProducer { } pub fn attribute(mut self, name: String, value: String) -> Self { - let effect = Effect::new(move |mut vtag: VTag, _scope: &ScopeHolder| { + let effect = Effect::new(move |mut vtag: VTag, _scope: &ScopeHolder| { vtag.add_attribute(&name, &value); vtag }); @@ -43,8 +45,8 @@ impl VTagProducer { self } - pub fn listener(mut self, listener: Box>) -> Self { - let effect = Effect::new(move |mut vtag: VTag, _scope: &ScopeHolder| { + pub fn listener(mut self, listener: std::rc::Rc) -> Self { + let effect = Effect::new(move |mut vtag: VTag, _scope: &ScopeHolder| { vtag.add_listener(listener); vtag }); @@ -53,8 +55,8 @@ impl VTagProducer { } pub fn classes(mut self, classes: Classes) -> Self { - let effect = Effect::new(move |mut vtag: VTag, _scope: &ScopeHolder| { - vtag.set_classes(classes); + let effect = Effect::new(move |mut vtag: VTag, _scope: &ScopeHolder| { + vtag.add_attribute("class", &classes.to_string()); vtag }); self.effects.push(effect); diff --git a/yew-dsl/src/vtext.rs b/yew-dsl/src/vtext.rs new file mode 100644 index 000000000..5d6c0f9ac --- /dev/null +++ b/yew-dsl/src/vtext.rs @@ -0,0 +1,18 @@ +use crate::BoxedVNodeProducer; +use yew::virtual_dom::VText; +use yew::Component; + +/// A wrapper around a function which produces `VText` nodes. +pub struct VTextProducer(Box VText>); + +impl VTextProducer { + pub fn new + 'static>(text: TEXT) -> Self { + VTextProducer(Box::new(move || VText::new(text.into()))) + } +} + +impl From for BoxedVNodeProducer { + fn from(vtext_prod: VTextProducer) -> Self { + BoxedVNodeProducer::wrap(move |_scope| (vtext_prod.0)().into()) + } +} diff --git a/yewtil/src/dsl.rs b/yewtil/src/dsl.rs deleted file mode 100644 index b63c52b9b..000000000 --- a/yewtil/src/dsl.rs +++ /dev/null @@ -1,59 +0,0 @@ -pub use crate::dsl::vcomp::VCompProducer; -use crate::dsl::vlist::VListProducer; -pub use crate::dsl::vtag::VTagProducer; -pub use crate::dsl::vtext::VTextProducer; -use yew::virtual_dom::vcomp::ScopeHolder; -use yew::virtual_dom::VNode; -use yew::Component; - -mod vcomp; -mod vlist; -mod vtag; -mod vtext; - -/// Wrapper around a function that produces a vnode. -pub struct BoxedVNodeProducer(Box) -> VNode>); - -impl BoxedVNodeProducer { - fn wrap(f: impl FnOnce(ScopeHolder) -> VNode + 'static) -> Self { - BoxedVNodeProducer(Box::new(f)) - } - fn execute(self, scope: &ScopeHolder) -> VNode { - (self.0)(scope.clone()) - } - pub fn build(self) -> VNode { - let scope = ScopeHolder::default(); - self.execute(&scope) - } -} - -impl Into> for BoxedVNodeProducer { - fn into(self) -> VNode { - self.build() - } -} - -/// Creates a tag node. -pub fn tag(tag: &'static str) -> VTagProducer { - VTagProducer::new(tag) -} - -/// Creates a component (Specified by the second type parameter). -pub fn comp(props: CHILD::Properties) -> VCompProducer { - VCompProducer::new::(props) -} - -/// Creates a text node -pub fn text + 'static>(text: T) -> VTextProducer { - VTextProducer::new::(text) -} - -/// Creates a new vlist, populated with the provided vnodes -pub fn populated_list(list: Vec>) -> VListProducer { - VListProducer::populated_new(list) -} - -/// Creates a new vlist -pub fn list() -> VListProducer { - VListProducer::new() -} diff --git a/yewtil/src/dsl/vtext.rs b/yewtil/src/dsl/vtext.rs deleted file mode 100644 index 71554628f..000000000 --- a/yewtil/src/dsl/vtext.rs +++ /dev/null @@ -1,17 +0,0 @@ -use crate::dsl::BoxedVNodeProducer; -use yew::virtual_dom::VText; -use yew::Component; - -pub struct VTextProducer(Box VText>); - -impl VTextProducer { - pub fn new + 'static>(text: T) -> Self { - VTextProducer(Box::new(move || VText::new(text.into()))) - } -} - -impl From> for BoxedVNodeProducer { - fn from(vtext_prod: VTextProducer) -> Self { - BoxedVNodeProducer::wrap(move |_scope| (vtext_prod.0)().into()) - } -}