Compare commits
No commits in common. "28f1eee391f5baab386abb6934ab1c04c1c68155" and "f2a7507317dfd2127e9cbdced7e7ef9564bc6dec" have entirely different histories.
28f1eee391
...
f2a7507317
45 changed files with 226 additions and 253 deletions
|
|
@ -7,7 +7,7 @@ use crate::registry;
|
||||||
/// Construye el [`Nav`] en cada petición a partir de [`registry::admin_menu()`], ya filtrado por
|
/// Construye el [`Nav`] en cada petición a partir de [`registry::admin_menu()`], ya filtrado por
|
||||||
/// el usuario de la petición actual. Pensado para que un tema lo registre en su propia región de
|
/// el usuario de la petición actual. Pensado para que un tema lo registre en su propia región de
|
||||||
/// navegación (p. ej. `pagetop-bootsier` lo añade a su sidebar) e intercepte `Nav`/`nav::Item` en
|
/// navegación (p. ej. `pagetop-bootsier` lo añade a su sidebar) e intercepte `Nav`/`nav::Item` en
|
||||||
/// [`Theme::render_component()`](pagetop::core::theme::Theme::render_component) si quiere darle un
|
/// [`Theme::handle_component()`](pagetop::core::theme::Theme::handle_component) si quiere darle un
|
||||||
/// aspecto propio; sin intercepción, se renderiza con el marcado por defecto de [`Nav`].
|
/// aspecto propio; sin intercepción, se renderiza con el marcado por defecto de [`Nav`].
|
||||||
///
|
///
|
||||||
/// Sólo se renderiza en páginas creadas con
|
/// Sólo se renderiza en páginas creadas con
|
||||||
|
|
|
||||||
|
|
@ -276,7 +276,7 @@ pub fn global() -> &'static AdminRegistry {
|
||||||
/// Pensado para que un tema lo use como navegación de `CoreTemplates::Admin` (p. ej. un sidebar) --
|
/// Pensado para que un tema lo use como navegación de `CoreTemplates::Admin` (p. ej. un sidebar) --
|
||||||
/// ver [`crate::component::AdminMenu`]. `pagetop-admin` no impone ningún marcado propio: el
|
/// ver [`crate::component::AdminMenu`]. `pagetop-admin` no impone ningún marcado propio: el
|
||||||
/// [`Nav`] resultante se renderiza con su aspecto por defecto salvo que el tema lo intercepte en
|
/// [`Nav`] resultante se renderiza con su aspecto por defecto salvo que el tema lo intercepte en
|
||||||
/// [`Theme::render_component()`](pagetop::core::theme::Theme::render_component).
|
/// [`Theme::handle_component()`](pagetop::core::theme::Theme::handle_component).
|
||||||
pub fn admin_menu(cx: &Context) -> Nav {
|
pub fn admin_menu(cx: &Context) -> Nav {
|
||||||
let reg = global();
|
let reg = global();
|
||||||
let current_path = cx.request().map(|r| r.path()).unwrap_or("");
|
let current_path = cx.request().map(|r| r.path()).unwrap_or("");
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,11 @@ impl Theme for Bootsier {
|
||||||
theme::BootsierColors::from(intent).as_str()
|
theme::BootsierColors::from(intent).as_str()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_component(&self, component: &mut dyn Component, _cx: &mut Context) {
|
async fn handle_component(
|
||||||
|
&self,
|
||||||
|
component: &mut dyn Component,
|
||||||
|
cx: &mut Context,
|
||||||
|
) -> Option<Result<Markup, ComponentError>> {
|
||||||
setup_component!(component, {
|
setup_component!(component, {
|
||||||
Badge => |c| theme::bs::badge::setup(c),
|
Badge => |c| theme::bs::badge::setup(c),
|
||||||
Brand => |c| theme::bs::brand::setup(c),
|
Brand => |c| theme::bs::brand::setup(c),
|
||||||
|
|
@ -144,13 +148,7 @@ impl Theme for Bootsier {
|
||||||
form::select::Field => |c| theme::bs::form::select::setup(c),
|
form::select::Field => |c| theme::bs::form::select::setup(c),
|
||||||
form::Textarea => |c| theme::bs::form::textarea::setup(c),
|
form::Textarea => |c| theme::bs::form::textarea::setup(c),
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
async fn render_component(
|
|
||||||
&self,
|
|
||||||
component: &dyn Component,
|
|
||||||
cx: &mut Context,
|
|
||||||
) -> Option<Result<Markup, ComponentError>> {
|
|
||||||
render_component!(component, {
|
render_component!(component, {
|
||||||
layout::Region => |c| theme::bs::layout::region::render(c, cx).await?,
|
layout::Region => |c| theme::bs::layout::region::render(c, cx).await?,
|
||||||
layout::Template => |c| theme::bs::layout::template::render(c, cx).await?,
|
layout::Template => |c| theme::bs::layout::template::render(c, cx).await?,
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
//! la shell completa de AdminLTE 4 (barra superior + barra lateral + área de contenido), que se
|
//! la shell completa de AdminLTE 4 (barra superior + barra lateral + área de contenido), que se
|
||||||
//! activa creando la página con [`Page::admin()`](pagetop::response::Page::admin) en lugar de
|
//! activa creando la página con [`Page::admin()`](pagetop::response::Page::admin) en lugar de
|
||||||
//! [`Page::new()`](pagetop::response::Page::new). No define sus propias variantes de plantilla:
|
//! [`Page::new()`](pagetop::response::Page::new). No define sus propias variantes de plantilla:
|
||||||
//! intercepta el componente `Template` en `render_component()` (ver `bs::layout`).
|
//! intercepta el componente `Template` en `handle_component()` (ver `bs::layout`).
|
||||||
//!
|
//!
|
||||||
//! ```rust,no_run
|
//! ```rust,no_run
|
||||||
//! use pagetop::prelude::*;
|
//! use pagetop::prelude::*;
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ impl Component for Icon {
|
||||||
self.props.get_id()
|
self.props.get_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(&mut self, _cx: &mut Context) {
|
fn setup(&mut self, _cx: &Context) {
|
||||||
if !matches!(self.icon_kind(), IconKind::None) {
|
if !matches!(self.icon_kind(), IconKind::None) {
|
||||||
self.alter_prop(PropsOp::prepend_classes("icon"));
|
self.alter_prop(PropsOp::prepend_classes("icon"));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ pub enum BootsierRegions {
|
||||||
/// Los componentes registrados aquí se renderizan directamente dentro del
|
/// Los componentes registrados aquí se renderizan directamente dentro del
|
||||||
/// `<ul class="sidebar-menu">`, sin el `<div>` envolvente que añade
|
/// `<ul class="sidebar-menu">`, sin el `<div>` envolvente que añade
|
||||||
/// [`Region`](pagetop::base::component::layout::Region) por defecto --
|
/// [`Region`](pagetop::base::component::layout::Region) por defecto --
|
||||||
/// [`Bootsier`](crate::Bootsier) intercepta este componente en `render_component()` para
|
/// [`Bootsier`](crate::Bootsier) intercepta este componente en `handle_component()` para
|
||||||
/// renderizarlo así. Los elementos esperados son
|
/// renderizarlo así. Los elementos esperados son
|
||||||
/// [`bs::sidebar::Item`](crate::theme::bs::sidebar::Item) y
|
/// [`bs::sidebar::Item`](crate::theme::bs::sidebar::Item) y
|
||||||
/// [`bs::sidebar::Section`](crate::theme::bs::sidebar::Section).
|
/// [`bs::sidebar::Section`](crate::theme::bs::sidebar::Section).
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ impl Component for Offcanvas {
|
||||||
self.props.get_id()
|
self.props.get_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(&mut self, cx: &mut Context) {
|
fn setup(&mut self, cx: &Context) {
|
||||||
// Asegura que el panel tiene un identificador único.
|
// Asegura que el panel tiene un identificador único.
|
||||||
self.alter_prop(PropsOp::ensure_id(cx.build_id::<Self>(1)));
|
self.alter_prop(PropsOp::ensure_id(cx.build_id::<Self>(1)));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ use crate::tree::{MenuKey, MenuNode, TreeOptions, build_tree, try_resolve_menu_u
|
||||||
/// </nav>
|
/// </nav>
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// Los temas pueden sobreescribir el render con `render_component()`, tanto de `MenuBlock` como,
|
/// Los temas pueden sobreescribir el render con `handle_component()`, tanto de `MenuBlock` como,
|
||||||
/// más generalmente, de [`Nav`]/[`nav::Item`]/[`Dropdown`]/[`dropdown::Item`]. `pagetop-bootsier`
|
/// más generalmente, de [`Nav`]/[`nav::Item`]/[`Dropdown`]/[`dropdown::Item`]. `pagetop-bootsier`
|
||||||
/// ya intercepta `Dropdown` así (ver `theme::bs::dropdown`), y por tanto también los que cuelguen
|
/// ya intercepta `Dropdown` así (ver `theme::bs::dropdown`), y por tanto también los que cuelguen
|
||||||
/// de un `nav::Item::dropdown()`; `MenuBlock`, `Nav` y `Navbar` siguen sin interceptarse: Bootsier
|
/// de un `nav::Item::dropdown()`; `MenuBlock`, `Nav` y `Navbar` siguen sin interceptarse: Bootsier
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ impl Component for RoleTable {
|
||||||
self.props.get_id()
|
self.props.get_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(&mut self, _cx: &mut Context) {
|
fn setup(&mut self, _cx: &Context) {
|
||||||
self.alter_prop(PropsOp::set_id("role-table-wrapper"));
|
self.alter_prop(PropsOp::set_id("role-table-wrapper"));
|
||||||
self.alter_prop(PropsOp::prepend_classes("user-admin-table-wrapper"));
|
self.alter_prop(PropsOp::prepend_classes("user-admin-table-wrapper"));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ impl Component for UserTable {
|
||||||
self.props.get_id()
|
self.props.get_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(&mut self, _cx: &mut Context) {
|
fn setup(&mut self, _cx: &Context) {
|
||||||
self.alter_prop(PropsOp::set_id("user-table-wrapper"));
|
self.alter_prop(PropsOp::set_id("user-table-wrapper"));
|
||||||
self.alter_prop(PropsOp::prepend_classes("user-admin-table-wrapper"));
|
self.alter_prop(PropsOp::prepend_classes("user-admin-table-wrapper"));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ impl Component for Badge {
|
||||||
self.props.get_id()
|
self.props.get_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(&mut self, cx: &mut Context) {
|
fn setup(&mut self, cx: &Context) {
|
||||||
self.alter_prop(PropsOp::prepend_classes(util::join!(
|
self.alter_prop(PropsOp::prepend_classes(util::join!(
|
||||||
"badge badge-",
|
"badge badge-",
|
||||||
self.intent().color(cx)
|
self.intent().color(cx)
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ impl Component for Block {
|
||||||
self.props.get_id()
|
self.props.get_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(&mut self, cx: &mut Context) {
|
fn setup(&mut self, cx: &Context) {
|
||||||
// Asegura que el bloque tiene un identificador único.
|
// Asegura que el bloque tiene un identificador único.
|
||||||
self.alter_prop(PropsOp::ensure_id(cx.build_id::<Self>(1)));
|
self.alter_prop(PropsOp::ensure_id(cx.build_id::<Self>(1)));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ impl Component for Brand {
|
||||||
self.props.get_id()
|
self.props.get_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(&mut self, _cx: &mut Context) {
|
fn setup(&mut self, _cx: &Context) {
|
||||||
self.alter_prop(PropsOp::prepend_classes("brand"));
|
self.alter_prop(PropsOp::prepend_classes("brand"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ impl Component for Breadcrumb {
|
||||||
self.props.get_id()
|
self.props.get_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(&mut self, cx: &mut Context) {
|
fn setup(&mut self, cx: &Context) {
|
||||||
for crumb in self.crumbs.iter_mut() {
|
for crumb in self.crumbs.iter_mut() {
|
||||||
crumb.setup(cx);
|
crumb.setup(cx);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ impl Crumb {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normaliza la clase base según el papel del elemento. Sólo lo usa `Breadcrumb`.
|
// Normaliza la clase base según el papel del elemento. Sólo lo usa `Breadcrumb`.
|
||||||
pub(super) fn setup(&mut self, _cx: &mut Context) {
|
pub(super) fn setup(&mut self, _cx: &Context) {
|
||||||
if *self.is_current() {
|
if *self.is_current() {
|
||||||
self.alter_prop(PropsOp::prepend_classes("active"))
|
self.alter_prop(PropsOp::prepend_classes("active"))
|
||||||
.alter_prop(PropsOp::set("aria-current", "page"));
|
.alter_prop(PropsOp::set("aria-current", "page"));
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ impl Component for Button {
|
||||||
self.props.get_id()
|
self.props.get_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(&mut self, cx: &mut Context) {
|
fn setup(&mut self, cx: &Context) {
|
||||||
use button::{Size, Style};
|
use button::{Size, Style};
|
||||||
|
|
||||||
self.alter_prop(PropsOp::prepend_classes(match self.size() {
|
self.alter_prop(PropsOp::prepend_classes(match self.size() {
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ impl Component for Container {
|
||||||
self.props.get_id()
|
self.props.get_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(&mut self, _cx: &mut Context) {
|
fn setup(&mut self, _cx: &Context) {
|
||||||
if let Some(flex) = self.flex() {
|
if let Some(flex) = self.flex() {
|
||||||
flex.apply_to(&mut self.props);
|
flex.apply_to(&mut self.props);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ impl Component for Dialog {
|
||||||
self.props.get_id()
|
self.props.get_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(&mut self, cx: &mut Context) {
|
fn setup(&mut self, cx: &Context) {
|
||||||
// Asegura que el diálogo tiene un identificador único con el que abrirlo.
|
// Asegura que el diálogo tiene un identificador único con el que abrirlo.
|
||||||
self.alter_prop(PropsOp::ensure_id(cx.build_id::<Self>(1)));
|
self.alter_prop(PropsOp::ensure_id(cx.build_id::<Self>(1)));
|
||||||
self.alter_prop(PropsOp::prepend_classes("dialog"));
|
self.alter_prop(PropsOp::prepend_classes("dialog"));
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ impl Component for Dropdown {
|
||||||
self.props.get_id()
|
self.props.get_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(&mut self, _cx: &mut Context) {
|
fn setup(&mut self, _cx: &Context) {
|
||||||
self.alter_prop(PropsOp::prepend_classes("dropdown"));
|
self.alter_prop(PropsOp::prepend_classes("dropdown"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -123,7 +123,7 @@ impl Component for Field {
|
||||||
self.props.get_id()
|
self.props.get_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(&mut self, cx: &mut Context) {
|
fn setup(&mut self, cx: &Context) {
|
||||||
// Asegura `name` e `id`.
|
// Asegura `name` e `id`.
|
||||||
// Si falta uno se deriva del otro; si faltan ambos se genera un valor único.
|
// Si falta uno se deriva del otro; si faltan ambos se genera un valor único.
|
||||||
let name = self
|
let name = self
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ impl Component for Checkbox {
|
||||||
self.props.get_id()
|
self.props.get_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(&mut self, cx: &mut Context) {
|
fn setup(&mut self, cx: &Context) {
|
||||||
// Asegura `name` e `id`.
|
// Asegura `name` e `id`.
|
||||||
// Si falta uno se deriva del otro; si faltan ambos se genera un valor único.
|
// Si falta uno se deriva del otro; si faltan ambos se genera un valor único.
|
||||||
let name = self
|
let name = self
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ impl Component for Form {
|
||||||
self.props.get_id()
|
self.props.get_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(&mut self, _cx: &mut Context) {
|
fn setup(&mut self, _cx: &Context) {
|
||||||
self.alter_prop(PropsOp::prepend_classes("form"));
|
self.alter_prop(PropsOp::prepend_classes("form"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -199,7 +199,7 @@ impl Component for Field {
|
||||||
self.props.get_id()
|
self.props.get_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(&mut self, _cx: &mut Context) {
|
fn setup(&mut self, _cx: &Context) {
|
||||||
if let Some(container_id) = self
|
if let Some(container_id) = self
|
||||||
.id()
|
.id()
|
||||||
.or_else(|| self.name().as_deref().map(|n| util::join!("edit-", n)))
|
.or_else(|| self.name().as_deref().map(|n| util::join!("edit-", n)))
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ impl Component for Number {
|
||||||
self.props.get_id()
|
self.props.get_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(&mut self, _cx: &mut Context) {
|
fn setup(&mut self, _cx: &Context) {
|
||||||
if let Some(container_id) = self
|
if let Some(container_id) = self
|
||||||
.id()
|
.id()
|
||||||
.or_else(|| self.name().as_deref().map(|n| util::join!("edit-", n)))
|
.or_else(|| self.name().as_deref().map(|n| util::join!("edit-", n)))
|
||||||
|
|
|
||||||
|
|
@ -123,7 +123,7 @@ impl Component for Field {
|
||||||
self.props.get_id()
|
self.props.get_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(&mut self, cx: &mut Context) {
|
fn setup(&mut self, cx: &Context) {
|
||||||
// Asegura `name` e `id`.
|
// Asegura `name` e `id`.
|
||||||
// Si falta uno se deriva del otro; si faltan ambos se genera un valor único.
|
// Si falta uno se deriva del otro; si faltan ambos se genera un valor único.
|
||||||
let name = self
|
let name = self
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ impl Component for Range {
|
||||||
self.props.get_id()
|
self.props.get_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(&mut self, _cx: &mut Context) {
|
fn setup(&mut self, _cx: &Context) {
|
||||||
if let Some(container_id) = self
|
if let Some(container_id) = self
|
||||||
.id()
|
.id()
|
||||||
.or_else(|| self.name().as_deref().map(|n| util::join!("edit-", n)))
|
.or_else(|| self.name().as_deref().map(|n| util::join!("edit-", n)))
|
||||||
|
|
|
||||||
|
|
@ -225,7 +225,7 @@ impl Component for Field {
|
||||||
self.props.get_id()
|
self.props.get_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(&mut self, _cx: &mut Context) {
|
fn setup(&mut self, _cx: &Context) {
|
||||||
if let Some(container_id) = self
|
if let Some(container_id) = self
|
||||||
.id()
|
.id()
|
||||||
.or_else(|| self.name().as_deref().map(|n| util::join!("edit-", n)))
|
.or_else(|| self.name().as_deref().map(|n| util::join!("edit-", n)))
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ impl Component for Textarea {
|
||||||
self.props.get_id()
|
self.props.get_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(&mut self, _cx: &mut Context) {
|
fn setup(&mut self, _cx: &Context) {
|
||||||
if let Some(container_id) = self
|
if let Some(container_id) = self
|
||||||
.id()
|
.id()
|
||||||
.or_else(|| self.name().as_deref().map(|n| util::join!("edit-", n)))
|
.or_else(|| self.name().as_deref().map(|n| util::join!("edit-", n)))
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ impl Component for Image {
|
||||||
self.props.get_id()
|
self.props.get_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(&mut self, _cx: &mut Context) {
|
fn setup(&mut self, _cx: &Context) {
|
||||||
self.alter_prop(PropsOp::prepend_classes(match self.source() {
|
self.alter_prop(PropsOp::prepend_classes(match self.source() {
|
||||||
image::Source::Logo(_) => "image image-fluid",
|
image::Source::Logo(_) => "image image-fluid",
|
||||||
image::Source::Responsive(_) => "image image-fluid",
|
image::Source::Responsive(_) => "image image-fluid",
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,8 @@ use std::fmt;
|
||||||
/// se renderiza nada.
|
/// se renderiza nada.
|
||||||
///
|
///
|
||||||
/// Si un tema necesita maquetar una región determinada de forma distinta, puede capturar este
|
/// Si un tema necesita maquetar una región determinada de forma distinta, puede capturar este
|
||||||
/// componente en [`Theme::render_component()`](crate::core::theme::Theme::render_component) y
|
/// componente en [`Theme::handle_component()`](crate::core::theme::Theme::handle_component) y hacer
|
||||||
/// hacer [`downcast_ref()`](crate::core::AnyCast::downcast_ref) sobre el [`RegionRef`] que devuelve
|
/// [`downcast_ref()`](crate::core::AnyCast::downcast_ref) sobre el [`RegionRef`] que devuelve
|
||||||
/// [`Self::region()`], para compararlo con la variante deseada.
|
/// [`Self::region()`], para compararlo con la variante deseada.
|
||||||
///
|
///
|
||||||
/// Como cualquier otro componente, participa también en el despacho de las
|
/// Como cualquier otro componente, participa también en el despacho de las
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ use std::fmt;
|
||||||
/// independientemente de la plantilla que se use.
|
/// independientemente de la plantilla que se use.
|
||||||
///
|
///
|
||||||
/// Si un tema necesita maquetar una plantilla determinada de forma distinta, puede capturar este
|
/// Si un tema necesita maquetar una plantilla determinada de forma distinta, puede capturar este
|
||||||
/// componente en [`Theme::render_component()`] y hacer [`downcast_ref()`] sobre el [`TemplateRef`]
|
/// componente en [`Theme::handle_component()`] y hacer [`downcast_ref()`] sobre el [`TemplateRef`]
|
||||||
/// que devuelve [`Self::template()`], para compararlo con la variante deseada.
|
/// que devuelve [`Self::template()`], para compararlo con la variante deseada.
|
||||||
///
|
///
|
||||||
/// Como cualquier otro componente, participa también en el despacho de las
|
/// Como cualquier otro componente, participa también en el despacho de las
|
||||||
|
|
@ -28,7 +28,7 @@ use std::fmt;
|
||||||
/// [`ReservedRegions::PageBottom`]: crate::response::ReservedRegions::PageBottom
|
/// [`ReservedRegions::PageBottom`]: crate::response::ReservedRegions::PageBottom
|
||||||
/// [`Page::render()`]: crate::response::Page::render
|
/// [`Page::render()`]: crate::response::Page::render
|
||||||
/// [`Theme::render_page_body()`]: crate::core::theme::Theme::render_page_body
|
/// [`Theme::render_page_body()`]: crate::core::theme::Theme::render_page_body
|
||||||
/// [`Theme::render_component()`]: crate::core::theme::Theme::render_component
|
/// [`Theme::handle_component()`]: crate::core::theme::Theme::handle_component
|
||||||
/// [`downcast_ref()`]: crate::core::AnyCast::downcast_ref
|
/// [`downcast_ref()`]: crate::core::AnyCast::downcast_ref
|
||||||
#[derive(Clone, Getters)]
|
#[derive(Clone, Getters)]
|
||||||
pub struct Template {
|
pub struct Template {
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ impl Component for Messages {
|
||||||
self.props.get_id()
|
self.props.get_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(&mut self, _cx: &mut Context) {
|
fn setup(&mut self, _cx: &Context) {
|
||||||
self.alter_prop(PropsOp::prepend_classes("messages"));
|
self.alter_prop(PropsOp::prepend_classes("messages"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ impl Component for Nav {
|
||||||
self.props.get_id()
|
self.props.get_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(&mut self, _cx: &mut Context) {
|
fn setup(&mut self, _cx: &Context) {
|
||||||
self.alter_prop(PropsOp::prepend_classes(match self.nav_layout() {
|
self.alter_prop(PropsOp::prepend_classes(match self.nav_layout() {
|
||||||
nav::Layout::Default => "nav",
|
nav::Layout::Default => "nav",
|
||||||
nav::Layout::Start => "nav nav-start",
|
nav::Layout::Start => "nav nav-start",
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ impl Component for Item {
|
||||||
self.props.get_id()
|
self.props.get_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(&mut self, _cx: &mut Context) {
|
fn setup(&mut self, _cx: &Context) {
|
||||||
self.alter_prop(PropsOp::prepend_classes(self.item_kind().as_str()));
|
self.alter_prop(PropsOp::prepend_classes(self.item_kind().as_str()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ impl Component for Navbar {
|
||||||
self.props.get_id()
|
self.props.get_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(&mut self, cx: &mut Context) {
|
fn setup(&mut self, cx: &Context) {
|
||||||
// Asegura que la barra de navegación tiene un identificador único: lo necesita el botón de
|
// Asegura que la barra de navegación tiene un identificador único: lo necesita el botón de
|
||||||
// despliegue para referenciar el contenido colapsable con `aria-controls`.
|
// despliegue para referenciar el contenido colapsable con `aria-controls`.
|
||||||
self.alter_prop(PropsOp::ensure_id(cx.build_id::<Self>(1)));
|
self.alter_prop(PropsOp::ensure_id(cx.build_id::<Self>(1)));
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ impl Component for Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(&mut self, _cx: &mut Context) {
|
fn setup(&mut self, _cx: &Context) {
|
||||||
if let Self::Nav(nav) = self
|
if let Self::Nav(nav) = self
|
||||||
&& let Some(nav) = nav.get_mut()
|
&& let Some(nav) = nav.get_mut()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -184,7 +184,7 @@ impl Component for Pager {
|
||||||
self.props.get_id()
|
self.props.get_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(&mut self, cx: &mut Context) {
|
fn setup(&mut self, cx: &Context) {
|
||||||
// Asegura un `id` propio si no está definido. El formulario de salto a página deriva sus
|
// Asegura un `id` propio si no está definido. El formulario de salto a página deriva sus
|
||||||
// identificadores de éste para no colisionar si hay varios paginadores en la misma página.
|
// identificadores de éste para no colisionar si hay varios paginadores en la misma página.
|
||||||
let id = cx.required_id::<Self>(self.id(), 1);
|
let id = cx.required_id::<Self>(self.id(), 1);
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ impl Component for Table {
|
||||||
self.props.get_id()
|
self.props.get_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(&mut self, _cx: &mut Context) {
|
fn setup(&mut self, _cx: &Context) {
|
||||||
self.alter_prop(PropsOp::prepend_classes("table"));
|
self.alter_prop(PropsOp::prepend_classes("table"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -89,45 +89,38 @@ pub trait Component: AnyInfo + ComponentClone + ComponentRender + Send + Sync {
|
||||||
|
|
||||||
/// Configura el estado interno del componente antes de generar el marcado.
|
/// Configura el estado interno del componente antes de generar el marcado.
|
||||||
///
|
///
|
||||||
/// Segundo paso del [ciclo de renderizado](ComponentRender). Se ejecuta tras comprobar
|
/// Segundo paso del [ciclo de renderizado](ComponentRender): se ejecuta tras comprobar
|
||||||
/// [`is_renderable()`] y antes de la acción [`BeforeRender`] y de [`prepare()`]. Por defecto no
|
/// [`is_renderable()`](Self::is_renderable) y antes de la acción
|
||||||
/// hace nada.
|
/// [`BeforeRender`](crate::base::action::component::BeforeRender) y de
|
||||||
|
/// [`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
|
/// 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
|
/// ejemplo, calcular clases CSS, ajustar valores de campos, derivar atributos a partir del
|
||||||
/// contexto, etc. Recibe `&mut Context` donde también puede aplicar ajustes que dependan del
|
/// contexto, etc. Se desaconseja utilizar para operaciones de E/S o consultas a base de datos;
|
||||||
/// propio componente (p. ej. estilos o *assets*); cualquier extensión que intercepte
|
/// es intencionadamente síncrono.
|
||||||
/// [`BeforeRender`] verá ya aplicados esos cambios, porque `setup()` se ejecuta antes. Se
|
|
||||||
/// desaconseja utilizar para operaciones de E/S o consultas a base de datos; es
|
|
||||||
/// intencionadamente síncrono.
|
|
||||||
///
|
///
|
||||||
/// La frontera con [`prepare()`] es de **sincronía**, no de qué puede modificar cada uno. Los
|
/// La carga y el acceso a datos en general corresponden a [`prepare()`](Self::prepare), que es
|
||||||
/// dos reciben `&mut Context` (ambos pueden realizar ajustes en él), pero sólo `setup()` recibe
|
/// `async` precisamente para ello.
|
||||||
/// además `&mut self` (`prepare()` sólo recibe `&self`, así que no puede normalizar el propio
|
|
||||||
/// componente). La carga y el acceso a datos siguen correspondiendo a `prepare()`, que es
|
|
||||||
/// `async` precisamente para permitir E/S; `setup()` se mantiene síncrono a propósito. Esta
|
|
||||||
/// separación es deliberada y no debe fusionarse.
|
|
||||||
///
|
///
|
||||||
/// [`is_renderable()`]: Self::is_renderable
|
/// La separación entre `setup()` (mutación de estado) y [`prepare()`](Self::prepare)
|
||||||
/// [`prepare()`]: Self::prepare
|
/// (generación de HTML) es deliberada y no debe fusionarse.
|
||||||
/// [`BeforeRender`]: crate::base::action::component::BeforeRender
|
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
fn setup(&mut self, cx: &mut Context) {}
|
fn setup(&mut self, cx: &Context) {}
|
||||||
|
|
||||||
/// Genera el marcado HTML del componente cuando ningún tema lo sobrescribe.
|
/// Genera el marcado HTML del componente cuando ningún tema lo sobrescribe.
|
||||||
///
|
///
|
||||||
/// Este es el cuarto paso del [ciclo de renderizado](ComponentRender) tras llamar al
|
/// Este es el cuarto paso del [ciclo de renderizado](ComponentRender) tras llamar al
|
||||||
/// [`setup()`] del componente y despachar la acción [`BeforeRender`] que atiende los cambios de
|
/// [`setup()`] del componente y despachar la acción [`BeforeRender`] que atiende los cambios de
|
||||||
/// otras extensiones antes de renderizar. Se invoca sólo si ningún tema en la cadena devuelve
|
/// otras extensiones antes de renderizar. Se invoca sólo si ningún tema en la cadena devuelve
|
||||||
/// `Some` en [`Theme::render_component()`] para este componente.
|
/// `Some` en [`Theme::handle_component()`] para este componente.
|
||||||
///
|
///
|
||||||
/// Es `async`, a diferencia de [`setup()`], para permitir a los componentes realizar aquí sus
|
/// Es `async`, a diferencia de [`setup()`], para permitir a los componentes realizar aquí sus
|
||||||
/// llamadas asíncronas, como consultas a base de datos, peticiones a servicios externos, o
|
/// llamadas asíncronas, como consultas a base de datos, peticiones a servicios externos, o
|
||||||
/// cualquier operación de E/S, que necesiten para preparar su contenido.
|
/// cualquier operación de E/S, que necesiten para preparar su contenido.
|
||||||
///
|
///
|
||||||
/// 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 puedan implementar [`Theme::setup_component()`]/[`Theme::render_component()`] sin
|
/// temas puedan implementar `handle_component()` sin depender de los detalles internos.
|
||||||
/// depender de los detalles internos.
|
|
||||||
///
|
///
|
||||||
/// Los campos que representen contenido no deben almacenar [`Markup`] ya generado, sino que
|
/// Los campos que representen contenido no deben almacenar [`Markup`] ya generado, sino que
|
||||||
/// guardarán el dato en bruto, por ejemplo [`Lc`] para textos traducibles, o un componente
|
/// guardarán el dato en bruto, por ejemplo [`Lc`] para textos traducibles, o un componente
|
||||||
|
|
@ -139,13 +132,12 @@ pub trait Component: AnyInfo + ComponentClone + ComponentRender + Send + Sync {
|
||||||
/// [`ComponentError`] que puede incluir un marcado alternativo (*fallback*).
|
/// [`ComponentError`] que puede incluir un marcado alternativo (*fallback*).
|
||||||
///
|
///
|
||||||
/// [`setup()`]: Self::setup
|
/// [`setup()`]: Self::setup
|
||||||
|
/// [`BeforeRender`]: crate::base::action::component::BeforeRender
|
||||||
|
/// [`Theme::handle_component()`]: crate::core::theme::Theme::handle_component
|
||||||
/// [`Lc`]: crate::locale::Lc
|
/// [`Lc`]: crate::locale::Lc
|
||||||
/// [`Theme::setup_component()`]: crate::core::theme::Theme::setup_component
|
/// [`Html`]: crate::base::component::Html
|
||||||
/// [`Theme::render_component()`]: crate::core::theme::Theme::render_component
|
|
||||||
/// [`Embed`]: crate::core::component::Embed
|
/// [`Embed`]: crate::core::component::Embed
|
||||||
/// [`Child`]: crate::core::component::Child
|
/// [`Child`]: crate::core::component::Child
|
||||||
/// [`BeforeRender`]: crate::base::action::component::BeforeRender
|
|
||||||
/// [`Html`]: crate::base::component::Html
|
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
async fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
async fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||||
Ok(html! {})
|
Ok(html! {})
|
||||||
|
|
@ -162,35 +154,27 @@ impl<T: Component + Clone + 'static> ComponentClone for T {
|
||||||
|
|
||||||
// *************************************************************************************************
|
// *************************************************************************************************
|
||||||
|
|
||||||
/// Implementa [`render()`] para todos los componentes.
|
/// Implementa [`render()`](ComponentRender::render) para todos los componentes.
|
||||||
///
|
///
|
||||||
/// El proceso de renderizado de cada componente sigue esta secuencia:
|
/// El proceso de renderizado de cada componente sigue esta secuencia:
|
||||||
///
|
///
|
||||||
/// 1. Ejecuta [`is_renderable()`] para ver si puede renderizarse en el contexto actual. Si no es
|
/// 1. Ejecuta [`is_renderable()`](Component::is_renderable) para ver si puede renderizarse en el
|
||||||
/// así, devuelve un [`Markup`] vacío.
|
/// contexto actual. Si no es así, devuelve un [`Markup`] vacío.
|
||||||
/// 2. Ejecuta [`setup()`] para que el componente pueda ajustar su estado interno y, de forma
|
/// 2. Ejecuta [`setup()`](Component::setup) para que el componente
|
||||||
/// síncrona, el propio [`Context`].
|
/// pueda ajustar su estructura interna.
|
||||||
/// 3. Despacha [`action::component::BeforeRender<C>`] para que las extensiones puedan hacer ajustes
|
/// 3. Despacha [`action::component::BeforeRender<C>`](crate::base::action::component::BeforeRender)
|
||||||
/// previos.
|
/// para que las extensiones puedan hacer ajustes previos.
|
||||||
/// 4. Prepara el renderizado del componente, recorre la cadena de temas (hijo > padre > abuelo...)
|
/// 4. Prepara el renderizado del componente, recorre la cadena de temas (hijo > padre > abuelo...)
|
||||||
/// llamando en cada nivel primero a [`Theme::setup_component()`] y después a
|
/// llamando a [`Theme::handle_component()`](crate::core::theme::Theme::handle_component) en cada
|
||||||
/// [`Theme::render_component()`], hasta que uno devuelva `Some`. Si ninguno lo sobrescribe,
|
/// nivel hasta que uno devuelva `Some`. Si ninguno lo sobrescribe, llama al
|
||||||
/// llama al [`Component::prepare()`] del propio componente.
|
/// [`Component::prepare()`](Component::prepare) del propio componente.
|
||||||
/// 5. Despacha [`action::component::AfterRender<C>`] para que las extensiones puedan reaccionar con
|
/// 5. Despacha [`action::component::AfterRender<C>`](crate::base::action::component::AfterRender)
|
||||||
/// sus últimos ajustes.
|
/// para que las extensiones puedan reaccionar con sus últimos ajustes.
|
||||||
/// 6. Finalmente despacha [`action::component::TransformMarkup<C>`] para que las extensiones puedan
|
/// 6. Finalmente despacha
|
||||||
/// trabajar sobre el HTML final para modificarlo antes de devolverlo.
|
/// [`action::component::TransformMarkup<C>`](crate::base::action::component::TransformMarkup)
|
||||||
|
/// para que las extensiones puedan trabajar sobre el HTML final para modificarlo antes de
|
||||||
|
/// devolverlo.
|
||||||
/// 7. Devuelve el [`Markup`] resultante.
|
/// 7. Devuelve el [`Markup`] resultante.
|
||||||
///
|
|
||||||
/// [`render()`]: ComponentRender::render
|
|
||||||
/// [`is_renderable()`]: Component::is_renderable
|
|
||||||
/// [`setup()`]: Component::setup
|
|
||||||
/// [`Component::prepare()`]: Component::prepare
|
|
||||||
/// [`action::component::BeforeRender<C>`]: crate::base::action::component::BeforeRender
|
|
||||||
/// [`action::component::AfterRender<C>`]: crate::base::action::component::AfterRender
|
|
||||||
/// [`action::component::TransformMarkup<C>`]: crate::base::action::component::TransformMarkup
|
|
||||||
/// [`Theme::setup_component()`]: crate::core::theme::Theme::setup_component
|
|
||||||
/// [`Theme::render_component()`]: crate::core::theme::Theme::render_component
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<C: Component> ComponentRender for C {
|
impl<C: Component> ComponentRender for C {
|
||||||
async fn render(&mut self, cx: &mut Context) -> Markup {
|
async fn render(&mut self, cx: &mut Context) -> Markup {
|
||||||
|
|
@ -199,7 +183,7 @@ impl<C: Component> ComponentRender for C {
|
||||||
return html! {};
|
return html! {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configura el componente (y, de forma síncrona, el contexto) antes de preparar.
|
// Configura el componente antes de preparar.
|
||||||
self.setup(cx);
|
self.setup(cx);
|
||||||
|
|
||||||
// Acciones de las extensiones antes de renderizar el componente.
|
// Acciones de las extensiones antes de renderizar el componente.
|
||||||
|
|
@ -209,8 +193,7 @@ impl<C: Component> ComponentRender for C {
|
||||||
let result = 'resolve: {
|
let result = 'resolve: {
|
||||||
let mut t: Option<ThemeRef> = Some(cx.theme());
|
let mut t: Option<ThemeRef> = Some(cx.theme());
|
||||||
while let Some(theme) = t {
|
while let Some(theme) = t {
|
||||||
theme.setup_component(self, cx);
|
if let Some(r) = theme.handle_component(self, cx).await {
|
||||||
if let Some(r) = theme.render_component(self, cx).await {
|
|
||||||
break 'resolve r;
|
break 'resolve r;
|
||||||
}
|
}
|
||||||
t = theme.parent();
|
t = theme.parent();
|
||||||
|
|
|
||||||
|
|
@ -65,9 +65,9 @@ fn add_to_enabled(list: &mut Vec<ExtensionRef>, extension: ExtensionRef) {
|
||||||
|
|
||||||
// Recorre la cadena de `Theme::parent()` para detectar referencias circulares. `parent()` se
|
// Recorre la cadena de `Theme::parent()` para detectar referencias circulares. `parent()` se
|
||||||
// resuelve en tiempo de ejecución, así que un ciclo no puede descartarse al compilar. Se rechaza el
|
// resuelve en tiempo de ejecución, así que un ciclo no puede descartarse al compilar. Se rechaza el
|
||||||
// arranque si detecta uno, antes de provocar un bucle infinito en el `ComponentRender::render()` o
|
// arranque al detectar uno, antes de provocar un bucle infinito (en `Theme::handle_component()`) o
|
||||||
// un desbordamiento de pila en los métodos predefinidos de `Theme` que delegan recursivamente en el
|
// un desbordamiento de pila (en los métodos predefinidos de `Theme` que delegan recursivamente en
|
||||||
// tema padre.
|
// el tema padre).
|
||||||
fn check_theme_parent_chain(theme: ThemeRef) {
|
fn check_theme_parent_chain(theme: ThemeRef) {
|
||||||
let mut chain: Vec<ThemeRef> = vec![theme];
|
let mut chain: Vec<ThemeRef> = vec![theme];
|
||||||
let mut current = theme;
|
let mut current = theme;
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,15 @@
|
||||||
//! API para añadir y gestionar nuevos temas.
|
//! API para añadir y gestionar nuevos temas.
|
||||||
//!
|
//!
|
||||||
//! Un tema es la *piel* de la aplicación: define estilos, tipografías, espaciados o comportamientos
|
//! Un tema es la *piel* de la aplicación: define estilos, tipografías, espaciados o comportamientos
|
||||||
//! interactivos. Usa plantillas ([`Template`]) para maquetar los contenidos en base a regiones
|
//! interactivos. Usa plantillas ([`Template`](crate::base::component::layout::Template)) para
|
||||||
//! ([`Region`]). Cada región es un contenedor lógico identificado por un nombre para agrupar y
|
//! maquetar los contenidos en base a regiones ([`Region`](crate::base::component::layout::Region)).
|
||||||
//! renderizar componentes.
|
//! Cada región es un contenedor lógico identificado por un nombre para agrupar y renderizar
|
||||||
|
//! componentes.
|
||||||
//!
|
//!
|
||||||
//! Una página ([`Page`]) es un documento HTML completo. Implementa [`Contextual`] para gestionar su
|
//! Una página ([`Page`](crate::response::Page)) es un documento HTML completo. Implementa
|
||||||
//! propio [`Context`], donde mantiene el tema activo, la plantilla seleccionada y los componentes
|
//! [`Contextual`](crate::core::component::Contextual) para gestionar su propio
|
||||||
//! asociados a cada región a renderizar.
|
//! [`Context`](crate::core::component::Context), donde mantiene el tema activo, la plantilla
|
||||||
|
//! seleccionada y los componentes asociados a cada región a renderizar.
|
||||||
//!
|
//!
|
||||||
//! # Temas hijo, herencia y componentes
|
//! # Temas hijo, herencia y componentes
|
||||||
//!
|
//!
|
||||||
|
|
@ -15,29 +17,26 @@
|
||||||
//! identificado por [`Theme::parent()`]. Un tema hijo hereda automáticamente todos los métodos del
|
//! identificado por [`Theme::parent()`]. Un tema hijo hereda automáticamente todos los métodos del
|
||||||
//! padre y puede sobrescribirlos selectivamente. Esta herencia determina qué implementación de sus
|
//! padre y puede sobrescribirlos selectivamente. Esta herencia determina qué implementación de sus
|
||||||
//! métodos se usa cuando el tema hijo no los sobrescribe (ya sea el renderizado del `<body>` o del
|
//! métodos se usa cuando el tema hijo no los sobrescribe (ya sea el renderizado del `<body>` o del
|
||||||
//! `<head>`, la definición de los recursos necesarios, la traducción de puntos de corte y colores
|
//! `<head>`, la definición de los recursos necesarios, la asignación de colores por intención vía
|
||||||
//! por intención vía [`Theme::breakpoint_min_width()`] y [`Theme::intent_color()`], la captura de
|
//! [`Theme::intent_color()`], la captura de componentes con [`Theme::handle_component()`], las
|
||||||
//! componentes para alterar su comportamiento usando [`Theme::setup_component()`] y
|
//! páginas de error, etc.). Un tema hijo puede ser a su vez padre de otro, basta declararlo cada
|
||||||
//! [`Theme::render_component()`], las páginas de error, etc.).
|
//! vez con [`Theme::parent()`].
|
||||||
//!
|
//!
|
||||||
//! Un tema hijo puede ser a su vez padre de otro, basta declararlo cada vez en [`Theme::parent()`].
|
|
||||||
//! Como `parent()` se resuelve en tiempo de ejecución, PageTop no puede descartar en compilación
|
//! Como `parent()` se resuelve en tiempo de ejecución, PageTop no puede descartar en compilación
|
||||||
//! referencias circulares (un tema acaba siendo padre de sí mismo, directa o transitivamente). Ese
|
//! referencias circulares (un tema acaba siendo padre de sí mismo, directa o transitivamente). Ese
|
||||||
//! ciclo causaría un bucle infinito recorriendo la cadena de temas en [`Theme::setup_component()`]
|
//! ciclo provocaría un bucle infinito en [`Theme::handle_component()`] o un desbordamiento de pila
|
||||||
//! y [`Theme::render_component()`], o un desbordamiento de pila en los métodos predefinidos de
|
//! en los métodos predefinidos de `Theme` que delegan recursivamente en el padre. Para evitarlo,
|
||||||
//! `Theme` que delegan recursivamente en el padre. Para evitarlo, PageTop recorre la cadena de cada
|
//! PageTop recorre la cadena de cada tema al registrarlo y **aborta el arranque de la aplicación**
|
||||||
//! tema al registrarlo y **aborta el arranque de la aplicación** si detecta una referencia
|
//! si detecta una referencia circular.
|
||||||
//! circular.
|
|
||||||
//!
|
//!
|
||||||
//! Sin embargo, no dice nada sobre los componentes. Aunque un tema puede exportar su propio
|
//! Sin embargo, no dice nada sobre los componentes. Aunque un tema puede exportar su propio
|
||||||
//! catálogo de componentes, realmente no pertenecen como tal a ningún tema ni dependen de esa
|
//! catálogo de componentes, realmente no pertenecen como tal a ningún tema ni dependen de esa
|
||||||
//! cadena de herencia. Una extensión puede existir únicamente para aportar un componente genérico
|
//! cadena de herencia. Una extensión puede existir únicamente para aportar un componente genérico
|
||||||
//! (por ejemplo, un editor de texto enriquecido) pensado para usarse en cualquier aplicación, con
|
//! (por ejemplo, un editor de texto enriquecido) pensado para usarse en cualquier aplicación, con
|
||||||
//! independencia del tema activo. Que un tema decida capturar ese componente en
|
//! independencia del tema activo. Que un tema decida capturar ese componente en
|
||||||
//! [`Theme::setup_component()`] o [`Theme::render_component()`] para adaptarlo es una decisión
|
//! [`Theme::handle_component()`] para adaptarlo es una decisión propia del tema, no una relación de
|
||||||
//! propia del tema, no una relación de parentesco. Cualquier tema de la cadena de herencia puede
|
//! parentesco: cualquier tema de la cadena de herencia puede interceptar cualquier componente,
|
||||||
//! interceptar cualquier componente, venga de la extensión que venga, sin que exista ningún vínculo
|
//! venga de la extensión que venga, sin que exista ningún vínculo de diseño previo entre ambos.
|
||||||
//! de diseño previo entre ambos.
|
|
||||||
//!
|
//!
|
||||||
//! Lo que sí es responsabilidad del tema activo es garantizar que el componente disponga de los
|
//! Lo que sí es responsabilidad del tema activo es garantizar que el componente disponga de los
|
||||||
//! recursos que necesita para verse y comportarse correctamente: sus propios estilos y JavaScript,
|
//! recursos que necesita para verse y comportarse correctamente: sus propios estilos y JavaScript,
|
||||||
|
|
@ -46,9 +45,10 @@
|
||||||
//!
|
//!
|
||||||
//! # Cómo crear un tema nuevo
|
//! # Cómo crear un tema nuevo
|
||||||
//!
|
//!
|
||||||
//! Un tema mínimo es una extensión que implementa [`Extension`] y también [`Theme`] para que
|
//! Un tema mínimo es una extensión que implementa [`Extension`](crate::core::extension::Extension)
|
||||||
//! [`Extension::theme()`] devuelva `Some(&Self)`. Basta con un `impl Theme for MyTheme {}` vacío,
|
//! y también [`Theme`] para que [`Extension::theme()`](crate::core::extension::Extension::theme)
|
||||||
//! ya que todos los métodos de [`Theme`] tienen implementación por defecto.
|
//! devuelva `Some(&Self)`. Basta con un `impl Theme for MyTheme {}` vacío, ya que todos los
|
||||||
|
//! métodos de [`Theme`] tienen implementación por defecto.
|
||||||
//!
|
//!
|
||||||
//! Un tema puede personalizarse en seis pasos, cada uno necesario sólo si lo que ofrece PageTop
|
//! Un tema puede personalizarse en seis pasos, cada uno necesario sólo si lo que ofrece PageTop
|
||||||
//! por defecto no basta o no aplica:
|
//! por defecto no basta o no aplica:
|
||||||
|
|
@ -65,18 +65,18 @@
|
||||||
//! siempre las mismas: no hay un método de `Theme` para elegir una plantilla predeterminada
|
//! siempre las mismas: no hay un método de `Theme` para elegir una plantilla predeterminada
|
||||||
//! distinta. Un tema puede definir su propio *enum* que implemente [`TemplateName`] para
|
//! distinta. Un tema puede definir su propio *enum* que implemente [`TemplateName`] para
|
||||||
//! **añadir** plantillas que PageTop no ofrece, y no para redefinir `Standard`/`Admin`.
|
//! **añadir** plantillas que PageTop no ofrece, y no para redefinir `Standard`/`Admin`.
|
||||||
//! 3. **Cambiar cómo se renderiza o se ajusta** una región, una plantilla o un componente ya
|
//! 3. **Cambiar cómo se renderiza** una región, una plantilla o un componente ya existente, se hace
|
||||||
//! existente. Para sobrescribir su renderizado se captura el componente (por ejemplo, [`Region`]
|
//! capturando el componente ([`Region`](crate::base::component::layout::Region) o
|
||||||
//! o [`Template`], o el componente que sea) usando [`Theme::render_component()`]. En el caso de
|
//! [`Template`](crate::base::component::layout::Template), o el componente que sea) en
|
||||||
//! regiones y plantillas, para distinguir *qué* región o plantilla concreta envuelve el
|
//! [`Theme::handle_component()`]. En el caso de regiones y plantillas, para distinguir *qué*
|
||||||
//! componente, sin comparar cadenas, basta con encadenar el *getter* correspondiente
|
//! región o plantilla concreta envuelve el componente, sin comparar cadenas, basta con encadenar
|
||||||
|
//! el *getter* correspondiente
|
||||||
//! ([`Region::region()`](crate::base::component::layout::Region::region) o
|
//! ([`Region::region()`](crate::base::component::layout::Region::region) o
|
||||||
//! [`Template::template()`](crate::base::component::layout::Template::template)) con
|
//! [`Template::template()`](crate::base::component::layout::Template::template)) con
|
||||||
//! [`AnyCast::downcast_ref()`](crate::core::AnyCast::downcast_ref) hacia el tipo concreto (por
|
//! [`AnyCast::downcast_ref()`](crate::core::AnyCast::downcast_ref) hacia el tipo concreto (por
|
||||||
//! ejemplo, [`CoreTemplates`] o el propio *enum* del tema). `pagetop-bootsier` hace exactamente
|
//! ejemplo, [`CoreTemplates`] o el propio *enum* del tema). `pagetop-bootsier` hace exactamente
|
||||||
//! esto para maquetar `Standard` y `Admin` de forma distinta, sin necesitar sus propias
|
//! esto para maquetar `Standard` y `Admin` de forma distinta, sin necesitar sus propias
|
||||||
//! variantes de plantilla. Para ajustarlo sin rehacer su marcado (añadir una clase, un
|
//! variantes de plantilla.
|
||||||
//! atributo, etc.), se usa [`Theme::setup_component()`] en su lugar.
|
|
||||||
//! 4. **Definir los anchos mínimos *mobile-first* para los puntos de corte** sobrescribiendo
|
//! 4. **Definir los anchos mínimos *mobile-first* para los puntos de corte** sobrescribiendo
|
||||||
//! [`Theme::breakpoint_min_width()`]. Por defecto, [`Breakpoint`] resuelve el ancho mínimo de
|
//! [`Theme::breakpoint_min_width()`]. Por defecto, [`Breakpoint`] resuelve el ancho mínimo de
|
||||||
//! cada variante (`Sm`, `Md`, etc.) como una cadena CSS ya formateada (p. ej. `"768px"`) que
|
//! cada variante (`Sm`, `Md`, etc.) como una cadena CSS ya formateada (p. ej. `"768px"`) que
|
||||||
|
|
@ -95,14 +95,14 @@
|
||||||
//! 6. **Reexportar, extender o añadir componentes**. Un tema puede reexportar tal cual los
|
//! 6. **Reexportar, extender o añadir componentes**. Un tema puede reexportar tal cual los
|
||||||
//! componentes propios de PageTop que no requieran adaptación, extenderlos con un trait propio
|
//! componentes propios de PageTop que no requieran adaptación, extenderlos con un trait propio
|
||||||
//! para añadir métodos exclusivos (guardando su estado en valores extra con
|
//! para añadir métodos exclusivos (guardando su estado en valores extra con
|
||||||
//! [`PropsOp::set_extra()`] para consumirlos en el `setup()` vía [`Theme::setup_component()`] o
|
//! [`PropsOp::set_extra()`](crate::html::PropsOp::set_extra) para consumirlos en el
|
||||||
//! en el `render()` vía [`Theme::render_component()`]), o aportar componentes propios.
|
//! `setup()`/`render()` vía [`Theme::handle_component()`]), o aportar componentes propios.
|
||||||
//! `pagetop-bootsier` combina las tres estrategias: reexporta `Form`/`Fieldset` sin cambios,
|
//! `pagetop-bootsier` combina las tres estrategias: reexporta `Form`/`Fieldset` sin cambios,
|
||||||
//! extiende `Button`/`Badge`/`Dropdown`/`Nav`/`Navbar` con sus propios traits (`ButtonBootsier`,
|
//! extiende `Button`/`Badge`/`Dropdown`/`Nav`/`Navbar` con sus propios traits (`ButtonBootsier`,
|
||||||
//! `BadgeBootsier`, etc.), y añade componentes propios como `Offcanvas`.
|
//! `BadgeBootsier`, etc.), y añade componentes propios como `Offcanvas`.
|
||||||
//!
|
//!
|
||||||
//! Para forzar una plantilla completamente distinta en una página concreta, se puede llamar
|
//! Para forzar una plantilla completamente distinta en una página concreta, se puede llamar
|
||||||
//! manualmente a [`with_template()`].
|
//! manualmente a [`with_template()`](crate::core::component::Contextual::with_template).
|
||||||
//!
|
//!
|
||||||
//! Las páginas de error (403, 404, y otros errores fatales) no tienen una plantilla propia: se
|
//! Las páginas de error (403, 404, y otros errores fatales) no tienen una plantilla propia: se
|
||||||
//! renderizan con la plantilla ya activa en la página, para que el usuario no pierda el contexto de
|
//! renderizan con la plantilla ya activa en la página, para que el usuario no pierda el contexto de
|
||||||
|
|
@ -115,10 +115,11 @@
|
||||||
//!
|
//!
|
||||||
//! # Componentes que se procesan en todas las páginas
|
//! # Componentes que se procesan en todas las páginas
|
||||||
//!
|
//!
|
||||||
//! Los componentes añadidos a una página con [`with_child_in()`] sólo existen para esa petición
|
//! Los componentes añadidos a una página con
|
||||||
//! concreta: hay que volver a añadirlos cada vez que se construya la página. [`InRegion`] resuelve
|
//! [`with_child_in()`](crate::core::component::Contextual::with_child_in) sólo existen para esa
|
||||||
//! el caso contrario: un componente que se debe procesar en todas las páginas, o en todas las de un
|
//! petición concreta: hay que volver a añadirlos cada vez que se construya la página. [`InRegion`]
|
||||||
//! tema concreto, sin tener que registrarlo en el código de cada página.
|
//! resuelve el caso contrario: un componente que se debe procesar en todas las páginas, o en todas
|
||||||
|
//! las de un tema concreto, sin tener que registrarlo en el código de cada página.
|
||||||
//!
|
//!
|
||||||
//! `InRegion` registra el componente una sola vez, normalmente al arrancar la aplicación o al
|
//! `InRegion` registra el componente una sola vez, normalmente al arrancar la aplicación o al
|
||||||
//! inicializar una extensión, y a partir de ahí se procesa automáticamente en todas las páginas que
|
//! inicializar una extensión, y a partir de ahí se procesa automáticamente en todas las páginas que
|
||||||
|
|
@ -133,23 +134,13 @@
|
||||||
//! renderizado, de modo que su `setup()` siempre parte de un estado inicial limpio y no acumula
|
//! renderizado, de modo que su `setup()` siempre parte de un estado inicial limpio y no acumula
|
||||||
//! mutaciones entre peticiones.
|
//! mutaciones entre peticiones.
|
||||||
//!
|
//!
|
||||||
//! Como cualquier otro componente, antes de renderizarse pasa por [`is_renderable()`], el primer
|
//! Como cualquier otro componente, antes de renderizarse pasa por
|
||||||
//! paso del [ciclo de renderizado](crate::core::component::ComponentRender). Esto permite
|
//! [`is_renderable()`](crate::core::component::Component::is_renderable), el primer paso del
|
||||||
//! registrarlo una sola vez y que decida por sí mismo cuándo mostrarse, por ejemplo según la ruta
|
//! [ciclo de renderizado](crate::core::component::ComponentRender). Esto permite registrarlo una
|
||||||
//! de la petición o si el usuario actual está autenticado.
|
//! sola vez y que decida por sí mismo cuándo mostrarse, por ejemplo según la ruta de la petición o
|
||||||
|
//! si el usuario actual está autenticado.
|
||||||
//!
|
//!
|
||||||
//! [`PropsOp::set_extra()`]: crate::html::PropsOp::set_extra
|
|
||||||
//! [`ReservedRegions`]: crate::response::ReservedRegions
|
//! [`ReservedRegions`]: crate::response::ReservedRegions
|
||||||
//! [`Page`]: crate::response::Page
|
|
||||||
//! [`Extension`]: crate::core::extension::Extension
|
|
||||||
//! [`Extension::theme()`]: crate::core::extension::Extension::theme
|
|
||||||
//! [`Context`]: crate::core::component::Context
|
|
||||||
//! [`Contextual`]: crate::core::component::Contextual
|
|
||||||
//! [`with_template()`]: crate::core::component::Contextual::with_template
|
|
||||||
//! [`with_child_in()`]: crate::core::component::Contextual::with_child_in
|
|
||||||
//! [`is_renderable()`]: crate::core::component::Component::is_renderable
|
|
||||||
//! [`Template`]: crate::base::component::layout::Template
|
|
||||||
//! [`Region`]: crate::base::component::layout::Region
|
|
||||||
|
|
||||||
mod intent;
|
mod intent;
|
||||||
pub use intent::Intent;
|
pub use intent::Intent;
|
||||||
|
|
|
||||||
|
|
@ -236,53 +236,38 @@ pub trait Theme: Extension + Send + Sync {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Permite al tema modificar un componente antes de decidir cómo renderizarlo.
|
/// Permite al tema intervenir en el ciclo de renderizado de un componente.
|
||||||
///
|
|
||||||
/// Este método tiene especial utilidad en los **temas hijo** porque permite ajustar el estado
|
|
||||||
/// de un componente concreto sin modificar el resto del comportamiento heredado.
|
|
||||||
///
|
|
||||||
/// Recibe una referencia mutable al componente (como objeto dinámico [`Component`]) y el
|
|
||||||
/// contexto de renderizado. Se ejecuta en cada tema de la cadena (hijo > padre > abuelo...), en
|
|
||||||
/// ese orden, justo antes de que ese mismo nivel decida si renderiza el componente con
|
|
||||||
/// [`render_component()`](Self::render_component). La implementación por defecto no hace nada.
|
|
||||||
///
|
|
||||||
/// Usa la macro [`setup_component!`](crate::setup_component) para mutar por tipo:
|
|
||||||
///
|
|
||||||
/// ```rust,ignore
|
|
||||||
/// fn setup_component(&self, component: &mut dyn Component, cx: &mut Context) {
|
|
||||||
/// setup_component!(component, {
|
|
||||||
/// Button => |btn| { btn.add_class("btn-primary"); },
|
|
||||||
/// });
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
fn setup_component(&self, component: &mut dyn Component, cx: &mut Context) {}
|
|
||||||
|
|
||||||
/// Permite al tema sobrescribir el renderizado de un componente.
|
|
||||||
///
|
///
|
||||||
/// Este método tiene especial utilidad en los **temas hijo** porque permite sobrescribir el
|
/// 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
|
/// renderizado que el propio componente o el tema padre ofrece para un componente concreto, sin
|
||||||
/// modificar el resto del comportamiento heredado.
|
/// modificar el resto del comportamiento heredado.
|
||||||
///
|
///
|
||||||
/// Recibe una referencia compartida al componente (como objeto dinámico [`Component`]), ya con
|
/// Recibe una referencia mutable al componente (como objeto dinámico [`Component`]) y el
|
||||||
/// las modificaciones de [`setup_component()`](Self::setup_component) del mismo nivel
|
/// contexto de renderizado. Devuelve:
|
||||||
/// aplicadas, y el contexto de renderizado. Devuelve:
|
|
||||||
///
|
///
|
||||||
/// - `None` si este tema no sobrescribe el renderizado. Es la implementación por defecto. El
|
/// - `None` si este tema no sobrescribe el renderizado. Es la implementación por defecto. El
|
||||||
/// sistema continúa con el siguiente tema de la cadena y, si ninguno lo sobrescribe, usa
|
/// sistema continúa con el siguiente tema de la cadena y, si ninguno lo sobrescribe, usa
|
||||||
/// [`Component::prepare()`](crate::core::component::Component::prepare).
|
/// [`Component::prepare()`](crate::core::component::Component::prepare).
|
||||||
|
/// El tema puede mutar el componente antes de devolver `None`, dejando que otro nivel de la
|
||||||
|
/// cadena se encargue del renderizado.
|
||||||
/// - `Some(Ok(markup))` con el HTML generado por el tema para el componente.
|
/// - `Some(Ok(markup))` con el HTML generado por el tema para el componente.
|
||||||
/// - `Some(Err(e))` si el tema intentó renderizarlo pero falló.
|
/// - `Some(Err(e))` si el tema intentó renderizarlo pero falló.
|
||||||
///
|
///
|
||||||
/// Usa la macro [`render_component!`](crate::render_component), que devuelve `None` si ningún
|
/// Para renderizar usa [`render_component!`](crate::render_component), que devuelve `None` si
|
||||||
/// tipo coincide:
|
/// ningún tipo coincide. Para mutar sin renderizar usa
|
||||||
|
/// [`setup_component!`](crate::setup_component) y devuelve `None` explícitamente:
|
||||||
///
|
///
|
||||||
/// ```rust,ignore
|
/// ```rust,ignore
|
||||||
/// fn render_component(
|
/// fn handle_component(
|
||||||
/// &self,
|
/// &self,
|
||||||
/// component: &dyn Component,
|
/// component: &mut dyn Component,
|
||||||
/// cx: &mut Context,
|
/// cx: &mut Context,
|
||||||
/// ) -> Option<Result<Markup, ComponentError>> {
|
/// ) -> Option<Result<Markup, ComponentError>> {
|
||||||
|
/// // Sólo mutación: ajusta el componente y deja que otro nivel lo renderice.
|
||||||
|
/// setup_component!(component, {
|
||||||
|
/// Button => |btn| { btn.add_class("btn-primary"); },
|
||||||
|
/// });
|
||||||
|
/// // O renderizado completo:
|
||||||
/// render_component!(component, {
|
/// render_component!(component, {
|
||||||
/// Button => |btn| Ok(html! { button.btn.btn-primary { (btn.label()) } }),
|
/// Button => |btn| Ok(html! { button.btn.btn-primary { (btn.label()) } }),
|
||||||
/// Heading => |h| Ok(html! { h2.display-4 { (h.text()) } }),
|
/// Heading => |h| Ok(html! { h2.display-4 { (h.text()) } }),
|
||||||
|
|
@ -290,9 +275,9 @@ pub trait Theme: Extension + Send + Sync {
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
async fn render_component(
|
async fn handle_component(
|
||||||
&self,
|
&self,
|
||||||
component: &dyn Component,
|
component: &mut dyn Component,
|
||||||
cx: &mut Context,
|
cx: &mut Context,
|
||||||
) -> Option<Result<Markup, ComponentError>> {
|
) -> Option<Result<Markup, ComponentError>> {
|
||||||
None
|
None
|
||||||
|
|
@ -405,47 +390,9 @@ pub trait Theme: Extension + Send + Sync {
|
||||||
/// Referencia estática a un tema.
|
/// Referencia estática a un tema.
|
||||||
pub type ThemeRef = &'static dyn Theme;
|
pub type ThemeRef = &'static dyn Theme;
|
||||||
|
|
||||||
// **< setup_component! >***************************************************************************
|
|
||||||
|
|
||||||
/// Modifica un componente dentro de [`Theme::setup_component()`].
|
|
||||||
///
|
|
||||||
/// Evalúa `$component` contra cada tipo de componente listado en orden. En cuanto encuentra
|
|
||||||
/// coincidencia, ejecuta el bloque asociado y detiene la evaluación. Si ningún tipo coincide, no
|
|
||||||
/// hace nada.
|
|
||||||
///
|
|
||||||
/// Usa acceso mutable al componente mediante [`downcast_mut`], lo que permite modificar su estado.
|
|
||||||
///
|
|
||||||
/// # Ejemplo
|
|
||||||
///
|
|
||||||
/// ```rust,ignore
|
|
||||||
/// fn setup_component(&self, component: &mut dyn Component, cx: &mut Context) {
|
|
||||||
/// setup_component!(component, { Button => |btn| { btn.add_class("btn-primary"); } });
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// El tipo mutado aquí puede además renderizarse en [`Theme::render_component()`] del mismo tema,
|
|
||||||
/// que se ejecuta a continuación sobre el componente ya mutado.
|
|
||||||
///
|
|
||||||
/// [`Theme::setup_component()`]: crate::core::theme::Theme::setup_component
|
|
||||||
/// [`downcast_mut`]: crate::core::AnyCast::downcast_mut
|
|
||||||
/// [`Theme::render_component()`]: crate::core::theme::Theme::render_component
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! setup_component {
|
|
||||||
($component:expr, { $($type:ty => |$var:ident| $body:expr),* $(,)? }) => {
|
|
||||||
'setup_component: {
|
|
||||||
$(
|
|
||||||
if let Some($var) = ($component).downcast_mut::<$type>() {
|
|
||||||
$body;
|
|
||||||
break 'setup_component;
|
|
||||||
}
|
|
||||||
)*
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// **< render_component! >**************************************************************************
|
// **< render_component! >**************************************************************************
|
||||||
|
|
||||||
/// Sobrescribe el renderizado de componentes en [`Theme::render_component()`].
|
/// Sobrescribe el renderizado de componentes en [`Theme::handle_component()`].
|
||||||
///
|
///
|
||||||
/// Evalúa `$component` contra cada tipo de componente listado en orden. En cuanto encuentra
|
/// Evalúa `$component` contra cada tipo de componente listado en orden. En cuanto encuentra
|
||||||
/// coincidencia, devuelve `Some(Ok(markup))` o `Some(Err(e))` según el resultado de la expresión
|
/// coincidencia, devuelve `Some(Ok(markup))` o `Some(Err(e))` según el resultado de la expresión
|
||||||
|
|
@ -455,7 +402,7 @@ macro_rules! setup_component {
|
||||||
/// # Ejemplo
|
/// # Ejemplo
|
||||||
///
|
///
|
||||||
/// ```rust,ignore
|
/// ```rust,ignore
|
||||||
/// fn render_component(
|
/// fn handle_component(
|
||||||
/// &self,
|
/// &self,
|
||||||
/// component: &dyn Component,
|
/// component: &dyn Component,
|
||||||
/// cx: &mut Context,
|
/// cx: &mut Context,
|
||||||
|
|
@ -470,14 +417,15 @@ macro_rules! setup_component {
|
||||||
/// Ok(html! { h2.display-4 { (h.text()) } })
|
/// Ok(html! { h2.display-4 { (h.text()) } })
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
|
||||||
/// [`Theme::render_component()`]: crate::core::theme::Theme::render_component
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! render_component {
|
macro_rules! render_component {
|
||||||
($component:expr, { $($type:ty => |$var:ident| $body:expr),* $(,)? }) => {
|
($component:expr, { $($type:ty => |$var:ident| $body:expr),* $(,)? }) => {
|
||||||
'render_component: {
|
'render_component: {
|
||||||
|
// Reborrow explícito como referencia compartida para que `downcast_ref` funcione
|
||||||
|
// correctamente con `&mut dyn Component` (limitación del compilador con trait objects).
|
||||||
|
let __c = &*($component);
|
||||||
$(
|
$(
|
||||||
if let Some($var) = ($component).downcast_ref::<$type>() {
|
if let Some($var) = __c.downcast_ref::<$type>() {
|
||||||
break 'render_component Some($body);
|
break 'render_component Some($body);
|
||||||
}
|
}
|
||||||
)*
|
)*
|
||||||
|
|
@ -485,3 +433,62 @@ macro_rules! render_component {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// **< setup_component! >***************************************************************************
|
||||||
|
|
||||||
|
/// Muta un componente dentro de [`Theme::handle_component()`].
|
||||||
|
///
|
||||||
|
/// Evalúa `$component` contra cada tipo de componente listado en orden. En cuanto encuentra
|
||||||
|
/// coincidencia, ejecuta el bloque asociado y detiene la evaluación. Si ningún tipo coincide, no
|
||||||
|
/// hace nada.
|
||||||
|
///
|
||||||
|
/// Usa acceso mutable al componente mediante [`downcast_mut`](crate::core::AnyCast::downcast_mut),
|
||||||
|
/// lo que permite modificar su estado. El tema puede devolver `None` tras la mutación para que otro
|
||||||
|
/// nivel de la cadena se encargue del renderizado.
|
||||||
|
///
|
||||||
|
/// # Ejemplos
|
||||||
|
///
|
||||||
|
/// Solo mutación: el tema ajusta el componente y delega el renderizado al siguiente nivel:
|
||||||
|
///
|
||||||
|
/// ```rust,ignore
|
||||||
|
/// fn handle_component(
|
||||||
|
/// &self,
|
||||||
|
/// component: &mut dyn Component,
|
||||||
|
/// cx: &mut Context,
|
||||||
|
/// ) -> Option<Result<Markup, ComponentError>> {
|
||||||
|
/// setup_component!(component, { Button => |btn| { btn.add_class("btn-primary"); } });
|
||||||
|
/// None
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Mutación y renderizado combinados: el `Button` se muta y se renderiza aquí; el `Heading` se
|
||||||
|
/// muta pero continúa la cadena para que otro nivel lo renderice:
|
||||||
|
///
|
||||||
|
/// ```rust,ignore
|
||||||
|
/// fn handle_component(
|
||||||
|
/// &self,
|
||||||
|
/// component: &mut dyn Component,
|
||||||
|
/// cx: &mut Context,
|
||||||
|
/// ) -> Option<Result<Markup, ComponentError>> {
|
||||||
|
/// setup_component!(component, {
|
||||||
|
/// Button => |btn| { btn.add_class("btn-primary"); },
|
||||||
|
/// Heading => |h| { h.add_class("display-4"); },
|
||||||
|
/// });
|
||||||
|
/// render_component!(component, {
|
||||||
|
/// Button => |btn| Ok(html! { button.btn { (btn.label()) } }),
|
||||||
|
/// })
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! setup_component {
|
||||||
|
($component:expr, { $($type:ty => |$var:ident| $body:expr),* $(,)? }) => {
|
||||||
|
'setup_component: {
|
||||||
|
$(
|
||||||
|
if let Some($var) = ($component).downcast_mut::<$type>() {
|
||||||
|
$body;
|
||||||
|
break 'setup_component;
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,14 +21,14 @@ use crate::locale::Lc;
|
||||||
///
|
///
|
||||||
/// Requiere [`AnyInfo`] para que un [`RegionRef`] pueda recuperarse mediante
|
/// Requiere [`AnyInfo`] para que un [`RegionRef`] pueda recuperarse mediante
|
||||||
/// [`AnyCast::downcast_ref()`] hacia su tipo concreto (por ejemplo, para que un tema distinga en
|
/// [`AnyCast::downcast_ref()`] hacia su tipo concreto (por ejemplo, para que un tema distinga en
|
||||||
/// [`Theme::render_component()`] qué variante concreta está renderizando el componente [`Region`]).
|
/// [`Theme::handle_component()`] qué variante concreta está renderizando el componente [`Region`]).
|
||||||
///
|
///
|
||||||
/// [`Context`]: crate::core::component::Context
|
/// [`Context`]: crate::core::component::Context
|
||||||
/// [`Contextual::with_child_in()`]: crate::core::component::Contextual::with_child_in
|
/// [`Contextual::with_child_in()`]: crate::core::component::Contextual::with_child_in
|
||||||
/// [`ReservedRegions`]: crate::response::ReservedRegions
|
/// [`ReservedRegions`]: crate::response::ReservedRegions
|
||||||
/// [`Page`]: crate::response::Page
|
/// [`Page`]: crate::response::Page
|
||||||
/// [`AnyCast::downcast_ref()`]: crate::core::AnyCast::downcast_ref
|
/// [`AnyCast::downcast_ref()`]: crate::core::AnyCast::downcast_ref
|
||||||
/// [`Theme::render_component()`]: crate::core::theme::Theme::render_component
|
/// [`Theme::handle_component()`]: crate::core::theme::Theme::handle_component
|
||||||
/// [`Region`]: crate::base::component::layout::Region
|
/// [`Region`]: crate::base::component::layout::Region
|
||||||
pub trait RegionName: Send + Sync + AnyInfo {
|
pub trait RegionName: Send + Sync + AnyInfo {
|
||||||
/// Devuelve el nombre de la región.
|
/// Devuelve el nombre de la región.
|
||||||
|
|
@ -117,18 +117,14 @@ impl RegionName for CoreRegions {
|
||||||
/// Interfaz común para las plantillas lógicas de una página.
|
/// Interfaz común para las plantillas lógicas de una página.
|
||||||
///
|
///
|
||||||
/// Representa una variante identificada por un nombre. Un tema puede usar este nombre para decidir
|
/// Representa una variante identificada por un nombre. Un tema puede usar este nombre para decidir
|
||||||
/// la composición del cuerpo de una página ([`Page`]), es decir, qué regiones ([`RegionName`])
|
/// la composición del cuerpo de una página ([`Page`](crate::response::Page)), es decir, qué
|
||||||
/// renderizar y en qué orden.
|
/// regiones ([`RegionName`]) renderizar y en qué orden.
|
||||||
///
|
///
|
||||||
/// Requiere [`AnyInfo`] por el mismo motivo que [`RegionName`], para que un [`TemplateRef`] pueda
|
/// Requiere [`AnyInfo`] por el mismo motivo que [`RegionName`], para que un [`TemplateRef`] pueda
|
||||||
/// recuperarse mediante [`AnyCast::downcast_ref()`] hacia su tipo concreto (por ejemplo, para que
|
/// recuperarse mediante [`AnyCast::downcast_ref()`](crate::core::AnyCast::downcast_ref) hacia su
|
||||||
/// un tema distinga en [`Theme::render_component()`] qué variante concreta está renderizando el
|
/// tipo concreto (por ejemplo, para que un tema distinga en
|
||||||
/// componente [`Template`]).
|
/// [`Theme::handle_component()`](crate::core::theme::Theme::handle_component) qué variante concreta
|
||||||
///
|
/// está renderizando el componente [`Template`](crate::base::component::layout::Template)).
|
||||||
/// [`Page`]: crate::response::Page
|
|
||||||
/// [`AnyCast::downcast_ref()`]: crate::core::AnyCast::downcast_ref
|
|
||||||
/// [`Theme::render_component()`]: crate::core::theme::Theme::render_component
|
|
||||||
/// [`Template`]: crate::base::component::layout::Template
|
|
||||||
pub trait TemplateName: Send + Sync + AnyInfo {
|
pub trait TemplateName: Send + Sync + AnyInfo {
|
||||||
/// Devuelve el nombre de la plantilla.
|
/// Devuelve el nombre de la plantilla.
|
||||||
fn name(&self) -> &'static str;
|
fn name(&self) -> &'static str;
|
||||||
|
|
|
||||||
|
|
@ -112,13 +112,11 @@ impl Page {
|
||||||
|
|
||||||
/// Crea una nueva instancia de página con la plantilla [`CoreTemplates::Admin`].
|
/// Crea una nueva instancia de página con la plantilla [`CoreTemplates::Admin`].
|
||||||
///
|
///
|
||||||
/// Cada tema puede maquetarla de forma distinta capturando [`Template`] vía
|
/// Cada tema puede maquetarla de forma distinta capturando
|
||||||
/// [`Theme::render_component()`], pero la plantilla en sí es la misma constante para cualquier
|
/// [`Template`](crate::base::component::layout::Template) en `handle_component()`, pero la
|
||||||
/// tema.
|
/// plantilla en sí es la misma constante para cualquier tema.
|
||||||
///
|
///
|
||||||
/// [`CoreTemplates::Admin`]: crate::core::theme::CoreTemplates::Admin
|
/// [`CoreTemplates::Admin`]: crate::core::theme::CoreTemplates::Admin
|
||||||
/// [`Template`]: crate::base::component::layout::Template
|
|
||||||
/// [`Theme::render_component()`]: crate::core::theme::Theme::render_component
|
|
||||||
pub fn admin(request: HttpRequest) -> Self {
|
pub fn admin(request: HttpRequest) -> Self {
|
||||||
Page {
|
Page {
|
||||||
context: Context::admin(request),
|
context: Context::admin(request),
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ async fn setup() {
|
||||||
/// Replaces the default `Template` composition (`Header` + `Content` + `Footer`) with a fixed
|
/// Replaces the default `Template` composition (`Header` + `Content` + `Footer`) with a fixed
|
||||||
/// marker string, for both `CoreTemplates::Standard` and `CoreTemplates::Admin`. Mirrors how
|
/// marker string, for both `CoreTemplates::Standard` and `CoreTemplates::Admin`. Mirrors how
|
||||||
/// a real theme (e.g. `pagetop-bootsier`) tells its own layout apart from PageTop's default: by
|
/// a real theme (e.g. `pagetop-bootsier`) tells its own layout apart from PageTop's default: by
|
||||||
/// intercepting the `Template` component in `render_component()`, not by swapping which
|
/// intercepting the `Template` component in `handle_component()`, not by swapping which
|
||||||
/// `TemplateRef` gets resolved.
|
/// `TemplateRef` gets resolved.
|
||||||
struct MarkerTheme;
|
struct MarkerTheme;
|
||||||
|
|
||||||
|
|
@ -26,12 +26,12 @@ impl Extension for MarkerTheme {
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Theme for MarkerTheme {
|
impl Theme for MarkerTheme {
|
||||||
async fn render_component(
|
async fn handle_component(
|
||||||
&self,
|
&self,
|
||||||
component: &dyn Component,
|
component: &mut dyn Component,
|
||||||
_cx: &mut Context,
|
_cx: &mut Context,
|
||||||
) -> Option<Result<Markup, ComponentError>> {
|
) -> Option<Result<Markup, ComponentError>> {
|
||||||
let template = component.downcast_ref::<layout::Template>()?;
|
let template = (*component).downcast_ref::<layout::Template>()?;
|
||||||
template.template().downcast_ref::<CoreTemplates>()?;
|
template.template().downcast_ref::<CoreTemplates>()?;
|
||||||
Some(Ok(html! { "marker-template-output" }))
|
Some(Ok(html! { "marker-template-output" }))
|
||||||
}
|
}
|
||||||
|
|
@ -42,7 +42,7 @@ impl Theme for MarkerTheme {
|
||||||
// `Theme::default_template()`/`admin_template()` were removed: `Context::template()` always
|
// `Theme::default_template()`/`admin_template()` were removed: `Context::template()` always
|
||||||
// resolves `Default`/`Admin` to the core `CoreTemplates::Standard`/`Admin` identity, regardless
|
// resolves `Default`/`Admin` to the core `CoreTemplates::Standard`/`Admin` identity, regardless
|
||||||
// of which theme is active. Themes customize the actual rendering by intercepting the `Template`
|
// of which theme is active. Themes customize the actual rendering by intercepting the `Template`
|
||||||
// component in `render_component()` instead (see the tests further below).
|
// component in `handle_component()` instead (see the tests further below).
|
||||||
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn default_template_identity_is_independent_of_theme() {
|
async fn default_template_identity_is_independent_of_theme() {
|
||||||
|
|
@ -74,7 +74,7 @@ async fn explicit_template_is_not_overridden_by_a_later_with_theme() {
|
||||||
assert_eq!(cx.template().name(), "admin");
|
assert_eq!(cx.template().name(), "admin");
|
||||||
}
|
}
|
||||||
|
|
||||||
// **< A theme customizes rendering via `render_component()` >**************************************
|
// **< A theme customizes rendering via `handle_component()` >**************************************
|
||||||
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn without_a_matching_theme_the_default_composition_is_used() {
|
async fn without_a_matching_theme_the_default_composition_is_used() {
|
||||||
|
|
@ -89,7 +89,7 @@ async fn without_a_matching_theme_the_default_composition_is_used() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn theme_replaces_template_rendering_via_render_component() {
|
async fn theme_replaces_template_rendering_via_handle_component() {
|
||||||
setup().await;
|
setup().await;
|
||||||
|
|
||||||
let mut template = layout::Template::default();
|
let mut template = layout::Template::default();
|
||||||
|
|
@ -99,7 +99,7 @@ async fn theme_replaces_template_rendering_via_render_component() {
|
||||||
assert_eq!(html, "marker-template-output");
|
assert_eq!(html, "marker-template-output");
|
||||||
}
|
}
|
||||||
|
|
||||||
// **< Page::render() reaches the active theme's `render_component()` >*****************************
|
// **< Page::render() reaches the active theme's `handle_component()` >*****************************
|
||||||
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn page_admin_render_reflects_the_active_theme_template() {
|
async fn page_admin_render_reflects_the_active_theme_template() {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue