dioxus_document/document.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
use std::sync::Arc;
use super::*;
/// A context for the document
pub type DocumentContext = Arc<dyn Document>;
fn format_string_for_js(s: &str) -> String {
let escaped = s
.replace('\\', "\\\\")
.replace('\n', "\\n")
.replace('\r', "\\r")
.replace('"', "\\\"");
format!("\"{escaped}\"")
}
fn format_attributes(attributes: &[(&str, String)]) -> String {
let mut formatted = String::from("[");
for (key, value) in attributes {
formatted.push_str(&format!(
"[{}, {}],",
format_string_for_js(key),
format_string_for_js(value)
));
}
if formatted.ends_with(',') {
formatted.pop();
}
formatted.push(']');
formatted
}
/// Create a new element in the head with javascript through the [`Document::eval`] method
///
/// This can be used to implement the head element creation logic for most [`Document`] implementations.
pub fn create_element_in_head(
tag: &str,
attributes: &[(&str, String)],
children: Option<String>,
) -> String {
let helpers = include_str!("./js/head.js");
let attributes = format_attributes(attributes);
let children = children
.as_deref()
.map(format_string_for_js)
.unwrap_or("null".to_string());
let tag = format_string_for_js(tag);
format!(r#"{helpers};window.createElementInHead({tag}, {attributes}, {children});"#)
}
/// A provider for document-related functionality.
///
/// Provides things like a history API, a title, a way to run JS, and some other basics/essentials used
/// by nearly every platform.
///
/// An integration with some kind of navigation history.
///
/// Depending on your use case, your implementation may deviate from the described procedure. This
/// is fine, as long as both `current_route` and `current_query` match the described format.
///
/// However, you should document all deviations. Also, make sure the navigation is user-friendly.
/// The described behaviors are designed to mimic a web browser, which most users should already
/// know. Deviations might confuse them.
pub trait Document: 'static {
/// Run `eval` against this document, returning an [`Eval`] that can be used to await the result.
fn eval(&self, js: String) -> Eval;
/// Set the title of the document
fn set_title(&self, title: String) {
self.eval(format!("document.title = {title:?};"));
}
/// Create a new element in the head
fn create_head_element(
&self,
name: &str,
attributes: &[(&str, String)],
contents: Option<String>,
) {
// This default implementation remains to make the trait compatible with the 0.6 version, but it should not be used
// The element should only be created inside an effect so it is not called while the component is suspended
self.eval(create_element_in_head(name, attributes, contents));
}
/// Create a new meta tag in the head
fn create_meta(&self, props: MetaProps) {
let attributes = props.attributes();
self.create_head_element("meta", &attributes, None);
}
/// Create a new script tag in the head
fn create_script(&self, props: ScriptProps) {
let attributes = props.attributes();
self.create_head_element("script", &attributes, props.script_contents().ok());
}
/// Create a new style tag in the head
fn create_style(&self, props: StyleProps) {
let attributes = props.attributes();
self.create_head_element("style", &attributes, props.style_contents().ok());
}
/// Create a new link tag in the head
fn create_link(&self, props: LinkProps) {
let attributes = props.attributes();
self.create_head_element("link", &attributes, None);
}
/// Check if we should create a new head component at all. If it returns false, the head component will be skipped.
///
/// This runs once per head component and is used to hydrate head components in fullstack.
fn create_head_component(&self) -> bool {
true
}
}
/// A document that does nothing
#[derive(Default)]
pub struct NoOpDocument;
impl Document for NoOpDocument {
fn eval(&self, _: String) -> Eval {
let owner = generational_box::Owner::default();
struct NoOpEvaluator;
impl Evaluator for NoOpEvaluator {
fn poll_join(
&mut self,
_: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<serde_json::Value, EvalError>> {
std::task::Poll::Ready(Err(EvalError::Unsupported))
}
fn poll_recv(
&mut self,
_: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<serde_json::Value, EvalError>> {
std::task::Poll::Ready(Err(EvalError::Unsupported))
}
fn send(&self, _data: serde_json::Value) -> Result<(), EvalError> {
Err(EvalError::Unsupported)
}
}
Eval::new(owner.insert(Box::new(NoOpEvaluator)))
}
fn set_title(&self, _: String) {}
fn create_meta(&self, _: MetaProps) {}
fn create_script(&self, _: ScriptProps) {}
fn create_style(&self, _: StyleProps) {}
fn create_link(&self, _: LinkProps) {}
}