✨ Implementa temas hijo y macro render_component!
Añade `Theme::parent()` para declarar jerarquías de herencia entre temas. Sustituye la acción `PrepareRender<C>` por el método `Theme::prepare_component()` y la macro `render_component!`.
This commit is contained in:
parent
af309930f7
commit
4cbe84b4c0
14 changed files with 161 additions and 139 deletions
|
|
@ -26,7 +26,7 @@ según las necesidades de cada proyecto, incluyendo:
|
||||||
* **Extensiones** (*extensions*): añaden, extienden o personalizan funcionalidades usando las APIs
|
* **Extensiones** (*extensions*): añaden, extienden o personalizan funcionalidades usando las APIs
|
||||||
de PageTop o de terceros.
|
de PageTop o de terceros.
|
||||||
* **Temas** (*themes*): son extensiones que permiten modificar la apariencia de páginas y
|
* **Temas** (*themes*): son extensiones que permiten modificar la apariencia de páginas y
|
||||||
componentes sin comprometer su funcionalidad.
|
componentes. Pueden crearse temas hijo que heredan y refinan el comportamiento de su tema padre.
|
||||||
|
|
||||||
|
|
||||||
## ⚡️ Guía rápida
|
## ⚡️ Guía rápida
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,3 @@ pub use before_render_component::*;
|
||||||
|
|
||||||
mod after_render_component;
|
mod after_render_component;
|
||||||
pub use after_render_component::*;
|
pub use after_render_component::*;
|
||||||
|
|
||||||
mod prepare_render;
|
|
||||||
pub use prepare_render::*;
|
|
||||||
|
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
/// Tipo de función para alterar el renderizado de un componente.
|
|
||||||
///
|
|
||||||
/// Permite a un [tema](crate::base::action::theme) sobreescribir el renderizado predeterminado de
|
|
||||||
/// los componentes.
|
|
||||||
///
|
|
||||||
/// Recibe una referencia al componente `component` y una referencia mutable al contexto `cx`.
|
|
||||||
pub type FnPrepareRender<C> = fn(component: &C, cx: &mut Context) -> Result<Markup, ComponentError>;
|
|
||||||
|
|
||||||
/// Ejecuta [`FnPrepareRender`] para preparar el renderizado de un componente.
|
|
||||||
///
|
|
||||||
/// Permite a un tema hacer una implementación nueva del renderizado de un componente.
|
|
||||||
pub struct PrepareRender<C: Component> {
|
|
||||||
f: FnPrepareRender<C>,
|
|
||||||
theme_type_id: Option<UniqueId>,
|
|
||||||
referer_type_id: Option<UniqueId>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Filtro para despachar [`FnPrepareRender`] que modifica el renderizado de un componente `C`.
|
|
||||||
impl<C: Component> ActionDispatcher for PrepareRender<C> {
|
|
||||||
/// Devuelve el identificador de tipo ([`UniqueId`]) del tema.
|
|
||||||
fn theme_type_id(&self) -> Option<UniqueId> {
|
|
||||||
self.theme_type_id
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Devuelve el identificador de tipo ([`UniqueId`]) del componente `C`.
|
|
||||||
fn referer_type_id(&self) -> Option<UniqueId> {
|
|
||||||
self.referer_type_id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<C: Component> PrepareRender<C> {
|
|
||||||
/// Permite [registrar](Extension::actions) una nueva acción [`FnPrepareRender`] para un tema
|
|
||||||
/// dado.
|
|
||||||
pub fn new(theme: ThemeRef, f: FnPrepareRender<C>) -> Self {
|
|
||||||
PrepareRender {
|
|
||||||
f,
|
|
||||||
theme_type_id: Some(theme.type_id()),
|
|
||||||
referer_type_id: Some(UniqueId::of::<C>()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Despacha las acciones. Se detiene en cuanto una renderiza o produce un error.
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn dispatch(component: &C, cx: &mut Context) -> Result<Markup, ComponentError> {
|
|
||||||
let mut render_result: Result<Markup, ComponentError> = Ok(html! {});
|
|
||||||
dispatch_actions_until(
|
|
||||||
&ActionKey::new(
|
|
||||||
UniqueId::of::<Self>(),
|
|
||||||
Some(cx.theme().type_id()),
|
|
||||||
Some(UniqueId::of::<C>()),
|
|
||||||
None,
|
|
||||||
),
|
|
||||||
|action: &Self| match &render_result {
|
|
||||||
Ok(markup) if markup.is_empty() => {
|
|
||||||
render_result = (action.f)(component, cx);
|
|
||||||
std::ops::ControlFlow::Continue(())
|
|
||||||
}
|
|
||||||
_ => std::ops::ControlFlow::Break(()),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
render_result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -149,6 +149,8 @@ pub static CONFIG_VALUES: LazyLock<ConfigBuilder<DefaultState>> = LazyLock::new(
|
||||||
.expect("Failed to set application run mode")
|
.expect("Failed to set application run mode")
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// **< include_config! >****************************************************************************
|
||||||
|
|
||||||
/// Incluye los ajustes necesarios de la configuración anticipando valores por defecto.
|
/// Incluye los ajustes necesarios de la configuración anticipando valores por defecto.
|
||||||
///
|
///
|
||||||
/// # Sintaxis
|
/// # Sintaxis
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,9 @@ use list::ActionsList;
|
||||||
|
|
||||||
mod all;
|
mod all;
|
||||||
pub(crate) use all::add_action;
|
pub(crate) use all::add_action;
|
||||||
pub use all::{dispatch_actions, dispatch_actions_until};
|
pub use all::dispatch_actions;
|
||||||
|
|
||||||
|
// **< actions_boxed! >*****************************************************************************
|
||||||
|
|
||||||
/// Facilita la implementación del método [`actions()`](crate::core::extension::Extension::actions).
|
/// Facilita la implementación del método [`actions()`](crate::core::extension::Extension::actions).
|
||||||
///
|
///
|
||||||
|
|
@ -22,12 +24,11 @@ pub use all::{dispatch_actions, dispatch_actions_until};
|
||||||
/// # Ejemplo
|
/// # Ejemplo
|
||||||
///
|
///
|
||||||
/// ```rust,ignore
|
/// ```rust,ignore
|
||||||
/// # use pagetop::prelude::*;
|
|
||||||
/// impl Extension for MyTheme {
|
/// impl Extension for MyTheme {
|
||||||
/// fn actions(&self) -> Vec<ActionBox> {
|
/// fn actions(&self) -> Vec<ActionBox> {
|
||||||
/// actions_boxed![
|
/// actions_boxed![
|
||||||
/// action::theme::BeforeRender::<Button>::new(&Self, before_render_button),
|
/// action::theme::BeforeRender::<Button>::new(&Self, before_render_button),
|
||||||
/// action::theme::PrepareRender::<Error404>::new(&Self, render_error404),
|
/// action::theme::AfterRender::<Button>::new(&Self, after_render_button),
|
||||||
/// ]
|
/// ]
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
|
|
@ -35,7 +36,7 @@ pub use all::{dispatch_actions, dispatch_actions_until};
|
||||||
/// impl Theme for MyTheme {}
|
/// impl Theme for MyTheme {}
|
||||||
///
|
///
|
||||||
/// fn before_render_button(c: &mut Button, cx: &mut Context) { todo!() }
|
/// fn before_render_button(c: &mut Button, cx: &mut Context) { todo!() }
|
||||||
/// fn render_error404(c: &Error404, cx: &mut Context) -> Markup { todo!() }
|
/// fn after_render_button(c: &mut Button, cx: &mut Context) { todo!() }
|
||||||
/// ```
|
/// ```
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! actions_boxed {
|
macro_rules! actions_boxed {
|
||||||
|
|
|
||||||
|
|
@ -72,18 +72,3 @@ where
|
||||||
list.iter_map(f);
|
list.iter_map(f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Despacha las funciones asociadas a una [`ActionKey`] con posible salida anticipada.
|
|
||||||
///
|
|
||||||
/// Funciona igual que [`dispatch_actions`], pero el *closure* puede devolver
|
|
||||||
/// [`std::ops::ControlFlow::Continue`] para continuar ejecutando la siguiente acción; o
|
|
||||||
/// [`std::ops::ControlFlow::Break`] para detener la iteración inmediatamente.
|
|
||||||
pub fn dispatch_actions_until<A, F>(key: &ActionKey, f: F)
|
|
||||||
where
|
|
||||||
A: ActionDispatcher,
|
|
||||||
F: FnMut(&A) -> std::ops::ControlFlow<()>,
|
|
||||||
{
|
|
||||||
if let Some(list) = ACTIONS.read().get(key) {
|
|
||||||
list.iter_try_map(f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -39,21 +39,4 @@ impl ActionsList {
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iter_try_map<A, F>(&self, mut f: F)
|
|
||||||
where
|
|
||||||
A: ActionDispatcher,
|
|
||||||
F: FnMut(&A) -> std::ops::ControlFlow<()>,
|
|
||||||
{
|
|
||||||
let list = self.0.read();
|
|
||||||
for a in list.iter().rev() {
|
|
||||||
if let Some(action) = (**a).downcast_ref::<A>() {
|
|
||||||
if f(action).is_break() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
trace::error!("Failed to downcast action of type {}", (**a).type_name());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::base::action;
|
use crate::base::action;
|
||||||
use crate::core::component::{ComponentError, Context, Contextual};
|
use crate::core::component::{ComponentError, Context, Contextual};
|
||||||
|
use crate::core::theme::ThemeRef;
|
||||||
use crate::core::{AnyInfo, TypeInfo};
|
use crate::core::{AnyInfo, TypeInfo};
|
||||||
use crate::html::{html, Markup};
|
use crate::html::{html, Markup};
|
||||||
|
|
||||||
|
|
@ -70,16 +71,15 @@ pub trait Component: AnyInfo + ComponentRender + Send + Sync {
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
fn setup_before_prepare(&mut self, cx: &mut Context) {}
|
fn setup_before_prepare(&mut self, cx: &mut Context) {}
|
||||||
|
|
||||||
/// Devuelve el marcado HTML del componente usando el contexto proporcionado.
|
/// Versión del componente para preparar su propio renderizado.
|
||||||
///
|
///
|
||||||
/// Este método forma parte del ciclo de vida de los componentes y se invoca automáticamente
|
/// Este método forma parte del ciclo de vida de los componentes y se invoca automáticamente
|
||||||
/// durante el proceso de construcción del documento. Cada componente lo implementa para generar
|
/// durante el proceso de construcción del documento cuando ningún tema sobrescribe el
|
||||||
/// su propio contenido HTML. Los temas hijo pueden sobrescribir opcionalmente su resultado
|
/// renderizado mediante [`Theme::prepare_component()`](crate::core::theme::Theme::prepare_component).
|
||||||
/// mediante la acción [`PrepareRender`](crate::base::action::theme::PrepareRender).
|
|
||||||
///
|
///
|
||||||
/// Se recomienda obtener los datos del componente a través de sus propios métodos para que los
|
/// Se recomienda obtener los datos del componente a través de sus propios métodos para que los
|
||||||
/// temas hijo que implementen dicha acción puedan generar el nuevo HTML sin depender de los
|
/// temas puedan implementar [`Theme::prepare_component()`](crate::core::theme::Theme::prepare_component)
|
||||||
/// detalles internos del componente.
|
/// sin depender de los detalles internos del componente.
|
||||||
///
|
///
|
||||||
/// Por defecto, devuelve un [`Markup`] vacío (`Ok(html! {})`).
|
/// Por defecto, devuelve un [`Markup`] vacío (`Ok(html! {})`).
|
||||||
///
|
///
|
||||||
|
|
@ -104,10 +104,11 @@ pub trait Component: AnyInfo + ComponentRender + Send + Sync {
|
||||||
/// 4. Despacha [`action::component::BeforeRender<C>`](crate::base::action::component::BeforeRender)
|
/// 4. Despacha [`action::component::BeforeRender<C>`](crate::base::action::component::BeforeRender)
|
||||||
/// para que otras extensiones puedan también hacer ajustes previos.
|
/// para que otras extensiones puedan también hacer ajustes previos.
|
||||||
/// 5. **Prepara el renderizado del componente**:
|
/// 5. **Prepara el renderizado del componente**:
|
||||||
/// - Despacha [`action::theme::PrepareRender<C>`](crate::base::action::theme::PrepareRender)
|
/// - Recorre la cadena de temas llamando a
|
||||||
/// para permitir al tema generar un renderizado alternativo.
|
/// [`Theme::prepare_component()`](crate::core::theme::Theme::prepare_component) en cada nivel
|
||||||
/// - Si el tema no lo modifica, llama a [`prepare_component()`](Component::prepare_component)
|
/// (hijo → padre → abuelo…) hasta que uno devuelva `Some`.
|
||||||
/// para obtener el renderizado por defecto del componente.
|
/// - Si ningún tema lo sobrescribe, llama a
|
||||||
|
/// [`Component::prepare_component()`](Component::prepare_component) del propio componente.
|
||||||
/// 6. Despacha [`action::theme::AfterRender<C>`](crate::base::action::theme::AfterRender) para
|
/// 6. Despacha [`action::theme::AfterRender<C>`](crate::base::action::theme::AfterRender) para
|
||||||
/// que el tema pueda aplicar ajustes finales.
|
/// que el tema pueda aplicar ajustes finales.
|
||||||
/// 7. Despacha [`action::component::AfterRender<C>`](crate::base::action::component::AfterRender)
|
/// 7. Despacha [`action::component::AfterRender<C>`](crate::base::action::component::AfterRender)
|
||||||
|
|
@ -129,29 +130,23 @@ impl<C: Component> ComponentRender for C {
|
||||||
// Acciones de las extensiones antes de renderizar el componente.
|
// Acciones de las extensiones antes de renderizar el componente.
|
||||||
action::component::BeforeRender::dispatch(self, cx);
|
action::component::BeforeRender::dispatch(self, cx);
|
||||||
|
|
||||||
// Prepara el renderizado del componente.
|
// Prepara el renderizado: recorre la cadena de temas, luego el componente.
|
||||||
let prepare = match action::theme::PrepareRender::dispatch(self, cx) {
|
let prepare = match 'resolve: {
|
||||||
Ok(markup) if !markup.is_empty() => markup,
|
let mut t: Option<ThemeRef> = Some(cx.theme());
|
||||||
Ok(_) => match self.prepare_component(cx) {
|
while let Some(theme) = t {
|
||||||
Ok(markup) => markup,
|
if let Some(r) = theme.prepare_component(self, cx) {
|
||||||
Err(error) => {
|
break 'resolve r;
|
||||||
crate::trace::error!(
|
|
||||||
path = cx.request().map(|r| r.path()).unwrap_or("<unknown>"),
|
|
||||||
component = self.name(),
|
|
||||||
id = self.id().as_deref().unwrap_or("<not set>"),
|
|
||||||
source = "prepare_component",
|
|
||||||
"render failed, using fallback: {}",
|
|
||||||
error.message()
|
|
||||||
);
|
|
||||||
error.into_fallback()
|
|
||||||
}
|
}
|
||||||
},
|
t = theme.parent();
|
||||||
|
}
|
||||||
|
self.prepare_component(cx)
|
||||||
|
} {
|
||||||
|
Ok(markup) => markup,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
crate::trace::error!(
|
crate::trace::error!(
|
||||||
path = cx.request().map(|r| r.path()).unwrap_or("<unknown>"),
|
path = cx.request().map(|r| r.path()).unwrap_or("<unknown>"),
|
||||||
component = self.name(),
|
component = self.name(),
|
||||||
id = self.id().as_deref().unwrap_or("<not set>"),
|
id = self.id().as_deref().unwrap_or("<not set>"),
|
||||||
source = "PrepareRender",
|
|
||||||
"render failed, using fallback: {}",
|
"render failed, using fallback: {}",
|
||||||
error.message()
|
error.message()
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,12 @@
|
||||||
//! registran componentes en el [`Context`], las plantillas organizan las regiones y las páginas
|
//! registran componentes en el [`Context`], las plantillas organizan las regiones y las páginas
|
||||||
//! generan el documento HTML resultante.
|
//! generan el documento HTML resultante.
|
||||||
//!
|
//!
|
||||||
|
//! PageTop permite crear **temas hijo** que refinan el comportamiento de su tema padre. Un tema
|
||||||
|
//! hijo hereda automáticamente todos los métodos del padre y puede sobrescribirlos selectivamente:
|
||||||
|
//! por ejemplo, puede redefinir el renderizado de un componente con [`Theme::prepare_component()`]
|
||||||
|
//! sin modificar el resto del comportamiento heredado. Un tema hijo puede ser a su vez padre de
|
||||||
|
//! otro, basta declararlo cada vez con [`Theme::parent()`].
|
||||||
|
//!
|
||||||
//! Los temas pueden definir sus propias implementaciones de [`Template`] y [`Region`] (por ejemplo,
|
//! Los temas pueden definir sus propias implementaciones de [`Template`] y [`Region`] (por ejemplo,
|
||||||
//! mediante *enums* adicionales) para añadir nuevas plantillas o exponer regiones específicas.
|
//! mediante *enums* adicionales) para añadir nuevas plantillas o exponer regiones específicas.
|
||||||
|
|
||||||
|
|
@ -200,6 +206,44 @@ pub enum DefaultTemplate {
|
||||||
|
|
||||||
impl Template for DefaultTemplate {}
|
impl Template for DefaultTemplate {}
|
||||||
|
|
||||||
|
// **< render_component! >**************************************************************************
|
||||||
|
|
||||||
|
/// Sobrescribe el renderizado de componentes en la implementación de
|
||||||
|
/// [`Theme::prepare_component()`](crate::core::theme::Theme::prepare_component).
|
||||||
|
///
|
||||||
|
/// Evalúa `$component` contra cada tipo listado en orden. En cuanto encuentra coincidencia,
|
||||||
|
/// devuelve `Some(Ok(markup))` o `Some(Err(e))` según el resultado de la expresión asociada.
|
||||||
|
/// Si ningún tipo coincide, devuelve `None` para que el sistema continúe con la cadena de
|
||||||
|
/// herencia o con el renderizado por defecto del propio componente.
|
||||||
|
///
|
||||||
|
/// # Ejemplo
|
||||||
|
///
|
||||||
|
/// ```rust,ignore
|
||||||
|
/// fn prepare_component(
|
||||||
|
/// &self,
|
||||||
|
/// component: &dyn Component,
|
||||||
|
/// cx: &mut Context,
|
||||||
|
/// ) -> Option<Result<Markup, ComponentError>> {
|
||||||
|
/// render_component!(component, {
|
||||||
|
/// Button => |btn| Ok(html! { button.btn.btn-primary { (btn.label()) } }),
|
||||||
|
/// Heading => |h| Ok(html! { h2.display-4 { (h.text()) } }),
|
||||||
|
/// })
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! render_component {
|
||||||
|
($component:expr, { $($type:ty => |$var:ident| $body:expr),* $(,)? }) => {
|
||||||
|
'render_component: {
|
||||||
|
$(
|
||||||
|
if let Some($var) = ($component).downcast_ref::<$type>() {
|
||||||
|
break 'render_component Some($body);
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// **< Definitions >********************************************************************************
|
// **< Definitions >********************************************************************************
|
||||||
|
|
||||||
mod definition;
|
mod definition;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::base::component::{Html, Intro, IntroOpening};
|
use crate::base::component::{Html, Intro, IntroOpening};
|
||||||
use crate::core::component::{Child, ChildOp, Component, Contextual};
|
use crate::core::component::{Child, ChildOp, Component, ComponentError, Context, Contextual};
|
||||||
use crate::core::extension::Extension;
|
use crate::core::extension::Extension;
|
||||||
use crate::core::theme::{DefaultRegion, DefaultTemplate, TemplateRef};
|
use crate::core::theme::{DefaultRegion, DefaultTemplate, TemplateRef};
|
||||||
use crate::global;
|
use crate::global;
|
||||||
|
|
@ -45,6 +45,16 @@ use crate::service::http::StatusCode;
|
||||||
/// impl Theme for MyTheme {}
|
/// impl Theme for MyTheme {}
|
||||||
/// ```
|
/// ```
|
||||||
pub trait Theme: Extension + Send + Sync {
|
pub trait Theme: Extension + Send + Sync {
|
||||||
|
/// Devuelve el tema padre del que hereda este tema, si existe.
|
||||||
|
///
|
||||||
|
/// Un tema hijo delega automáticamente todos los métodos de esta interfaz al tema padre cuando
|
||||||
|
/// no los sobrescribe.
|
||||||
|
///
|
||||||
|
/// La implementación por defecto devuelve `None` (tema sin padre).
|
||||||
|
fn parent(&self) -> Option<ThemeRef> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
/// Devuelve la plantilla ([`Template`](crate::core::theme::Template)) que el propio tema
|
/// Devuelve la plantilla ([`Template`](crate::core::theme::Template)) que el propio tema
|
||||||
/// propone como predeterminada.
|
/// propone como predeterminada.
|
||||||
///
|
///
|
||||||
|
|
@ -57,7 +67,9 @@ pub trait Theme: Extension + Send + Sync {
|
||||||
/// seleccionar otra plantilla predeterminada o una plantilla propia.
|
/// seleccionar otra plantilla predeterminada o una plantilla propia.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn default_template(&self) -> TemplateRef {
|
fn default_template(&self) -> TemplateRef {
|
||||||
&DefaultTemplate::Standard
|
self.parent().map_or(&DefaultTemplate::Standard, |parent| {
|
||||||
|
parent.default_template()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Acciones específicas del tema antes de renderizar el `<body>` de la página.
|
/// Acciones específicas del tema antes de renderizar el `<body>` de la página.
|
||||||
|
|
@ -71,7 +83,11 @@ pub trait Theme: Extension + Send + Sync {
|
||||||
///
|
///
|
||||||
/// La implementación por defecto no realiza ninguna acción.
|
/// La implementación por defecto no realiza ninguna acción.
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
fn before_render_page_body(&self, page: &mut Page) {}
|
fn before_render_page_body(&self, page: &mut Page) {
|
||||||
|
if let Some(parent) = self.parent() {
|
||||||
|
parent.before_render_page_body(page);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Renderiza el contenido del `<body>` de la página.
|
/// Renderiza el contenido del `<body>` de la página.
|
||||||
///
|
///
|
||||||
|
|
@ -93,7 +109,11 @@ pub trait Theme: Extension + Send + Sync {
|
||||||
/// - Implementar lógicas de composición alternativas.
|
/// - Implementar lógicas de composición alternativas.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn render_page_body(&self, page: &mut Page) -> Markup {
|
fn render_page_body(&self, page: &mut Page) -> Markup {
|
||||||
page.template().render(page.context())
|
if let Some(parent) = self.parent() {
|
||||||
|
parent.render_page_body(page)
|
||||||
|
} else {
|
||||||
|
page.template().render(page.context())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Acciones específicas del tema después de renderizar el `<body>` de la página.
|
/// Acciones específicas del tema después de renderizar el `<body>` de la página.
|
||||||
|
|
@ -107,7 +127,11 @@ pub trait Theme: Extension + Send + Sync {
|
||||||
///
|
///
|
||||||
/// La implementación por defecto no realiza ninguna acción.
|
/// La implementación por defecto no realiza ninguna acción.
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
fn after_render_page_body(&self, page: &mut Page) {}
|
fn after_render_page_body(&self, page: &mut Page) {
|
||||||
|
if let Some(parent) = self.parent() {
|
||||||
|
parent.after_render_page_body(page);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Renderiza el contenido del `<head>` de la página.
|
/// Renderiza el contenido del `<head>` de la página.
|
||||||
///
|
///
|
||||||
|
|
@ -129,6 +153,9 @@ pub trait Theme: Extension + Send + Sync {
|
||||||
/// Los temas pueden sobrescribir este método para añadir etiquetas adicionales (por ejemplo,
|
/// Los temas pueden sobrescribir este método para añadir etiquetas adicionales (por ejemplo,
|
||||||
/// *favicons* personalizados, manifest, etiquetas de analítica, etc.).
|
/// *favicons* personalizados, manifest, etiquetas de analítica, etc.).
|
||||||
fn render_page_head(&self, page: &mut Page) -> Markup {
|
fn render_page_head(&self, page: &mut Page) -> Markup {
|
||||||
|
if let Some(parent) = self.parent() {
|
||||||
|
return parent.render_page_head(page);
|
||||||
|
}
|
||||||
let viewport = "width=device-width, initial-scale=1, shrink-to-fit=no";
|
let viewport = "width=device-width, initial-scale=1, shrink-to-fit=no";
|
||||||
html! {
|
html! {
|
||||||
meta charset="utf-8";
|
meta charset="utf-8";
|
||||||
|
|
@ -157,11 +184,53 @@ pub trait Theme: Extension + Send + Sync {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Permite sobrescribir el renderizado de un componente.
|
||||||
|
///
|
||||||
|
/// Este método tiene especial utilidad en los **temas hijo** porque permite sobrescribir el
|
||||||
|
/// renderizado que el propio componente o el tema padre ofrece para un componente concreto, sin
|
||||||
|
/// modificar el resto del comportamiento heredado.
|
||||||
|
///
|
||||||
|
/// Recibe una referencia al componente (como objeto dinámico [`Component`]) y el contexto de
|
||||||
|
/// renderizado. Devuelve:
|
||||||
|
///
|
||||||
|
/// - `None` si este tema no sobrescribe el componente. Es la implementación por defecto. En
|
||||||
|
/// este caso se recorre la cadena de temas padre y, si ninguno lo sobrescribe, se usa
|
||||||
|
/// [`Component::prepare_component()`](crate::core::component::Component::prepare_component).
|
||||||
|
/// - `Some(Ok(markup))` con el HTML generado por el tema para el componente.
|
||||||
|
/// - `Some(Err(e))` si el tema intentó renderizarlo pero falló.
|
||||||
|
///
|
||||||
|
/// La mejor manera de implementar este método es usando la macro [`render_component!`], que
|
||||||
|
/// determina el componente a renderizar y devuelve `None` si ninguno coincide:
|
||||||
|
///
|
||||||
|
/// ```rust,ignore
|
||||||
|
/// fn prepare_component(
|
||||||
|
/// &self,
|
||||||
|
/// component: &dyn Component,
|
||||||
|
/// cx: &mut Context,
|
||||||
|
/// ) -> Option<Result<Markup, ComponentError>> {
|
||||||
|
/// render_component!(component, {
|
||||||
|
/// Button => |btn| Ok(html! { button.btn.btn-primary { (btn.label()) } }),
|
||||||
|
/// Heading => |h| Ok(html! { h2.display-4 { (h.text()) } }),
|
||||||
|
/// })
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
fn prepare_component(
|
||||||
|
&self,
|
||||||
|
component: &dyn Component,
|
||||||
|
cx: &mut Context,
|
||||||
|
) -> Option<Result<Markup, ComponentError>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
/// Contenido predefinido para la página de error "*403 - Forbidden*" (acceso denegado).
|
/// Contenido predefinido para la página de error "*403 - Forbidden*" (acceso denegado).
|
||||||
///
|
///
|
||||||
/// Los temas pueden sobrescribir este método para personalizar el diseño y el contenido de la
|
/// Los temas pueden sobrescribir este método para personalizar el diseño y el contenido de la
|
||||||
/// página de error.
|
/// página de error.
|
||||||
fn error_403(&self, page: &mut Page) {
|
fn error_403(&self, page: &mut Page) {
|
||||||
|
if let Some(parent) = self.parent() {
|
||||||
|
return parent.error_403(page);
|
||||||
|
}
|
||||||
page.alter_title(L10n::l("error403_title"))
|
page.alter_title(L10n::l("error403_title"))
|
||||||
.alter_template(&DefaultTemplate::Error)
|
.alter_template(&DefaultTemplate::Error)
|
||||||
.alter_child_in(
|
.alter_child_in(
|
||||||
|
|
@ -182,6 +251,9 @@ pub trait Theme: Extension + Send + Sync {
|
||||||
/// Los temas pueden sobrescribir este método para personalizar el diseño y el contenido de la
|
/// Los temas pueden sobrescribir este método para personalizar el diseño y el contenido de la
|
||||||
/// página de error.
|
/// página de error.
|
||||||
fn error_404(&self, page: &mut Page) {
|
fn error_404(&self, page: &mut Page) {
|
||||||
|
if let Some(parent) = self.parent() {
|
||||||
|
return parent.error_404(page);
|
||||||
|
}
|
||||||
page.alter_title(L10n::l("error404_title"))
|
page.alter_title(L10n::l("error404_title"))
|
||||||
.alter_template(&DefaultTemplate::Error)
|
.alter_template(&DefaultTemplate::Error)
|
||||||
.alter_child_in(
|
.alter_child_in(
|
||||||
|
|
@ -209,6 +281,9 @@ pub trait Theme: Extension + Send + Sync {
|
||||||
/// Los temas pueden sobrescribir este método para personalizar el diseño y el contenido de la
|
/// Los temas pueden sobrescribir este método para personalizar el diseño y el contenido de la
|
||||||
/// página de error.
|
/// página de error.
|
||||||
fn error_fatal(&self, page: &mut Page, code: StatusCode, title: L10n, alert: L10n, help: L10n) {
|
fn error_fatal(&self, page: &mut Page, code: StatusCode, title: L10n, alert: L10n, help: L10n) {
|
||||||
|
if let Some(parent) = self.parent() {
|
||||||
|
return parent.error_fatal(page, code, title, alert, help);
|
||||||
|
}
|
||||||
page.alter_title(title)
|
page.alter_title(title)
|
||||||
.alter_template(&DefaultTemplate::Error)
|
.alter_template(&DefaultTemplate::Error)
|
||||||
.alter_child_in(
|
.alter_child_in(
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,7 @@ según las necesidades de cada proyecto, incluyendo:
|
||||||
* **Extensiones** (*extensions*): añaden, extienden o personalizan funcionalidades usando las APIs
|
* **Extensiones** (*extensions*): añaden, extienden o personalizan funcionalidades usando las APIs
|
||||||
de PageTop o de terceros.
|
de PageTop o de terceros.
|
||||||
* **Temas** (*themes*): son extensiones que permiten modificar la apariencia de páginas y
|
* **Temas** (*themes*): son extensiones que permiten modificar la apariencia de páginas y
|
||||||
componentes sin comprometer su funcionalidad.
|
componentes. Pueden crearse temas hijo que heredan y refinan el comportamiento de su tema padre.
|
||||||
|
|
||||||
|
|
||||||
# ⚡️ Guía rápida
|
# ⚡️ Guía rápida
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,8 @@ pub use request::RequestLocale;
|
||||||
mod l10n;
|
mod l10n;
|
||||||
pub use l10n::L10n;
|
pub use l10n::L10n;
|
||||||
|
|
||||||
|
// **< include_locales! >***************************************************************************
|
||||||
|
|
||||||
/// Incluye un conjunto de recursos **Fluent** con textos de traducción propios.
|
/// Incluye un conjunto de recursos **Fluent** con textos de traducción propios.
|
||||||
///
|
///
|
||||||
/// Esta macro integra en el binario de la aplicación los archivos FTL ubicados en los siguientes
|
/// Esta macro integra en el binario de la aplicación los archivos FTL ubicados en los siguientes
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@ pub use crate::include_locales;
|
||||||
pub use crate::static_files_service;
|
pub use crate::static_files_service;
|
||||||
// crate::core::action
|
// crate::core::action
|
||||||
pub use crate::actions_boxed;
|
pub use crate::actions_boxed;
|
||||||
|
// crate::core::theme
|
||||||
|
pub use crate::render_component;
|
||||||
|
|
||||||
// API.
|
// API.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ pub use pagetop_statics::ResourceFiles;
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use actix_web::test;
|
pub use actix_web::test;
|
||||||
|
|
||||||
|
// **< static_files_service! >**********************************************************************
|
||||||
|
|
||||||
/// Configura un servicio web para publicar archivos estáticos.
|
/// Configura un servicio web para publicar archivos estáticos.
|
||||||
///
|
///
|
||||||
/// La macro ofrece tres modos para configurar el servicio:
|
/// La macro ofrece tres modos para configurar el servicio:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue