use crate::base::action; use crate::core::component::{ComponentError, Context, Contextual}; use crate::core::theme::ThemeRef; use crate::core::{AnyInfo, TypeInfo}; use crate::html::{html, Markup}; /// Permite clonar un componente. /// /// 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. pub trait ComponentClone { /// Devuelve un clon del componente encapsulado en un [`Box`]. fn clone_box(&self) -> Box; } /// Define la función de renderizado para todos los componentes. /// /// Este *trait* se implementa automáticamente en cualquier tipo (componente) que implemente /// [`Component`], por lo que no requiere ninguna codificación manual. pub trait ComponentRender { /// Renderiza el componente usando el contexto proporcionado. fn render(&mut self, cx: &mut Context) -> Markup; } /// Interfaz común que debe implementar un componente renderizable en PageTop. /// /// Se recomienda que los componentes declaren sus campos como privados, que deriven /// [`AutoDefault`](crate::AutoDefault) o implementen [`Default`] para inicializarlos por defecto, y /// [`Getters`](crate::Getters) para acceder a sus datos. Deberán implementar explícitamente el /// método [`new()`](Self::new) y podrán sobrescribir los demás métodos para personalizar su /// comportamiento. /// /// # Requisito: derivar `Clone` /// /// 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 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). pub trait Component: AnyInfo + ComponentClone + ComponentRender + Send + Sync { /// Crea una nueva instancia del componente. /// /// Por convención suele devolver `Self::default()`. fn new() -> Self where Self: Sized; /// Devuelve el nombre del componente. /// /// Por defecto se obtiene del nombre corto del tipo usando [`TypeInfo::ShortName`]. fn name(&self) -> &'static str { TypeInfo::ShortName.of::() } /// Devuelve una descripción del componente, si existe. /// /// Por defecto, no se proporciona ninguna descripción (`None`). fn description(&self) -> Option { None } /// Devuelve el identificador del componente, si existe. /// /// Este identificador puede usarse para referenciar el componente en el HTML. Por defecto, no /// tiene ningún identificador (`None`). fn id(&self) -> Option { None } /// Indica si el componente es renderizable. /// /// 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 /// 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. #[allow(unused_variables)] fn is_renderable(&self, cx: &Context) -> bool { true } /// Configura el estado interno del componente antes de generar el marcado. /// /// 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 /// propósito es mutar el propio componente, no el contexto. Por defecto no hace nada. #[allow(unused_variables)] fn setup(&mut self, cx: &Context) {} /// Genera el marcado HTML del componente cuando ningún tema lo sobrescribe. /// /// 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 /// [`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 /// temas puedan implementar `handle_component()` sin depender de los detalles internos. /// /// 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 { Ok(html! {}) } } // ************************************************************************************************* impl ComponentClone for T { fn clone_box(&self) -> Box { Box::new(self.clone()) } } // ************************************************************************************************* /// Implementa [`render()`](ComponentRender::render) para todos los componentes. /// /// El proceso de renderizado de cada componente sigue esta secuencia: /// /// 1. Ejecuta [`is_renderable()`](Component::is_renderable) para ver si puede renderizarse en el /// contexto actual. Si no es así, devuelve un [`Markup`] vacío. /// 2. Ejecuta [`setup()`](Component::setup) para que el componente /// pueda ajustar su estructura interna. /// 3. Despacha [`action::component::BeforeRender`](crate::base::action::component::BeforeRender) /// para que las extensiones puedan hacer ajustes previos. /// 4. **Prepara el renderizado del componente** recorriendo la cadena de temas (hijo → padre → /// abuelo…) llamando a [`Theme::handle_component()`](crate::core::theme::Theme::handle_component) /// en cada nivel hasta que uno devuelva `Some`. Si ninguno lo sobrescribe, llama a /// [`Component::prepare()`](Component::prepare) del propio componente. /// 5. Despacha [`action::component::AfterRender`](crate::base::action::component::AfterRender) /// para que las extensiones puedan reaccionar con sus últimos ajustes. /// 6. Despacha [`action::component::TransformMarkup`](crate::base::action::component::TransformMarkup) /// para que las extensiones puedan modificar el HTML final antes de devolverlo. /// 7. Devuelve el [`Markup`] resultante. impl ComponentRender for C { 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! {}; } // Configura el componente antes de preparar. self.setup(cx); // Acciones de las extensiones antes de renderizar el componente. action::component::BeforeRender::dispatch(self, cx); // Prepara el renderizado: recorre la cadena de temas, luego el componente. let prepare = match 'resolve: { let mut t: Option = Some(cx.theme()); while let Some(theme) = t { if let Some(r) = theme.handle_component(self, cx) { break 'resolve r; } t = theme.parent(); } self.prepare(cx) } { Ok(markup) => markup, Err(error) => { crate::trace::error!( path = cx.request().map(|r| r.path()).unwrap_or(""), component = self.name(), id = self.id().as_deref().unwrap_or(""), "render failed, using fallback: {}", error.message() ); error.into_fallback() } }; // Acciones de las extensiones después de renderizar el componente. action::component::AfterRender::dispatch(self, cx); // Acciones de las extensiones que transforman el HTML final antes de devolverlo. action::component::TransformMarkup::dispatch(self, cx, prepare) } }