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