tachys/view/
mod.rs

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