tachys/view/
mod.rs

1use self::add_attr::AddAnyAttr;
2use crate::{hydration::Cursor, ssr::StreamBuilder};
3use parking_lot::RwLock;
4use std::{cell::RefCell, future::Future, rc::Rc, sync::Arc};
5
6/// Add attributes to typed views.
7pub mod add_attr;
8/// A typed-erased view type.
9pub mod any_view;
10/// Allows choosing between one of several views.
11pub mod either;
12/// View rendering for `Result<_, _>` types.
13pub mod error_boundary;
14/// A type-erased view collection.
15pub mod fragment;
16/// View implementations for several iterable types.
17pub mod iterators;
18/// Keyed list iteration.
19pub mod keyed;
20mod primitives;
21/// Optimized types for static strings known at compile time.
22#[cfg(feature = "nightly")]
23pub mod static_types;
24/// View implementation for string types.
25pub mod strings;
26/// Optimizations for creating views via HTML `<template>` nodes.
27pub mod template;
28/// View implementations for tuples.
29pub mod tuples;
30
31/// The `Render` trait allows rendering something as part of the user interface.
32pub trait Render: Sized {
33    /// The “view state” for this type, which can be retained between updates.
34    ///
35    /// For example, for a text node, `State` might be the actual DOM text node
36    /// and the previous string, to allow for diffing between updates.
37    type State: Mountable;
38
39    /// Creates the view for the first time, without hydrating from existing HTML.
40    fn build(self) -> Self::State;
41
42    /// Updates the view with new data.
43    fn rebuild(self, state: &mut Self::State);
44}
45
46pub(crate) trait MarkBranch {
47    fn open_branch(&mut self, branch_id: &str);
48
49    fn close_branch(&mut self, branch_id: &str);
50}
51
52impl MarkBranch for String {
53    fn open_branch(&mut self, branch_id: &str) {
54        self.push_str("<!--bo-");
55        self.push_str(branch_id);
56        self.push_str("-->");
57    }
58
59    fn close_branch(&mut self, branch_id: &str) {
60        self.push_str("<!--bc-");
61        self.push_str(branch_id);
62        self.push_str("-->");
63    }
64}
65
66impl MarkBranch for StreamBuilder {
67    fn open_branch(&mut self, branch_id: &str) {
68        self.sync_buf.push_str("<!--bo-");
69        self.sync_buf.push_str(branch_id);
70        self.sync_buf.push_str("-->");
71    }
72
73    fn close_branch(&mut self, branch_id: &str) {
74        self.sync_buf.push_str("<!--bc-");
75        self.sync_buf.push_str(branch_id);
76        self.sync_buf.push_str("-->");
77    }
78}
79
80/// The `RenderHtml` trait allows rendering something to HTML, and transforming
81/// that HTML into an interactive interface.
82///
83/// This process is traditionally called “server rendering” and “hydration.” As a
84/// metaphor, this means that the structure of the view is created on the server, then
85/// “dehydrated” to HTML, sent across the network, and “rehydrated” with interactivity
86/// in the browser.
87///
88/// However, the same process can be done entirely in the browser: for example, a view
89/// can be transformed into some HTML that is used to create a `<template>` node, which
90/// can be cloned many times and “hydrated,” which is more efficient than creating the
91/// whole view piece by piece.
92pub trait RenderHtml
93where
94    Self: Render + AddAnyAttr + Send,
95{
96    /// The type of the view after waiting for all asynchronous data to load.
97    type AsyncOutput: RenderHtml;
98
99    /// The minimum length of HTML created when this view is rendered.
100    const MIN_LENGTH: usize;
101
102    /// Whether this should actually exist in the DOM, if it is the child of an element.
103    const EXISTS: bool = true;
104
105    /// “Runs” the view without other side effects. For primitive types, this is a no-op. For
106    /// reactive types, this can be used to gather data about reactivity or about asynchronous data
107    /// that needs to be loaded.
108    fn dry_resolve(&mut self);
109
110    /// Waits for any asynchronous sections of the view to load and returns the output.
111    fn resolve(self) -> impl Future<Output = Self::AsyncOutput> + Send;
112
113    /// An estimated length for this view, when rendered to HTML.
114    ///
115    /// This is used for calculating the string buffer size when rendering HTML. It does not need
116    /// to be precise, but should be an appropriate estimate. The more accurate, the fewer
117    /// reallocations will be required and the faster server-side rendering will be.
118    fn html_len(&self) -> usize {
119        Self::MIN_LENGTH
120    }
121
122    /// Renders a view to an HTML string.
123    fn to_html(self) -> String
124    where
125        Self: Sized,
126    {
127        let mut buf = String::with_capacity(self.html_len());
128        self.to_html_with_buf(&mut buf, &mut Position::FirstChild, true, false);
129        buf
130    }
131
132    /// Renders a view to HTML with branch markers. This can be used to support libraries that diff
133    /// HTML pages against one another, by marking sections of the view that branch to different
134    /// types with marker comments.
135    fn to_html_branching(self) -> String
136    where
137        Self: Sized,
138    {
139        let mut buf = String::with_capacity(self.html_len());
140        self.to_html_with_buf(&mut buf, &mut Position::FirstChild, true, true);
141        buf
142    }
143
144    /// Renders a view to an in-order stream of HTML.
145    fn to_html_stream_in_order(self) -> StreamBuilder
146    where
147        Self: Sized,
148    {
149        let mut builder = StreamBuilder::with_capacity(self.html_len(), None);
150        self.to_html_async_with_buf::<false>(
151            &mut builder,
152            &mut Position::FirstChild,
153            true,
154            false,
155        );
156        builder.finish()
157    }
158
159    /// Renders a view to an in-order stream of HTML with branch markers. This can be used to support libraries that diff
160    /// HTML pages against one another, by marking sections of the view that branch to different
161    /// types with marker comments.
162    fn to_html_stream_in_order_branching(self) -> StreamBuilder
163    where
164        Self: Sized,
165    {
166        let mut builder = StreamBuilder::with_capacity(self.html_len(), None);
167        self.to_html_async_with_buf::<false>(
168            &mut builder,
169            &mut Position::FirstChild,
170            true,
171            true,
172        );
173        builder.finish()
174    }
175
176    /// Renders a view to an out-of-order stream of HTML.
177    fn to_html_stream_out_of_order(self) -> StreamBuilder
178    where
179        Self: Sized,
180    {
181        //let capacity = self.html_len();
182        let mut builder =
183            StreamBuilder::with_capacity(self.html_len(), Some(vec![0]));
184
185        self.to_html_async_with_buf::<true>(
186            &mut builder,
187            &mut Position::FirstChild,
188            true,
189            false,
190        );
191        builder.finish()
192    }
193
194    /// Renders a view to an out-of-order stream of HTML with branch markers. This can be used to support libraries that diff
195    /// HTML pages against one another, by marking sections of the view that branch to different
196    /// types with marker comments.
197    fn to_html_stream_out_of_order_branching(self) -> StreamBuilder
198    where
199        Self: Sized,
200    {
201        let mut builder =
202            StreamBuilder::with_capacity(self.html_len(), Some(vec![0]));
203
204        self.to_html_async_with_buf::<true>(
205            &mut builder,
206            &mut Position::FirstChild,
207            true,
208            true,
209        );
210        builder.finish()
211    }
212
213    /// Renders a view to HTML, writing it into the given buffer.
214    fn to_html_with_buf(
215        self,
216        buf: &mut String,
217        position: &mut Position,
218        escape: bool,
219        mark_branches: bool,
220    );
221
222    /// Renders a view into a buffer of (synchronous or asynchronous) HTML chunks.
223    fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
224        self,
225        buf: &mut StreamBuilder,
226        position: &mut Position,
227        escape: bool,
228        mark_branches: bool,
229    ) where
230        Self: Sized,
231    {
232        buf.with_buf(|buf| {
233            self.to_html_with_buf(buf, position, escape, mark_branches)
234        });
235    }
236
237    /// Makes a set of DOM nodes rendered from HTML interactive.
238    ///
239    /// If `FROM_SERVER` is `true`, this HTML was rendered using [`RenderHtml::to_html`]
240    /// (e.g., during server-side rendering ).
241    ///
242    /// If `FROM_SERVER` is `false`, the HTML was rendered using [`ToTemplate::to_template`]
243    /// (e.g., into a `<template>` element).
244    fn hydrate<const FROM_SERVER: bool>(
245        self,
246        cursor: &Cursor,
247        position: &PositionState,
248    ) -> Self::State;
249
250    /// Hydrates using [`RenderHtml::hydrate`], beginning at the given element.
251    fn hydrate_from<const FROM_SERVER: bool>(
252        self,
253        el: &crate::renderer::types::Element,
254    ) -> Self::State
255    where
256        Self: Sized,
257    {
258        self.hydrate_from_position::<FROM_SERVER>(el, Position::default())
259    }
260
261    /// Hydrates using [`RenderHtml::hydrate`], beginning at the given element and position.
262    fn hydrate_from_position<const FROM_SERVER: bool>(
263        self,
264        el: &crate::renderer::types::Element,
265        position: Position,
266    ) -> Self::State
267    where
268        Self: Sized,
269    {
270        let cursor = Cursor::new(el.clone());
271        let position = PositionState::new(position);
272        self.hydrate::<FROM_SERVER>(&cursor, &position)
273    }
274}
275
276/// Allows a type to be mounted to the DOM.
277pub trait Mountable {
278    /// Detaches the view from the DOM.
279    fn unmount(&mut self);
280
281    /// Mounts a node to the interface.
282    fn mount(
283        &mut self,
284        parent: &crate::renderer::types::Element,
285        marker: Option<&crate::renderer::types::Node>,
286    );
287
288    /// Inserts another `Mountable` type before this one. Returns `false` if
289    /// this does not actually exist in the UI (for example, `()`).
290    fn insert_before_this(&self, child: &mut dyn Mountable) -> bool;
291
292    /// Inserts another `Mountable` type before this one, or before the marker
293    /// if this one doesn't exist in the UI (for example, `()`).
294    fn insert_before_this_or_marker(
295        &self,
296        parent: &crate::renderer::types::Element,
297        child: &mut dyn Mountable,
298        marker: Option<&crate::renderer::types::Node>,
299    ) {
300        if !self.insert_before_this(child) {
301            child.mount(parent, marker);
302        }
303    }
304}
305
306/// Indicates where a node should be mounted to its parent.
307pub enum MountKind {
308    /// Node should be mounted before this marker node.
309    Before(crate::renderer::types::Node),
310    /// Node should be appended to the parent’s children.
311    Append,
312}
313
314impl<T> Mountable for Option<T>
315where
316    T: Mountable,
317{
318    fn unmount(&mut self) {
319        if let Some(ref mut mounted) = self {
320            mounted.unmount()
321        }
322    }
323
324    fn mount(
325        &mut self,
326        parent: &crate::renderer::types::Element,
327        marker: Option<&crate::renderer::types::Node>,
328    ) {
329        if let Some(ref mut inner) = self {
330            inner.mount(parent, marker);
331        }
332    }
333
334    fn insert_before_this(&self, child: &mut dyn Mountable) -> bool {
335        self.as_ref()
336            .map(|inner| inner.insert_before_this(child))
337            .unwrap_or(false)
338    }
339}
340
341impl<T> Mountable for Rc<RefCell<T>>
342where
343    T: Mountable,
344{
345    fn unmount(&mut self) {
346        self.borrow_mut().unmount()
347    }
348
349    fn mount(
350        &mut self,
351        parent: &crate::renderer::types::Element,
352        marker: Option<&crate::renderer::types::Node>,
353    ) {
354        self.borrow_mut().mount(parent, marker);
355    }
356
357    fn insert_before_this(&self, child: &mut dyn Mountable) -> bool {
358        self.borrow().insert_before_this(child)
359    }
360}
361
362/// Allows data to be added to a static template.
363pub trait ToTemplate {
364    /// The HTML content of the static template.
365    const TEMPLATE: &'static str = "";
366    /// The `class` attribute content known at compile time.
367    const CLASS: &'static str = "";
368    /// The `style` attribute content known at compile time.
369    const STYLE: &'static str = "";
370    /// The length of the template.
371    const LEN: usize = Self::TEMPLATE.len();
372
373    /// Renders a view type to a template. This does not take actual view data,
374    /// but can be used for constructing part of an HTML `<template>` that corresponds
375    /// to a view of a particular type.
376    fn to_template(
377        buf: &mut String,
378        class: &mut String,
379        style: &mut String,
380        inner_html: &mut String,
381        position: &mut Position,
382    );
383}
384
385/// Keeps track of what position the item currently being hydrated is in, relative to its siblings
386/// and parents.
387#[derive(Debug, Default, Clone)]
388pub struct PositionState(Arc<RwLock<Position>>);
389
390impl PositionState {
391    /// Creates a new position tracker.
392    pub fn new(position: Position) -> Self {
393        Self(Arc::new(RwLock::new(position)))
394    }
395
396    /// Sets the current position.
397    pub fn set(&self, position: Position) {
398        *self.0.write() = position;
399    }
400
401    /// Gets the current position.
402    pub fn get(&self) -> Position {
403        *self.0.read()
404    }
405
406    /// Creates a new [`PositionState`], which starts with the same [`Position`], but no longer
407    /// shares data with this `PositionState`.
408    pub fn deep_clone(&self) -> Self {
409        let current = self.get();
410        Self(Arc::new(RwLock::new(current)))
411    }
412}
413
414/// The position of this element, relative to others.
415#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
416pub enum Position {
417    /// This is the current node.
418    Current,
419    /// This is the first child of its parent.
420    #[default]
421    FirstChild,
422    /// This is the next child after another child.
423    NextChild,
424    /// This is the next child after a text node.
425    NextChildAfterText,
426    /// This is the only child of its parent.
427    OnlyChild,
428    /// This is the last child of its parent.
429    LastChild,
430}
431
432/// Declares that this type can be converted into some other type, which can be renderered.
433pub trait IntoRender {
434    /// The renderable type into which this type can be converted.
435    type Output;
436
437    /// Consumes this value, transforming it into the renderable type.
438    fn into_render(self) -> Self::Output;
439}
440
441impl<T> IntoRender for T
442where
443    T: Render,
444{
445    type Output = Self;
446
447    fn into_render(self) -> Self::Output {
448        self
449    }
450}