Open itsezc opened 1 year ago
In the short term, using a Vec of Elements as children can work, it just makes using the component less clean:
#![allow(non_snake_case)]
use dioxus::prelude::*;
fn main() {
dioxus_desktop::launch(app)
}
fn app(cx: Scope) -> Element {
render! {
Breadcrumbs {
divider: "/",
child_routes: vec![
render!{
Breadcrumb {
name: "Level 1",
link: "/"
}
},
render!{
Breadcrumb {
name: "Level 2",
link: "/level2"
}
}
]
}
}
}
#[derive(Props)]
pub struct BreadcrumbsProps<'a> {
divider: Option<&'a str>,
child_routes: Option<Vec<Element<'a>>>,
}
pub fn Breadcrumbs<'a>(cx: Scope<'a, BreadcrumbsProps<'a>>) -> Element {
cx.render(rsx!(
nav {
role: "navigation",
for child in cx.props.child_routes.iter().flatten() {
span {
p { cx.props.divider.unwrap_or("->") }
child
}
}
}
))
}
#[derive(PartialEq, Props)]
pub struct BreadcrumbProps<'a> {
name: &'a str,
link: &'a str,
}
pub fn Breadcrumb<'a>(cx: Scope<'a, BreadcrumbProps<'a>>) -> Element {
cx.render(rsx!(
a {
href: cx.props.link,
cx.props.name
}
))
}
In the future, for the specific case of a sitemap, this should become easier with #1020. The enum based router exposes a sitemap which should make it possible to implement a link tree that is generic over the router instead of hardcoded with components
In the longer term, a way to traverse through a single template could be beneficial. A single Element node can contain many different nodes, so it will never have a simple children attribute
For example this is one Element struct even though it includes several elements in the rsx:
div {
img {
src: "",
}
div {
p { "Hello world" }
}
}
We could implement a cursor to help users traverse through the element structure. It could look something like this:
use dioxus::{
core::{DynamicNode, IntoDynNode},
prelude::{SvgAttributes, *},
};
#[derive(Clone)]
struct NodeCursor<'a> {
// The position in the inner node, represented as a list of child offsets
// more info https://dioxuslabs.com/docs/nightly/guide/en/contributing/walkthrough_readme.html#the-rsx-macro
position: Vec<u8>,
// The inner node we are moving in. This is a block of static nodes with references to any dynamic children
inner: VNode<'a>,
}
impl<'a> NodeCursor<'a> {
fn current_node(&self) -> TemplateNode {
let mut child_index_iter = self.position.iter().copied();
let mut current =
self.inner.template.get().roots[child_index_iter.next().unwrap() as usize];
for child_index in child_index_iter {
match current {
TemplateNode::Element {
tag,
namespace,
attrs,
children,
} => {
current = children[child_index as usize];
}
_ => unreachable!(),
}
}
current
}
// Node cursor would have a set of getters to get data about the underlying node
fn current_node_text(&self) -> Option<&str> {
match self.current_node() {
TemplateNode::Element {
tag,
namespace,
attrs,
children,
} => None,
TemplateNode::Text { text } => Some(text),
TemplateNode::Dynamic { id } => None,
TemplateNode::DynamicText { id } => {
let node = &self.inner.dynamic_nodes[id];
match node {
dioxus::core::DynamicNode::Text(text) => Some(text.value),
_ => None,
}
}
}
}
fn first_child(&self) -> Option<NodeCursor<'a>> {
match self.current_node() {
TemplateNode::Element {
tag,
namespace,
attrs,
children,
} => Some(NodeCursor {
inner: self.inner.clone(),
position: {
let mut new_pos = self.position.clone();
new_pos.push(0);
new_pos
},
}),
TemplateNode::Dynamic { id } => {
let dyn_node = &self.inner.dynamic_nodes[id];
match dyn_node {
// We cannot easily traverse into Components
dioxus::core::DynamicNode::Component(_) => None,
dioxus::core::DynamicNode::Fragment(children) => {
children.get(0).map(|child| NodeCursor {
inner: child.clone(),
position: vec![0],
})
}
// Children and Placeholders do not have children
dioxus::core::DynamicNode::Placeholder(_) => None,
dioxus::core::DynamicNode::Text(_) => None,
}
}
// Text does not have children
TemplateNode::Text { text } => None,
TemplateNode::DynamicText { id } => None,
}
}
fn next_sibling(&self) -> NodeCursor<'a> {
todo!()
}
}
// This allows NodeCursor to be used in rsx
impl<'a> IntoDynNode<'a> for NodeCursor<'a> {
fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a> {
todo!()
}
}
Specific Demand
Iterating through the Children of a component is very complex currently, for instance:
So this should be consumable like so:
Additional context: https://discord.com/channels/899851952891002890/1127735881076330548
Implement Suggestion
The syntax for such implementation is not very straight forward, making the DX very poor implementing components like this. Either this should be made easier, or a better approach should be documented on how to best tackle this issue with code examples.