mirror of https://github.com/yewstack/yew
Separate out `yewtil::dsl` into `yew-dsl` (#1199)
* Separated out `yewtil::dsl` into `yew-dsl` * Reformat code. * Remove an unused import. * Added some rudimentary documentation. * Reformat code. * Added default impl for vlist. * Reformat code.
This commit is contained in:
parent
7a3f6dbc7c
commit
f8c6615a77
|
@ -26,6 +26,9 @@ members = [
|
||||||
"yewtil/examples/futures",
|
"yewtil/examples/futures",
|
||||||
"yewtil/examples/function_component",
|
"yewtil/examples/function_component",
|
||||||
|
|
||||||
|
# dsl
|
||||||
|
"yew-dsl",
|
||||||
|
|
||||||
# Examples
|
# Examples
|
||||||
"examples/counter",
|
"examples/counter",
|
||||||
"examples/crm",
|
"examples/crm",
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
[package]
|
||||||
|
name = "yew-dsl"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Teymour Aldridge <teymour.aldridge@icloud.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
yew = {path="../yew"}
|
|
@ -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<PARENT> = Rc<RefCell<Option<Scope<PARENT>>>>;
|
||||||
|
|
||||||
|
/// `BoxedVNodeProducer` is a wrapper around a function which produces a `VNode`.
|
||||||
|
pub struct BoxedVNodeProducer<COMP: Component>(Box<dyn FnOnce(ScopeHolder<COMP>) -> VNode>);
|
||||||
|
|
||||||
|
impl<COMP: Component> BoxedVNodeProducer<COMP> {
|
||||||
|
fn wrap(f: impl FnOnce(ScopeHolder<COMP>) -> VNode + 'static) -> Self {
|
||||||
|
BoxedVNodeProducer(Box::new(f))
|
||||||
|
}
|
||||||
|
fn execute(self, scope: &ScopeHolder<COMP>) -> VNode {
|
||||||
|
(self.0)(scope.clone())
|
||||||
|
}
|
||||||
|
pub fn build(self) -> VNode {
|
||||||
|
let scope = ScopeHolder::default();
|
||||||
|
self.execute(&scope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<COMP: Component> Into<VNode> for BoxedVNodeProducer<COMP> {
|
||||||
|
fn into(self) -> VNode {
|
||||||
|
self.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates HTML tags (e.g. 'span', 'div', etc).
|
||||||
|
pub fn tag<COMP: Component>(tag: &'static str) -> VTagProducer<COMP> {
|
||||||
|
VTagProducer::new(tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates child components.
|
||||||
|
pub fn comp<COMP: Component, CHILD: Component>(props: CHILD::Properties) -> VCompProducer<COMP> {
|
||||||
|
VCompProducer::new::<CHILD>(props)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates text nodes.
|
||||||
|
pub fn text<COMP: Component, TEXT: Into<String> + 'static>(text: TEXT) -> VTextProducer {
|
||||||
|
VTextProducer::new::<TEXT>(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates new lists populatated with the data supplied to the function.
|
||||||
|
pub fn populated_list<COMP: Component>(list: Vec<BoxedVNodeProducer<COMP>>) -> VListProducer<COMP> {
|
||||||
|
VListProducer::populated_new(list)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates new (empty) lists.
|
||||||
|
pub fn list<COMP: Component>() -> VListProducer<COMP> {
|
||||||
|
VListProducer::new()
|
||||||
|
}
|
|
@ -1,14 +1,19 @@
|
||||||
use crate::dsl::BoxedVNodeProducer;
|
use crate::BoxedVNodeProducer;
|
||||||
use yew::virtual_dom::vcomp::ScopeHolder;
|
|
||||||
use yew::virtual_dom::VComp;
|
use yew::virtual_dom::VComp;
|
||||||
use yew::{Component, NodeRef};
|
use yew::{Component, NodeRef};
|
||||||
|
|
||||||
pub struct VCompProducer<COMP: Component>(Box<dyn FnOnce(ScopeHolder<COMP>) -> VComp<COMP>>);
|
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<COMP: Component>(Box<dyn FnOnce(ScopeHolder<COMP>) -> VComp>);
|
||||||
|
|
||||||
impl<COMP: Component> VCompProducer<COMP> {
|
impl<COMP: Component> VCompProducer<COMP> {
|
||||||
pub fn new<CHILD: Component>(props: CHILD::Properties) -> Self {
|
pub fn new<CHILD: Component>(props: CHILD::Properties) -> Self {
|
||||||
// TODO allow getting the noderef as a parameter somewhere.
|
// TODO: allow getting the NodeRef as a parameter somewhere.
|
||||||
VCompProducer(Box::new(move |scope| VComp::new::<CHILD>(props, scope, NodeRef::default())))
|
VCompProducer(Box::new(move |_| {
|
||||||
|
VComp::new::<CHILD>(props, NodeRef::default(), None)
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,21 @@
|
||||||
use crate::dsl::BoxedVNodeProducer;
|
use crate::BoxedVNodeProducer;
|
||||||
use yew::virtual_dom::VList;
|
use yew::virtual_dom::VList;
|
||||||
use yew::Component;
|
use yew::Component;
|
||||||
|
|
||||||
|
///
|
||||||
pub struct VListProducer<COMP: Component> {
|
pub struct VListProducer<COMP: Component> {
|
||||||
children: Vec<BoxedVNodeProducer<COMP>>,
|
children: Vec<BoxedVNodeProducer<COMP>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<COMP: Component> Default for VListProducer<COMP> {
|
||||||
|
fn default() -> Self {
|
||||||
|
VListProducer::<COMP> { children: vec![] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<COMP: Component> VListProducer<COMP> {
|
impl<COMP: Component> VListProducer<COMP> {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
VListProducer { children: vec![] }
|
VListProducer::<COMP> { children: vec![] }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn child<T: Into<BoxedVNodeProducer<COMP>>>(mut self, child: T) -> Self {
|
pub fn child<T: Into<BoxedVNodeProducer<COMP>>>(mut self, child: T) -> Self {
|
||||||
|
@ -17,7 +24,7 @@ impl<COMP: Component> VListProducer<COMP> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn populated_new(children: Vec<BoxedVNodeProducer<COMP>>) -> Self {
|
pub fn populated_new(children: Vec<BoxedVNodeProducer<COMP>>) -> Self {
|
||||||
VListProducer { children }
|
VListProducer::<COMP> { children }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,31 +1,33 @@
|
||||||
use crate::dsl::BoxedVNodeProducer;
|
use crate::BoxedVNodeProducer;
|
||||||
use yew::virtual_dom::vcomp::ScopeHolder;
|
use crate::ScopeHolder;
|
||||||
use yew::virtual_dom::{Listener, VTag};
|
use yew::virtual_dom::{Listener, VTag};
|
||||||
use yew::{Classes, Component};
|
use yew::{Classes, Component};
|
||||||
|
|
||||||
pub struct Effect<T, COMP: Component>(Box<dyn FnOnce(T, &ScopeHolder<COMP>) -> T>);
|
pub struct Effect<T, COMP: Component>(Box<dyn FnOnce(T, &ScopeHolder<COMP>) -> T>);
|
||||||
|
|
||||||
impl<T, COMP: Component> Effect<T, COMP> {
|
impl<T, COMP: Component> Effect<T, COMP> {
|
||||||
fn new(f: impl FnOnce(T, &ScopeHolder<COMP>) -> T + 'static) -> Self {
|
fn new(f: impl FnOnce(T, &ScopeHolder<COMP>) -> T + 'static) -> Self {
|
||||||
Effect(Box::new(f))
|
Effect::<T, COMP>(Box::new(f))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct VTagProducer<COMP: Component> {
|
pub struct VTagProducer<COMP: Component> {
|
||||||
tag_type: &'static str,
|
tag_type: &'static str,
|
||||||
effects: Vec<Effect<VTag<COMP>, COMP>>,
|
effects: Vec<Effect<VTag, COMP>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<COMP: Component> VTagProducer<COMP> {
|
impl<COMP: Component> VTagProducer<COMP> {
|
||||||
pub fn new(tag_type: &'static str) -> Self {
|
pub fn new(tag_type: &'static str) -> Self {
|
||||||
VTagProducer {
|
VTagProducer::<COMP> {
|
||||||
tag_type,
|
tag_type,
|
||||||
effects: vec![],
|
effects: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO, consider making this T: Into<VNode> - 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<VNode> - 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<T: Into<BoxedVNodeProducer<COMP>> + 'static>(mut self, child: T) -> Self {
|
pub fn child<T: Into<BoxedVNodeProducer<COMP>> + 'static>(mut self, child: T) -> Self {
|
||||||
let effect = Effect::new(move |mut vtag: VTag<COMP>, scope: &ScopeHolder<COMP>| {
|
let effect = Effect::new(move |mut vtag: VTag, scope: &ScopeHolder<COMP>| {
|
||||||
let child = child.into().execute(scope);
|
let child = child.into().execute(scope);
|
||||||
vtag.add_child(child);
|
vtag.add_child(child);
|
||||||
vtag
|
vtag
|
||||||
|
@ -35,7 +37,7 @@ impl<COMP: Component> VTagProducer<COMP> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn attribute(mut self, name: String, value: String) -> Self {
|
pub fn attribute(mut self, name: String, value: String) -> Self {
|
||||||
let effect = Effect::new(move |mut vtag: VTag<COMP>, _scope: &ScopeHolder<COMP>| {
|
let effect = Effect::new(move |mut vtag: VTag, _scope: &ScopeHolder<COMP>| {
|
||||||
vtag.add_attribute(&name, &value);
|
vtag.add_attribute(&name, &value);
|
||||||
vtag
|
vtag
|
||||||
});
|
});
|
||||||
|
@ -43,8 +45,8 @@ impl<COMP: Component> VTagProducer<COMP> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn listener(mut self, listener: Box<dyn Listener<COMP>>) -> Self {
|
pub fn listener(mut self, listener: std::rc::Rc<dyn Listener>) -> Self {
|
||||||
let effect = Effect::new(move |mut vtag: VTag<COMP>, _scope: &ScopeHolder<COMP>| {
|
let effect = Effect::new(move |mut vtag: VTag, _scope: &ScopeHolder<COMP>| {
|
||||||
vtag.add_listener(listener);
|
vtag.add_listener(listener);
|
||||||
vtag
|
vtag
|
||||||
});
|
});
|
||||||
|
@ -53,8 +55,8 @@ impl<COMP: Component> VTagProducer<COMP> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn classes(mut self, classes: Classes) -> Self {
|
pub fn classes(mut self, classes: Classes) -> Self {
|
||||||
let effect = Effect::new(move |mut vtag: VTag<COMP>, _scope: &ScopeHolder<COMP>| {
|
let effect = Effect::new(move |mut vtag: VTag, _scope: &ScopeHolder<COMP>| {
|
||||||
vtag.set_classes(classes);
|
vtag.add_attribute("class", &classes.to_string());
|
||||||
vtag
|
vtag
|
||||||
});
|
});
|
||||||
self.effects.push(effect);
|
self.effects.push(effect);
|
|
@ -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<dyn FnOnce() -> VText>);
|
||||||
|
|
||||||
|
impl VTextProducer {
|
||||||
|
pub fn new<TEXT: Into<String> + 'static>(text: TEXT) -> Self {
|
||||||
|
VTextProducer(Box::new(move || VText::new(text.into())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<COMP: Component> From<VTextProducer> for BoxedVNodeProducer<COMP> {
|
||||||
|
fn from(vtext_prod: VTextProducer) -> Self {
|
||||||
|
BoxedVNodeProducer::wrap(move |_scope| (vtext_prod.0)().into())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<COMP: Component>(Box<dyn FnOnce(ScopeHolder<COMP>) -> VNode<COMP>>);
|
|
||||||
|
|
||||||
impl<COMP: Component> BoxedVNodeProducer<COMP> {
|
|
||||||
fn wrap(f: impl FnOnce(ScopeHolder<COMP>) -> VNode<COMP> + 'static) -> Self {
|
|
||||||
BoxedVNodeProducer(Box::new(f))
|
|
||||||
}
|
|
||||||
fn execute(self, scope: &ScopeHolder<COMP>) -> VNode<COMP> {
|
|
||||||
(self.0)(scope.clone())
|
|
||||||
}
|
|
||||||
pub fn build(self) -> VNode<COMP> {
|
|
||||||
let scope = ScopeHolder::default();
|
|
||||||
self.execute(&scope)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<COMP: Component> Into<VNode<COMP>> for BoxedVNodeProducer<COMP> {
|
|
||||||
fn into(self) -> VNode<COMP> {
|
|
||||||
self.build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a tag node.
|
|
||||||
pub fn tag<COMP: Component>(tag: &'static str) -> VTagProducer<COMP> {
|
|
||||||
VTagProducer::new(tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a component (Specified by the second type parameter).
|
|
||||||
pub fn comp<COMP: Component, CHILD: Component>(props: CHILD::Properties) -> VCompProducer<COMP> {
|
|
||||||
VCompProducer::new::<CHILD>(props)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a text node
|
|
||||||
pub fn text<COMP: Component, T: Into<String> + 'static>(text: T) -> VTextProducer<COMP> {
|
|
||||||
VTextProducer::new::<T>(text)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new vlist, populated with the provided vnodes
|
|
||||||
pub fn populated_list<COMP: Component>(list: Vec<BoxedVNodeProducer<COMP>>) -> VListProducer<COMP> {
|
|
||||||
VListProducer::populated_new(list)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new vlist
|
|
||||||
pub fn list<COMP: Component>() -> VListProducer<COMP> {
|
|
||||||
VListProducer::new()
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
use crate::dsl::BoxedVNodeProducer;
|
|
||||||
use yew::virtual_dom::VText;
|
|
||||||
use yew::Component;
|
|
||||||
|
|
||||||
pub struct VTextProducer<COMP: Component>(Box<dyn FnOnce() -> VText<COMP>>);
|
|
||||||
|
|
||||||
impl<COMP: Component> VTextProducer<COMP> {
|
|
||||||
pub fn new<T: Into<String> + 'static>(text: T) -> Self {
|
|
||||||
VTextProducer(Box::new(move || VText::new(text.into())))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<COMP: Component> From<VTextProducer<COMP>> for BoxedVNodeProducer<COMP> {
|
|
||||||
fn from(vtext_prod: VTextProducer<COMP>) -> Self {
|
|
||||||
BoxedVNodeProducer::wrap(move |_scope| (vtext_prod.0)().into())
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue