@@ -0,0 +1,848 @@
1+//! Helpers for building virtual DOM nodes.
3+use crate::{Attribute, Listener, Node, RootRender, VdomWeak};
6+/// A virtual DOM element builder.
8+/// Typically constructed with element-specific constructors, eg the `div`
9+/// function for building `<div>` elements or the `button` function for building
10+/// `<button>` elements.
11+#[derive(Clone, Debug)]
12+pub struct ElementBuilder<'a, Listeners, Attributes, Children>
14+ Listeners: 'a + AsRef<[Listener<'a>]>,
15+ Attributes: 'a + AsRef<[Attribute<'a>]>,
16+ Children: 'a + AsRef<[Node<'a>]>,
20+ listeners: Listeners,
21+ attributes: Attributes,
25+impl<'a, Listeners, Attributes, Children> ElementBuilder<'a, Listeners, Attributes, Children>
27+ Listeners: 'a + AsRef<[Listener<'a>]>,
28+ Attributes: 'a + AsRef<[Attribute<'a>]>,
29+ Children: 'a + AsRef<[Node<'a>]>,
31+ /// Set the listeners for this element.
33+ /// You can use this method to customize the backing storage for listeners,
34+ /// for example to use a fixed-size array instead of the default
35+ /// dynamically-sized `bumpalo::collections::Vec`.
37+ /// Any listeners already added to the builder will be overridden.
42+ /// use dodrio::{builder::*, bumpalo::Bump, on};
44+ /// let b = Bump::new();
46+ /// // Create a `<div>` with a fixed-size array of two listeners.
47+ /// let my_div = div(&b)
49+ /// on(&b, "click", |root, vdom, event| {
52+ /// on(&b, "dblclick", |root, vdom, event| {
59+ pub fn listeners<L>(self, listeners: L) -> ElementBuilder<'a, L, Attributes, Children>
61+ L: 'a + AsRef<[Listener<'a>]>,
65+ tag_name: self.tag_name,
67+ attributes: self.attributes,
68+ children: self.children,
72+ /// Set the attributes for this element.
74+ /// You can use this method to customize the backing storage for attributes,
75+ /// for example to use a fixed-size array instead of the default
76+ /// dynamically-sized `bumpalo::collections::Vec`.
78+ /// Any attributes already added to the builder will be overridden.
83+ /// use dodrio::{builder::*, bumpalo::Bump, Attribute};
85+ /// let b = Bump::new();
87+ /// // Create a `<div>` with a fixed-size array of two attributes.
88+ /// let my_div = div(&b)
96+ /// value: "notification",
102+ pub fn attributes<A>(self, attributes: A) -> ElementBuilder<'a, Listeners, A, Children>
104+ A: 'a + AsRef<[Attribute<'a>]>,
108+ tag_name: self.tag_name,
109+ listeners: self.listeners,
111+ children: self.children,
115+ /// Set the children for this element.
117+ /// You can use this method to customize the backing storage for children,
118+ /// for example to use a fixed-size array instead of the default
119+ /// dynamically-sized `bumpalo::collections::Vec`.
121+ /// Any children already added to the builder will be overridden.
126+ /// use dodrio::{builder::*, bumpalo::Bump};
128+ /// let b = Bump::new();
130+ /// // Create a `<div>` with a fixed-size array of two `<span>` children.
131+ /// let my_div = div(&b)
133+ /// span(&b).finish(),
134+ /// span(&b).finish(),
139+ pub fn children<C>(self, children: C) -> ElementBuilder<'a, Listeners, Attributes, C>
141+ C: 'a + AsRef<[Node<'a>]>,
145+ tag_name: self.tag_name,
146+ listeners: self.listeners,
147+ attributes: self.attributes,
152+ /// Create the virtual DOM node described by this builder.
157+ /// use dodrio::{builder::*, bumpalo::Bump, Node};
159+ /// let b = Bump::new();
161+ /// // Start with a builder...
162+ /// let builder: ElementBuilder<_, _, _> = div(&b);
164+ /// // ...and finish it to create a virtual DOM node!
165+ /// let my_div: Node = builder.finish();
168+ pub fn finish(self) -> Node<'a> {
179+impl<'a, Attributes, Children>
180+ ElementBuilder<'a, bumpalo::collections::Vec<'a, Listener<'a>>, Attributes, Children>
182+ Attributes: 'a + AsRef<[Attribute<'a>]>,
183+ Children: 'a + AsRef<[Node<'a>]>,
185+ /// Add a new event listener to this element.
187+ /// The `event` string specifies which event will be listened for. The
188+ /// `callback` function is the function that will be invoked if the
189+ /// specified event occurs.
194+ /// use dodrio::{builder::*, bumpalo::Bump};
196+ /// let b = Bump::new();
198+ /// // A button that does something when clicked!
199+ /// let my_button = button(&b)
200+ /// .on("click", |root, vdom, event| {
206+ pub fn on<F>(mut self, event: &'a str, callback: F) -> Self
208+ F: 'static + Fn(&mut dyn RootRender, VdomWeak, web_sys::Event),
210+ self.listeners.push(Listener {
212+ callback: self.bump.alloc(callback),
218+impl<'a, Listeners, Children>
219+ ElementBuilder<'a, Listeners, bumpalo::collections::Vec<'a, Attribute<'a>>, Children>
221+ Listeners: 'a + AsRef<[Listener<'a>]>,
222+ Children: 'a + AsRef<[Node<'a>]>,
224+ /// Add a new attribute to this element.
229+ /// use dodrio::{builder::*, bumpalo::Bump};
231+ /// let b = Bump::new();
233+ /// // Create the `<div id="my-div"/>` element.
234+ /// let my_div = div(&b).attr("id", "my-div").finish();
237+ pub fn attr(mut self, name: &'a str, value: &'a str) -> Self {
238+ self.attributes.push(Attribute { name, value });
242+ /// Conditionally add a "boolean-style" attribute to this element.
244+ /// If the `should_add` parameter is true, then adds an attribute with the
245+ /// given `name` and an empty string value. If the `should_add` parameter is
246+ /// false, then the attribute is not added.
248+ /// This method is useful for attributes whose semantics are defined by
249+ /// whether or not the attribute is present or not, and whose value is
250+ /// ignored. Example attributes like this include:
259+ /// use dodrio::{builder::*, bumpalo::Bump};
260+ /// use js_sys::Math;
262+ /// let b = Bump::new();
264+ /// // Create the `<div>` that is randomly hidden 50% of the time.
265+ /// let my_div = div(&b)
266+ /// .bool_attr("hidden", Math::random() >= 0.5)
269+ pub fn bool_attr(mut self, name: &'a str, should_add: bool) -> Self {
271+ self.attributes.push(Attribute { name, value: "" });
277+impl<'a, Listeners, Attributes>
278+ ElementBuilder<'a, Listeners, Attributes, bumpalo::collections::Vec<'a, Node<'a>>>
280+ Listeners: 'a + AsRef<[Listener<'a>]>,
281+ Attributes: 'a + AsRef<[Attribute<'a>]>,
283+ /// Add a new child to this element.
288+ /// use dodrio::{builder::*, bumpalo::Bump};
289+ /// use js_sys::Math;
291+ /// let b = Bump::new();
293+ /// // Create `<p><span></span></p>`.
294+ /// let my_div = p(&b)
295+ /// .child(span(&b).finish())
299+ pub fn child(mut self, child: Node<'a>) -> Self {
300+ self.children.push(child);
305+macro_rules! builder_constructors {
315+ ) -> ElementBuilder<
317+ bumpalo::collections::Vec<'a, Listener<'a>>,
318+ bumpalo::collections::Vec<'a, Attribute<'a>>,
319+ bumpalo::collections::Vec<'a, Node<'a>>,
323+ tag_name: stringify!($name),
324+ listeners: bumpalo::collections::Vec::new_in(bump),
325+ attributes: bumpalo::collections::Vec::new_in(bump),
326+ children: bumpalo::collections::Vec::new_in(bump),
333+// Organized in the same order as
334+// https://developer.mozilla.org/en-US/docs/Web/HTML/Element
336+// Does not include obsolete elements.
337+builder_constructors! {
338+ // Document metadata
341+ /// [`<base>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base)
345+ /// [`<head>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head)
349+ /// [`<link>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link)
353+ /// [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta)
357+ /// [`<style>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/style)
361+ /// [`<title>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/title)
368+ /// [`<body>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/body)
372+ // Content sectioning
375+ /// [`<address>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/address)
379+ /// [`<article>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/article)
383+ /// [`<aside>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/aside)
387+ /// [`<footer>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/footer)
391+ /// [`<header>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/header)
395+ /// [`<h1>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h1)
399+ /// [`<h2>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h2)
403+ /// [`<h3>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h3)
407+ /// [`<h4>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h4)
411+ /// [`<h5>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h5)
415+ /// [`<h6>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h6)
419+ /// [`<hgroup>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/hgroup)
423+ /// [`<main>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/main)
427+ /// [`<nav>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/nav)
431+ /// [`<section>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/section)
438+ /// [`<blockquote>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/blockquote)
442+ /// [`<dd>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dd)
446+ /// [`<div>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div)
450+ /// [`<dl>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dl)
454+ /// [`<dt>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dt)
458+ /// [`<figcaption>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figcaption)
462+ /// [`<figure>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figure)
466+ /// [`<hr>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/hr)
470+ /// [`<li>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/li)
474+ /// [`<ol>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ol)
478+ /// [`<p>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/p)
482+ /// [`<pre>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/pre)
486+ /// [`<ul>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul)
490+ // Inline text semantics
493+ /// [`<a>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a)
497+ /// [`<abbr>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/abbr)
501+ /// [`<b>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/b)
505+ /// [`<bdi>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/bdi)
509+ /// [`<bdo>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/bdo)
513+ /// [`<br>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/br)
517+ /// [`<cite>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/cite)
521+ /// [`<code>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/code)
525+ /// [`<data>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/data)
529+ /// [`<dfn>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dfn)
533+ /// [`<em>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/em)
537+ /// [`<i>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/i)
541+ /// [`<kbd>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/kbd)
545+ /// [`<mark>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/mark)
549+ /// [`<q>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/q)
553+ /// [`<rb>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/rb)
557+ /// [`<rp>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/rp)
561+ /// [`<rt>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/rt)
565+ /// [`<rtc>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/rtc)
569+ /// [`<ruby>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ruby)
573+ /// [`<s>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/s)
577+ /// [`<samp>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/samp)
581+ /// [`<small>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/small)
585+ /// [`<span>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/span)
589+ /// [`<strong>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/strong)
593+ /// [`<sub>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sub)
597+ /// [`<sup>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sup)
601+ /// [`<time>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/time)
605+ /// [`<u>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/u)
609+ /// [`<var>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/var)
613+ /// [`<wbr>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/wbr)
617+ // Image and multimedia
620+ /// [`<area>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/area)
624+ /// [`<audio>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/audio)
628+ /// [`<img>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img)
632+ /// [`<map>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/map)
636+ /// [`<track>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/track)
640+ /// [`<video>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video)
647+ /// [`<embed>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/embed)
651+ /// [`<iframe>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe)
655+ /// [`<object>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/object)
659+ /// [`<param>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/param)
663+ /// [`<picture>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture)
667+ /// [`<source>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/source)
674+ /// [`<canvas>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas)
678+ /// [`<noscript>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/noscript)
682+ /// [`<script>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script)
686+ // Demarcating edits
689+ /// [`<del>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/del)
693+ /// [`<ins>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ins)
700+ /// [`<caption>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/caption)
704+ /// [`<col>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/col)
708+ /// [`<colgroup>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/colgroup)
712+ /// [`<table>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table)
716+ /// [`<tbody>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tbody)
720+ /// [`<td>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/td)
724+ /// [`<tfoot>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tfoot)
728+ /// [`<th>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/th)
732+ /// [`<thead>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/thead)
736+ /// [`<tr>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tr)
743+ /// [`<button>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button)
747+ /// [`<datalist>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/datalist)
751+ /// [`<fieldset>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/fieldset)
755+ /// [`<form>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form)
759+ /// [`<input>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input)
763+ /// [`<label>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label)
767+ /// [`<legend>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/legend)
771+ /// [`<meter>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meter)
775+ /// [`<optgroup>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/optgroup)
779+ /// [`<option>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option)
783+ /// [`<output>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/output)
787+ /// [`<progress>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/progress)
791+ /// [`<select>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select)
795+ /// [`<textarea>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea)
799+ // Interactive elements
802+ /// [`<details>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details)
806+ /// [`<dialog>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog)
810+ /// [`<menu>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/menu)
814+ /// [`<menuitem>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/menuitem)
818+ /// [`<summary>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/summary)
825+ /// [`<slot>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot)
829+ /// [`<template>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template)
834+/// Construct a text node.
836+/// This is `dodrio`'s virtual DOM equivalent of `document.createTextNode`.
841+/// use dodrio::builder::*;
843+/// let my_text = text("hello, dodrio!");
846+pub fn text<'a>(contents: &'a str) -> Node<'a> {
847+ Node::text(contents)