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}