🎨 Convierte el ciclo de renderizado en async
`prepare()` de `Component`, `render()` de `Region` y `Template`, y la implementación de `ComponentRender` pasan a ser funciones async. Se actualizan todos los componentes base, helpers, tests y ejemplos.
This commit is contained in:
parent
9acd8cc51a
commit
9354894b3a
48 changed files with 436 additions and 365 deletions
|
|
@ -2,28 +2,23 @@ use crate::core::component::{Component, Context};
|
|||
use crate::html::{Markup, html};
|
||||
use crate::{AutoDefault, UniqueId, builder_fn};
|
||||
|
||||
use parking_lot::Mutex;
|
||||
|
||||
pub use parking_lot::MutexGuard as ComponentGuard;
|
||||
use parking_lot::RwLock;
|
||||
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
use std::vec::IntoIter;
|
||||
|
||||
/// Representa un componente hijo encapsulado para su uso en una lista [`Children`].
|
||||
#[derive(AutoDefault)]
|
||||
pub struct Child(Option<Mutex<Box<dyn Component>>>);
|
||||
// **< Child >**************************************************************************************
|
||||
|
||||
impl Clone for Child {
|
||||
fn clone(&self) -> Self {
|
||||
Child(self.0.as_ref().map(|m| Mutex::new(m.lock().clone_box())))
|
||||
}
|
||||
}
|
||||
/// Representa un componente hijo encapsulado para su uso en una lista [`Children`].
|
||||
#[derive(AutoDefault, Clone)]
|
||||
pub struct Child(Option<Arc<RwLock<Box<dyn Component>>>>);
|
||||
|
||||
impl fmt::Debug for Child {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match &self.0 {
|
||||
None => write!(f, "Child(None)"),
|
||||
Some(c) => write!(f, "Child({})", c.lock().name()),
|
||||
Some(c) => write!(f, "Child({})", c.read().name()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -31,7 +26,7 @@ impl fmt::Debug for Child {
|
|||
impl Child {
|
||||
/// Crea un nuevo `Child` a partir de un componente.
|
||||
pub fn with(component: impl Component) -> Self {
|
||||
Child(Some(Mutex::new(Box::new(component))))
|
||||
Child(Some(Arc::new(RwLock::new(Box::new(component)))))
|
||||
}
|
||||
|
||||
// **< Child BUILDER >**************************************************************************
|
||||
|
|
@ -41,7 +36,7 @@ impl Child {
|
|||
/// Si se proporciona `Some(component)`, se encapsula como [`Child`]; y si es `None`, se limpia.
|
||||
#[builder_fn]
|
||||
pub fn with_component<C: Component>(mut self, component: Option<C>) -> Self {
|
||||
self.0 = component.map(|c| Mutex::new(Box::new(c) as Box<dyn Component>));
|
||||
self.0 = component.map(|c| Arc::new(RwLock::new(Box::new(c) as Box<dyn Component>)));
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -50,22 +45,28 @@ impl Child {
|
|||
/// Devuelve el identificador del componente, si existe y está definido.
|
||||
#[inline]
|
||||
pub fn id(&self) -> Option<String> {
|
||||
self.0.as_ref().and_then(|c| c.lock().id())
|
||||
self.0.as_ref().and_then(|c| c.read().id())
|
||||
}
|
||||
|
||||
// **< Child RENDER >***************************************************************************
|
||||
|
||||
/// Renderiza el componente con el contexto proporcionado.
|
||||
pub fn render(&self, cx: &mut Context) -> Markup {
|
||||
self.0.as_ref().map_or(html! {}, |c| c.lock().render(cx))
|
||||
pub async fn render(&self, cx: &mut Context) -> Markup {
|
||||
match &self.0 {
|
||||
None => html! {},
|
||||
Some(m) => {
|
||||
let mut component = m.read().clone_box();
|
||||
component.render(cx).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// **< Child HELPERS >**************************************************************************
|
||||
|
||||
/// Devuelve el [`UniqueId`] del tipo del componente, si existe.
|
||||
// Devuelve el [`UniqueId`] del tipo del componente, si el Child no está vacío.
|
||||
#[inline]
|
||||
fn type_id(&self) -> Option<UniqueId> {
|
||||
self.0.as_ref().map(|c| c.lock().type_id())
|
||||
self.0.as_ref().map(|c| c.read().type_id())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -80,12 +81,12 @@ impl<C: Component + 'static> From<Embed<C>> for Child {
|
|||
/// children.with_child(my_embed.into());
|
||||
/// ```
|
||||
fn from(embed: Embed<C>) -> Self {
|
||||
if let Some(m) = embed.0 {
|
||||
Child(Some(Mutex::new(
|
||||
Box::new(m.into_inner()) as Box<dyn Component>
|
||||
)))
|
||||
} else {
|
||||
Child(None)
|
||||
match embed.0 {
|
||||
None => Child(None),
|
||||
Some(arc) => Child(Some(Arc::new(RwLock::new(match Arc::try_unwrap(arc) {
|
||||
Ok(c) => Box::new(c) as Box<dyn Component>,
|
||||
Err(arc) => arc.clone_box(),
|
||||
})))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -114,7 +115,7 @@ impl From<Child> for ChildOp {
|
|||
}
|
||||
}
|
||||
|
||||
// *************************************************************************************************
|
||||
// **< Embed >**************************************************************************************
|
||||
|
||||
/// Contenedor tipado para un *único* componente de un tipo concreto conocido.
|
||||
///
|
||||
|
|
@ -125,11 +126,12 @@ impl From<Child> for ChildOp {
|
|||
/// Se usa habitualmente para incrustar un componente dentro de otro cuando no se necesita una lista
|
||||
/// completa de hijos ([`Children`]), sino un único componente tipado en un campo concreto.
|
||||
#[derive(AutoDefault)]
|
||||
pub struct Embed<C: Component>(Option<Mutex<C>>);
|
||||
pub struct Embed<C: Component>(Option<Arc<C>>);
|
||||
|
||||
impl<C: Component + Clone> Clone for Embed<C> {
|
||||
// Arc<C>: Clone no requiere C: Clone, pero #[derive(Clone)] añadiría ese bound innecesariamente.
|
||||
impl<C: Component> Clone for Embed<C> {
|
||||
fn clone(&self) -> Self {
|
||||
Embed(self.0.as_ref().map(|m| Mutex::new(m.lock().clone())))
|
||||
Embed(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -137,7 +139,7 @@ impl<C: Component> fmt::Debug for Embed<C> {
|
|||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match &self.0 {
|
||||
None => write!(f, "Embed(None)"),
|
||||
Some(c) => write!(f, "Embed({})", c.lock().name()),
|
||||
Some(c) => write!(f, "Embed({})", c.name()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -145,7 +147,7 @@ impl<C: Component> fmt::Debug for Embed<C> {
|
|||
impl<C: Component> Embed<C> {
|
||||
/// Crea un nuevo `Embed` a partir de un componente.
|
||||
pub fn with(component: C) -> Self {
|
||||
Embed(Some(Mutex::new(component)))
|
||||
Embed(Some(Arc::new(component)))
|
||||
}
|
||||
|
||||
// **< Embed BUILDER >**************************************************************************
|
||||
|
|
@ -155,7 +157,7 @@ impl<C: Component> Embed<C> {
|
|||
/// Si se proporciona `Some(component)`, se encapsula como [`Embed`]; y si es `None`, se limpia.
|
||||
#[builder_fn]
|
||||
pub fn with_component(mut self, component: Option<C>) -> Self {
|
||||
self.0 = component.map(Mutex::new);
|
||||
self.0 = component.map(Arc::new);
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -164,55 +166,69 @@ impl<C: Component> Embed<C> {
|
|||
/// Devuelve el identificador del componente, si existe y está definido.
|
||||
#[inline]
|
||||
pub fn id(&self) -> Option<String> {
|
||||
self.0.as_ref().and_then(|c| c.lock().id())
|
||||
self.0.as_deref().and_then(|c| c.id())
|
||||
}
|
||||
|
||||
/// Devuelve un acceso al componente incrustado.
|
||||
/// Devuelve una referencia inmutable al componente incrustado, si existe.
|
||||
///
|
||||
/// - Devuelve `Some(ComponentGuard<C>)` si existe el componente, o `None` si está vacío.
|
||||
/// - El acceso es **exclusivo**: mientras el *guard* esté activo, no habrá otros accesos.
|
||||
/// - Se recomienda mantener el *guard* **el menor tiempo posible** para evitar bloqueos
|
||||
/// innecesarios.
|
||||
/// - Para modificar el componente, declara el *guard* como `mut`:
|
||||
/// `if let Some(mut c) = embed.get() { c.alter_title(...); }`.
|
||||
/// Para acceso mutable, usa [`get_mut`](Embed::get_mut).
|
||||
///
|
||||
/// # Ejemplo
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use pagetop::prelude::*;
|
||||
/// let embed = Embed::with(Html::with(|_| html! { "Prueba" }));
|
||||
/// {
|
||||
/// if let Some(component) = embed.get() {
|
||||
/// assert_eq!(component.name(), "Html");
|
||||
/// }
|
||||
/// }; // El *guard* se libera aquí, antes del *drop* de `embed`.
|
||||
///
|
||||
/// let embed = Embed::with(Block::new().with_title(L10n::n("Title")));
|
||||
/// {
|
||||
/// if let Some(mut component) = embed.get() {
|
||||
/// component.alter_title(L10n::n("New Title"));
|
||||
/// }
|
||||
/// }; // El *guard* se libera aquí, antes del *drop* de `embed`.
|
||||
/// if let Some(component) = embed.get() {
|
||||
/// assert_eq!(component.name(), "Html");
|
||||
/// }
|
||||
/// ```
|
||||
pub fn get(&self) -> Option<ComponentGuard<'_, C>> {
|
||||
self.0.as_ref().map(|m| m.lock())
|
||||
pub fn get(&self) -> Option<&C> {
|
||||
self.0.as_deref()
|
||||
}
|
||||
|
||||
/// Devuelve una referencia mutable al componente incrustado, si existe.
|
||||
///
|
||||
/// Si el [`Arc`] interno es compartido (por ejemplo, justo después de clonar el componente
|
||||
/// padre), aplica *copy-on-write*: clona `C` antes de devolver la referencia mutable. El
|
||||
/// prototipo almacenado queda intacto.
|
||||
///
|
||||
/// # Ejemplo
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use pagetop::prelude::*;
|
||||
/// let mut embed = Embed::with(Block::new().with_title(L10n::n("Title")));
|
||||
/// if let Some(component) = embed.get_mut() {
|
||||
/// component.alter_title(L10n::n("New Title"));
|
||||
/// }
|
||||
/// ```
|
||||
pub fn get_mut(&mut self) -> Option<&mut C>
|
||||
where
|
||||
C: Clone,
|
||||
{
|
||||
self.0.as_mut().map(Arc::make_mut)
|
||||
}
|
||||
|
||||
// **< Embed RENDER >***************************************************************************
|
||||
|
||||
/// Renderiza el componente con el contexto proporcionado.
|
||||
pub fn render(&self, cx: &mut Context) -> Markup {
|
||||
self.0.as_ref().map_or(html! {}, |c| c.lock().render(cx))
|
||||
pub async fn render(&self, cx: &mut Context) -> Markup {
|
||||
match &self.0 {
|
||||
None => html! {},
|
||||
Some(m) => {
|
||||
let mut component = m.clone_box();
|
||||
component.render(cx).await
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// *************************************************************************************************
|
||||
// **< Children >***********************************************************************************
|
||||
|
||||
/// Operaciones para componentes hijo [`Child`] en una lista [`Children`].
|
||||
pub enum ChildOp {
|
||||
/// Añade un hijo al final de la lista.
|
||||
Add(Child),
|
||||
/// Añade un hijo solo si la lista está vacía.
|
||||
/// Añade un hijo sólo si la lista está vacía.
|
||||
AddIfEmpty(Child),
|
||||
/// Añade varios hijos al final de la lista, en el orden recibido.
|
||||
AddMany(Vec<Child>),
|
||||
|
|
@ -243,13 +259,10 @@ pub enum ChildOp {
|
|||
/// - [`Child`]: representa un componente hijo encapsulado dentro de la lista. Almacena cualquier
|
||||
/// componente sin necesidad de conocer su tipo concreto.
|
||||
/// - [`Embed<C>`]: contenedor tipado para un *único* componente de tipo `C`. Preferible a
|
||||
/// `Children` cuando el padre solo necesita un componente y quiere acceso directo a los métodos
|
||||
/// `Children` cuando el padre sólo necesita un componente y quiere acceso directo a los métodos
|
||||
/// de `C`.
|
||||
/// - [`ChildOp`]: operaciones disponibles sobre la lista. Cuando se necesita algo más que añadir al
|
||||
/// final, se construye la variante adecuada y se pasa a [`with_child`](Self::with_child).
|
||||
/// - [`ComponentGuard`]: devuelto por [`Embed::get`] para garantizar acceso exclusivo al componente
|
||||
/// tipado. Mientras está activo bloquea cualquier otro acceso por lo que conviene liberarlo
|
||||
/// cuanto antes.
|
||||
///
|
||||
/// # Conversiones implícitas
|
||||
///
|
||||
|
|
@ -297,14 +310,14 @@ impl Children {
|
|||
}
|
||||
}
|
||||
|
||||
/// Añade un componente hijo al final de la lista.
|
||||
// Añade un componente hijo al final de la lista.
|
||||
#[inline]
|
||||
pub(crate) fn add(&mut self, child: Child) -> &mut Self {
|
||||
self.0.push(child);
|
||||
self
|
||||
}
|
||||
|
||||
/// Añade un componente hijo en la lista sólo si está vacía.
|
||||
// Añade un componente hijo en la lista sólo si está vacía.
|
||||
#[inline]
|
||||
pub(crate) fn add_if_empty(&mut self, child: Child) -> &mut Self {
|
||||
if self.0.is_empty() {
|
||||
|
|
@ -345,17 +358,17 @@ impl Children {
|
|||
// **< Children RENDER >************************************************************************
|
||||
|
||||
/// Renderiza todos los componentes hijo, en orden.
|
||||
pub fn render(&self, cx: &mut Context) -> Markup {
|
||||
pub async fn render(&self, cx: &mut Context) -> Markup {
|
||||
html! {
|
||||
@for c in &self.0 {
|
||||
(c.render(cx))
|
||||
(c.render(cx).await)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// **< Children HELPERS >***********************************************************************
|
||||
|
||||
/// Añade más de un componente hijo al final de la lista (en el orden recibido).
|
||||
// Añade más de un componente hijo al final de la lista (en el orden recibido).
|
||||
#[inline]
|
||||
fn add_many<I>(&mut self, iter: I) -> &mut Self
|
||||
where
|
||||
|
|
@ -365,7 +378,7 @@ impl Children {
|
|||
self
|
||||
}
|
||||
|
||||
/// Inserta un hijo después del componente con el `id` dado, o al final si no se encuentra.
|
||||
// Inserta un hijo después del componente con el `id` dado, o al final si no se encuentra.
|
||||
#[inline]
|
||||
fn insert_after_id(&mut self, id: impl AsRef<str>, child: Child) -> &mut Self {
|
||||
let id = Some(id.as_ref());
|
||||
|
|
@ -376,7 +389,7 @@ impl Children {
|
|||
self
|
||||
}
|
||||
|
||||
/// Inserta un hijo antes del componente con el `id` dado, o al principio si no se encuentra.
|
||||
// Inserta un hijo antes del componente con el `id` dado, o al principio si no se encuentra.
|
||||
#[inline]
|
||||
fn insert_before_id(&mut self, id: impl AsRef<str>, child: Child) -> &mut Self {
|
||||
let id = Some(id.as_ref());
|
||||
|
|
@ -387,14 +400,14 @@ impl Children {
|
|||
self
|
||||
}
|
||||
|
||||
/// Inserta un hijo al principio de la lista.
|
||||
// Inserta un hijo al principio de la lista.
|
||||
#[inline]
|
||||
fn prepend(&mut self, child: Child) -> &mut Self {
|
||||
self.0.insert(0, child);
|
||||
self
|
||||
}
|
||||
|
||||
/// Inserta más de un componente hijo al principio de la lista (manteniendo el orden recibido).
|
||||
// Inserta más de un componente hijo al principio de la lista (manteniendo el orden recibido).
|
||||
#[inline]
|
||||
fn prepend_many<I>(&mut self, iter: I) -> &mut Self
|
||||
where
|
||||
|
|
@ -405,7 +418,7 @@ impl Children {
|
|||
self
|
||||
}
|
||||
|
||||
/// Elimina el primer hijo con el `id` dado.
|
||||
// Elimina el primer hijo con el `id` dado.
|
||||
#[inline]
|
||||
fn remove_by_id(&mut self, id: impl AsRef<str>) -> &mut Self {
|
||||
let id = Some(id.as_ref());
|
||||
|
|
@ -415,7 +428,7 @@ impl Children {
|
|||
self
|
||||
}
|
||||
|
||||
/// Sustituye el primer hijo con el `id` dado por otro componente.
|
||||
// Sustituye el primer hijo con el `id` dado por otro componente.
|
||||
#[inline]
|
||||
fn replace_by_id(&mut self, id: impl AsRef<str>, child: Child) -> &mut Self {
|
||||
let id = Some(id.as_ref());
|
||||
|
|
@ -428,7 +441,7 @@ impl Children {
|
|||
self
|
||||
}
|
||||
|
||||
/// Elimina todos los componentes hijo de la lista.
|
||||
// Elimina todos los componentes hijo de la lista.
|
||||
#[inline]
|
||||
fn reset(&mut self) -> &mut Self {
|
||||
self.0.clear();
|
||||
|
|
@ -445,7 +458,7 @@ impl IntoIterator for Children {
|
|||
/// # Ejemplo
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// let children = Children::new().with(child1).with(child2);
|
||||
/// let children = Children::new().with_child(child1).with_child(child2);
|
||||
/// for child in children {
|
||||
/// println!("{:?}", child.id());
|
||||
/// }
|
||||
|
|
@ -464,7 +477,7 @@ impl<'a> IntoIterator for &'a Children {
|
|||
/// # Ejemplo
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// let children = Children::new().with(child1).with(child2);
|
||||
/// let children = Children::new().with_child(child1).with_child(child2);
|
||||
/// for child in &children {
|
||||
/// println!("{:?}", child.id());
|
||||
/// }
|
||||
|
|
@ -483,9 +496,9 @@ impl<'a> IntoIterator for &'a mut Children {
|
|||
/// # Ejemplo
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// let mut children = Children::new().with(child1).with(child2);
|
||||
/// let mut children = Children::new().with_child(child1).with_child(child2);
|
||||
/// for child in &mut children {
|
||||
/// child.render(&mut context);
|
||||
/// child.render(&mut context).await;
|
||||
/// }
|
||||
/// ```
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
|
|
|
|||
|
|
@ -10,8 +10,9 @@ use crate::locale::{LangId, LanguageIdentifier, RequestLocale};
|
|||
use crate::web::HttpRequest;
|
||||
use crate::{CowStr, builder_fn, util};
|
||||
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use std::any::{Any, TypeId};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
|
||||
|
|
@ -132,7 +133,7 @@ pub trait Contextual: LangId {
|
|||
/// .with_param("flags", vec!["a", "b"]);
|
||||
/// ```
|
||||
#[builder_fn]
|
||||
fn with_param<T: 'static>(self, key: &'static str, value: T) -> Self;
|
||||
fn with_param<T: Send + Sync + 'static>(self, key: &'static str, value: T) -> Self;
|
||||
|
||||
/// Define los recursos del contexto usando [`AssetsOp`].
|
||||
#[builder_fn]
|
||||
|
|
@ -172,7 +173,7 @@ pub trait Contextual: LangId {
|
|||
/// if page.current_user().is_authenticated() {
|
||||
/// // Personalizar la página para el usuario autenticado.
|
||||
/// }
|
||||
/// page.render()
|
||||
/// page.render().await
|
||||
/// }
|
||||
/// ```
|
||||
fn current_user(&self) -> &CurrentUser;
|
||||
|
|
@ -305,19 +306,19 @@ pub trait Contextual: LangId {
|
|||
/// ```
|
||||
#[rustfmt::skip]
|
||||
pub struct Context {
|
||||
request : Option<HttpRequest>, // Petición HTTP de origen.
|
||||
locale : RequestLocale, // Idioma asociado a la petición.
|
||||
current_user: CurrentUser, // Identidad del usuario actual.
|
||||
theme : ThemeRef, // Referencia al tema usado para renderizar.
|
||||
template : TemplateRef, // Plantilla usada para renderizar.
|
||||
favicon : Option<Favicon>, // Favicon, si se ha definido.
|
||||
stylesheets : Assets<StyleSheet>, // Hojas de estilo CSS.
|
||||
javascripts : Assets<JavaScript>, // Scripts JavaScript.
|
||||
body_props : Props, // Identificador, clases CSS y atributos del <body>.
|
||||
regions : ChildrenInRegions, // Regiones de componentes para renderizar.
|
||||
params : HashMap<&'static str, (Box<dyn Any>, &'static str)>, // Parámetros en ejecución.
|
||||
id_counters : RefCell<HashMap<TypeId, usize>>, // RefCell permite mutar desde build_id(&self).
|
||||
messages : Vec<StatusMessage>, // Mensajes de usuario acumulados.
|
||||
request : Option<HttpRequest>, // Petición HTTP de origen.
|
||||
locale : RequestLocale, // Idioma asociado a la petición.
|
||||
current_user: CurrentUser, // Identidad del usuario actual.
|
||||
theme : ThemeRef, // Referencia al tema usado para renderizar.
|
||||
template : TemplateRef, // Plantilla usada para renderizar.
|
||||
favicon : Option<Favicon>, // Favicon, si se ha definido.
|
||||
stylesheets : Assets<StyleSheet>, // Hojas de estilo CSS.
|
||||
javascripts : Assets<JavaScript>, // Scripts JavaScript.
|
||||
body_props : Props, // Id, clases CSS y atributos del <body>.
|
||||
regions : ChildrenInRegions, // Regiones de componentes para renderizar.
|
||||
params : HashMap<&'static str, (Box<dyn Any + Send + Sync>, &'static str)>, // Parámetros.
|
||||
id_counters : Mutex<HashMap<TypeId, usize>>, // Mutex permite mutar desde build_id(&self).
|
||||
messages : Vec<StatusMessage>, // Mensajes de usuario acumulados.
|
||||
}
|
||||
|
||||
impl Default for Context {
|
||||
|
|
@ -347,7 +348,7 @@ impl Context {
|
|||
body_props : Props::default(),
|
||||
regions : ChildrenInRegions::default(),
|
||||
params : HashMap::default(),
|
||||
id_counters: RefCell::new(HashMap::new()),
|
||||
id_counters: Mutex::new(HashMap::new()),
|
||||
messages : Vec::new(),
|
||||
}
|
||||
}
|
||||
|
|
@ -390,10 +391,11 @@ impl Context {
|
|||
}
|
||||
|
||||
/// Renderiza los componentes de una región.
|
||||
pub fn render_region(&mut self, region_ref: RegionRef) -> Markup {
|
||||
pub async fn render_region_named(&mut self, region_name: &str) -> Markup {
|
||||
self.regions
|
||||
.children_for(self.theme, region_ref)
|
||||
.assemble_region(self.theme, region_name)
|
||||
.render(self)
|
||||
.await
|
||||
}
|
||||
|
||||
// **< Context HELPERS >************************************************************************
|
||||
|
|
@ -436,7 +438,7 @@ impl Context {
|
|||
segments
|
||||
};
|
||||
let count = {
|
||||
let mut map = self.id_counters.borrow_mut();
|
||||
let mut map = self.id_counters.lock();
|
||||
let n = map.entry(TypeId::of::<C>()).or_insert(0);
|
||||
*n += 1;
|
||||
*n
|
||||
|
|
@ -529,7 +531,7 @@ impl Contextual for Context {
|
|||
}
|
||||
|
||||
#[builder_fn]
|
||||
fn with_param<T: 'static>(mut self, key: &'static str, value: T) -> Self {
|
||||
fn with_param<T: Send + Sync + 'static>(mut self, key: &'static str, value: T) -> Self {
|
||||
let type_name = TypeInfo::FullName.of::<T>();
|
||||
self.params.insert(key, (Box::new(value), type_name));
|
||||
self
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use crate::async_trait;
|
||||
use crate::base::action;
|
||||
use crate::core::component::{ComponentError, Context, Contextual};
|
||||
use crate::core::theme::ThemeRef;
|
||||
|
|
@ -7,8 +8,9 @@ use crate::html::{Markup, html};
|
|||
/// Habilita el clonado de componentes.
|
||||
///
|
||||
/// Se implementa automáticamente para todo tipo que implemente [`Component`] y [`Clone`]. El método
|
||||
/// [`clone_box`](Self::clone_box) devuelve una copia en la *pila* del componente original, lo que
|
||||
/// permite clonar componentes sin conocer su tipo concreto en tiempo de compilación.
|
||||
/// [`clone_box`](Self::clone_box) devuelve un clon del componente original encapsulado en un
|
||||
/// `Box<dyn Component>`, lo que permite clonar componentes sin conocer su tipo concreto en tiempo
|
||||
/// de compilación.
|
||||
pub trait ComponentClone {
|
||||
/// Devuelve un clon del componente encapsulado en un [`Box<dyn Component>`].
|
||||
fn clone_box(&self) -> Box<dyn Component>;
|
||||
|
|
@ -18,9 +20,10 @@ pub trait ComponentClone {
|
|||
///
|
||||
/// Este *trait* se implementa automáticamente en cualquier tipo (componente) que implemente
|
||||
/// [`Component`], por lo que no requiere ninguna codificación manual.
|
||||
#[async_trait]
|
||||
pub trait ComponentRender {
|
||||
/// Renderiza el componente usando el contexto proporcionado.
|
||||
fn render(&mut self, cx: &mut Context) -> Markup;
|
||||
async fn render(&mut self, cx: &mut Context) -> Markup;
|
||||
}
|
||||
|
||||
/// Interfaz común que debe implementar un componente renderizable en PageTop.
|
||||
|
|
@ -35,9 +38,10 @@ pub trait ComponentRender {
|
|||
///
|
||||
/// Todo tipo que implemente `Component` **debe** derivar también [`Clone`]. Aunque el compilador
|
||||
/// no lo exige directamente (hacerlo rompería la seguridad de objeto de `dyn Component`),
|
||||
/// [`ComponentClone`] se implementa automáticamente mediante una *impl* blanket solo para los tipos
|
||||
/// [`ComponentClone`] se implementa automáticamente mediante una *impl blanket* sólo para los tipos
|
||||
/// que sean `Component + Clone + 'static`. Sin `Clone`, habría que implementar [`ComponentClone`] a
|
||||
/// mano, y el componente no podría registrarse en [`InRegion`](crate::core::theme::InRegion).
|
||||
#[async_trait]
|
||||
pub trait Component: AnyInfo + ComponentClone + ComponentRender + Send + Sync {
|
||||
/// Crea una nueva instancia del componente.
|
||||
///
|
||||
|
|
@ -72,12 +76,12 @@ pub trait Component: AnyInfo + ComponentClone + ComponentRender + Send + Sync {
|
|||
///
|
||||
/// Por defecto, todos los componentes son renderizables (`true`). Sin embargo, este método
|
||||
/// puede sobrescribirse para decidir dinámicamente si los componentes de este tipo se
|
||||
/// renderizan o no en función del contexto de renderizado. Recibe solo una referencia
|
||||
/// renderizan o no en función del contexto de renderizado. Recibe sólo una referencia
|
||||
/// compartida al contexto porque su único propósito es consultar datos, no modificarlos.
|
||||
///
|
||||
/// También puede asignarse una función [`FnIsRenderable`](super::FnIsRenderable) a un campo del
|
||||
/// componente para permitir que instancias concretas del mismo puedan decidir dinámicamente si
|
||||
/// se renderizan o no.
|
||||
/// También **puede asignarse una función [`FnIsRenderable`](super::FnIsRenderable) a un campo
|
||||
/// del componente** para permitir que instancias concretas del mismo puedan decidir
|
||||
/// dinámicamente si se renderizan o no.
|
||||
#[allow(unused_variables)]
|
||||
fn is_renderable(&self, cx: &Context) -> bool {
|
||||
true
|
||||
|
|
@ -88,8 +92,19 @@ pub trait Component: AnyInfo + ComponentClone + ComponentRender + Send + Sync {
|
|||
/// Segundo paso del [ciclo de renderizado](ComponentRender): se ejecuta tras comprobar
|
||||
/// [`is_renderable()`](Self::is_renderable) y antes de la acción
|
||||
/// [`BeforeRender`](crate::base::action::component::BeforeRender) y de
|
||||
/// [`prepare()`](Self::prepare). Recibe solo una referencia compartida al contexto porque su
|
||||
/// [`prepare()`](Self::prepare). Recibe sólo una referencia compartida al contexto porque su
|
||||
/// propósito es mutar el propio componente, no el contexto. Por defecto no hace nada.
|
||||
///
|
||||
/// Está pensado para **normalizar el estado interno** del componente antes de renderizarlo. Por
|
||||
/// ejemplo, calcular clases CSS, ajustar valores de campos, derivar atributos a partir del
|
||||
/// contexto, etc. Se desaconseja utilizar para operaciones de E/S o consultas a base de datos;
|
||||
/// es intencionadamente síncrono.
|
||||
///
|
||||
/// La carga y el acceso a datos en general corresponden a [`prepare()`](Self::prepare), que es
|
||||
/// `async` precisamente para ello.
|
||||
///
|
||||
/// La separación entre `setup()` (mutación de estado) y [`prepare()`](Self::prepare)
|
||||
/// (generación de HTML) es deliberada y no debe fusionarse.
|
||||
#[allow(unused_variables)]
|
||||
fn setup(&mut self, cx: &Context) {}
|
||||
|
||||
|
|
@ -97,8 +112,8 @@ pub trait Component: AnyInfo + ComponentClone + ComponentRender + Send + Sync {
|
|||
///
|
||||
/// Cuarto paso del [ciclo de renderizado](ComponentRender): se invoca tras
|
||||
/// [`setup()`](Self::setup) y la acción
|
||||
/// [`BeforeRender`](crate::base::action::component::BeforeRender), pero solo si ningún tema
|
||||
/// en la cadena devuelve `Some` en
|
||||
/// [`BeforeRender`](crate::base::action::component::BeforeRender), pero solamente si ningún
|
||||
/// tema en la cadena devuelve `Some` en
|
||||
/// [`Theme::handle_component()`](crate::core::theme::Theme::handle_component).
|
||||
///
|
||||
/// Se recomienda obtener los datos del componente a través de sus propios métodos para que los
|
||||
|
|
@ -107,7 +122,7 @@ pub trait Component: AnyInfo + ComponentClone + ComponentRender + Send + Sync {
|
|||
/// Por defecto, devuelve un [`Markup`] vacío (`Ok(html! {})`). En caso de error, devuelve un
|
||||
/// [`ComponentError`] que puede incluir un marcado alternativo (*fallback*).
|
||||
#[allow(unused_variables)]
|
||||
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||
async fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||
Ok(html! {})
|
||||
}
|
||||
}
|
||||
|
|
@ -143,8 +158,9 @@ impl<T: Component + Clone + 'static> ComponentClone for T {
|
|||
/// para que las extensiones puedan trabajar sobre el HTML final para modificarlo antes de
|
||||
/// devolverlo.
|
||||
/// 7. Devuelve el [`Markup`] resultante.
|
||||
#[async_trait]
|
||||
impl<C: Component> ComponentRender for C {
|
||||
fn render(&mut self, cx: &mut Context) -> Markup {
|
||||
async fn render(&mut self, cx: &mut Context) -> Markup {
|
||||
// Si no es renderizable, devuelve un bloque HTML vacío.
|
||||
if !self.is_renderable(cx) {
|
||||
return html! {};
|
||||
|
|
@ -160,12 +176,12 @@ impl<C: Component> ComponentRender for C {
|
|||
let result = 'resolve: {
|
||||
let mut t: Option<ThemeRef> = Some(cx.theme());
|
||||
while let Some(theme) = t {
|
||||
if let Some(r) = theme.handle_component(self, cx) {
|
||||
if let Some(r) = theme.handle_component(self, cx).await {
|
||||
break 'resolve r;
|
||||
}
|
||||
t = theme.parent();
|
||||
}
|
||||
self.prepare(cx)
|
||||
self.prepare(cx).await
|
||||
};
|
||||
let prepare = match result {
|
||||
Ok(markup) => markup,
|
||||
|
|
|
|||
|
|
@ -13,9 +13,10 @@ use crate::{AutoDefault, Getters};
|
|||
/// # use pagetop::prelude::*;
|
||||
/// # #[derive(Clone)]
|
||||
/// # struct MyComponent;
|
||||
/// # #[async_trait]
|
||||
/// # impl Component for MyComponent {
|
||||
/// # fn new() -> Self { MyComponent }
|
||||
/// fn prepare(&self, _cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||
/// async fn prepare(&self, _cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||
/// Err(ComponentError::new("Database connection failed")
|
||||
/// .with_fallback(html! { p class="error" { "Content temporarily unavailable." } }))
|
||||
/// }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue