From dea994e8ca7e8ed192c54428cae54871c28c52ea Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Mon, 17 Nov 2025 22:50:56 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Incorpora=20`is=5Frenderable`=20en?= =?UTF-8?q?=20`Component`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/base/action/component.rs | 3 - src/base/action/component/is_renderable.rs | 96 ---------------------- src/core/component.rs | 53 ++++++++++++ src/core/component/definition.rs | 36 +++++--- 4 files changed, 78 insertions(+), 110 deletions(-) delete mode 100644 src/base/action/component/is_renderable.rs diff --git a/src/base/action/component.rs b/src/base/action/component.rs index aaef1ce..30c7ba4 100644 --- a/src/base/action/component.rs +++ b/src/base/action/component.rs @@ -1,8 +1,5 @@ //! Acciones que operan sobre componentes. -mod is_renderable; -pub use is_renderable::*; - mod before_render_component; pub use before_render_component::*; diff --git a/src/base/action/component/is_renderable.rs b/src/base/action/component/is_renderable.rs deleted file mode 100644 index 5a0e244..0000000 --- a/src/base/action/component/is_renderable.rs +++ /dev/null @@ -1,96 +0,0 @@ -use crate::prelude::*; - -/// Tipo de función para determinar si un componente se renderiza o no. -/// -/// Se usa en la acción [`IsRenderable`] para controlar dinámicamente la visibilidad del componente -/// `component` según el contexto `cx`. El componente **no se renderiza** en cuanto una de las -/// funciones devuelva `false`. -pub type FnIsRenderable = fn(component: &C, cx: &Context) -> bool; - -/// Con la función [`FnIsRenderable`] se puede decidir si se renderiza o no un componente. -pub struct IsRenderable { - f: FnIsRenderable, - referer_type_id: Option, - referer_id: AttrId, - weight: Weight, -} - -/// Filtro para despachar [`FnIsRenderable`] para decidir si se renderiza o no un componente `C`. -impl ActionDispatcher for IsRenderable { - /// Devuelve el identificador de tipo ([`UniqueId`]) del componente `C`. - fn referer_type_id(&self) -> Option { - self.referer_type_id - } - - /// Devuelve el identificador del componente. - fn referer_id(&self) -> Option { - self.referer_id.get() - } - - /// Devuelve el peso para definir el orden de ejecución. - fn weight(&self) -> Weight { - self.weight - } -} - -impl IsRenderable { - /// Permite [registrar](Extension::actions) una nueva acción [`FnIsRenderable`]. - pub fn new(f: FnIsRenderable) -> Self { - IsRenderable { - f, - referer_type_id: Some(UniqueId::of::()), - referer_id: AttrId::default(), - weight: 0, - } - } - - /// Afina el registro para ejecutar la acción [`FnIsRenderable`] sólo para el componente `C` - /// con identificador `id`. - pub fn filter_by_referer_id(mut self, id: impl AsRef) -> Self { - self.referer_id.alter_value(id); - self - } - - /// Opcional. Acciones con pesos más bajos se aplican antes. Se pueden usar valores negativos. - pub fn with_weight(mut self, value: Weight) -> Self { - self.weight = value; - self - } - - // Despacha las acciones. Se detiene en cuanto una [`FnIsRenderable`] devuelve `false`. - #[inline] - pub(crate) fn dispatch(component: &C, cx: &mut Context) -> bool { - let mut renderable = true; - dispatch_actions( - &ActionKey::new( - UniqueId::of::(), - None, - Some(UniqueId::of::()), - None, - ), - |action: &Self| { - if renderable && !(action.f)(component, cx) { - renderable = false; - } - }, - ); - if renderable { - if let Some(id) = component.id() { - dispatch_actions( - &ActionKey::new( - UniqueId::of::(), - None, - Some(UniqueId::of::()), - Some(id), - ), - |action: &Self| { - if renderable && !(action.f)(component, cx) { - renderable = false; - } - }, - ); - } - } - renderable - } -} diff --git a/src/core/component.rs b/src/core/component.rs index a7faa2f..9c9ade2 100644 --- a/src/core/component.rs +++ b/src/core/component.rs @@ -11,6 +11,59 @@ pub use children::{Typed, TypedOp}; mod context; pub use context::{Context, ContextError, ContextOp, Contextual}; +/// Alias de función (*callback*) para **determinar si un componente se renderiza o no**. +/// +/// Puede usarse para permitir que una instancia concreta de un tipo de componente dado decida +/// dinámicamente durante el proceso de renderizado ([`Component::is_renderable()`]) si se renderiza +/// o no. +/// +/// # Ejemplo +/// +/// ```rust +/// # use pagetop::prelude::*; +/// #[derive(AutoDefault)] +/// struct SampleComponent { +/// renderable: Option, +/// } +/// +/// impl Component for SampleComponent { +/// fn new() -> Self { +/// Self::default() +/// } +/// +/// fn is_renderable(&self, cx: &mut Context) -> bool { +/// // Si hay callback, se usa; en caso contrario, se renderiza por defecto. +/// self.renderable.map_or(true, |f| f(cx)) +/// } +/// +/// fn prepare_component(&self, _cx: &mut Context) -> PrepareMarkup { +/// PrepareMarkup::Escaped("Visible component".into()) +/// } +/// } +/// +/// impl SampleComponent { +/// /// Asigna una función que decidirá si el componente se renderiza o no. +/// #[builder_fn] +/// pub fn with_renderable(mut self, f: Option) -> Self { +/// self.renderable = f; +/// self +/// } +/// } +/// +/// fn sample() { +/// let mut cx = Context::default().with_param("user_logged_in", true); +/// +/// // Se instancia un componente que sólo se renderiza si `user_logged_in` es `true`. +/// let mut component = SampleComponent::new().with_renderable(Some(|cx: &Context| { +/// cx.param::("user_logged_in").copied().unwrap_or(false) +/// })); +/// +/// // Aquí simplemente se comprueba que compila y se puede invocar. +/// let _markup = component.render(&mut cx); +/// } +/// ``` +pub type FnIsRenderable = fn(cx: &Context) -> bool; + /// Alias de función (*callback*) para **resolver una URL** según el contexto de renderizado. /// /// Se usa para generar enlaces dinámicos en función del contexto (petición, idioma, etc.). Debe diff --git a/src/core/component/definition.rs b/src/core/component/definition.rs index c0573b4..13b0385 100644 --- a/src/core/component/definition.rs +++ b/src/core/component/definition.rs @@ -45,6 +45,20 @@ pub trait Component: AnyInfo + ComponentRender + Send + Sync { None } + /// Indica si el componente es renderizable. + /// + /// Por defecto, todos los componentes son renderizables (`true`). Sin embargo, este método + /// puede sobrescribirse para decidir dinámicamente si los componentes de este tipo se + /// renderizan o no en función del contexto de renderizado. + /// + /// También puede usarse junto con un alias de función como + /// ([`FnIsRenderable`](crate::core::component::FnIsRenderable)) para permitir que instancias + /// concretas del componente decidan si se renderizan o no. + #[allow(unused_variables)] + fn is_renderable(&self, cx: &mut Context) -> bool { + true + } + /// Configura el componente justo antes de preparar el renderizado. /// /// Este método puede sobrescribirse para modificar la estructura interna del componente o el @@ -72,30 +86,30 @@ pub trait Component: AnyInfo + ComponentRender + Send + Sync { /// Implementa [`render()`](ComponentRender::render) para todos los componentes. /// -/// Y para cada componente ejecuta la siguiente secuencia: +/// El proceso de renderizado de cada componente sigue esta secuencia: /// -/// 1. Despacha [`action::component::IsRenderable`](crate::base::action::component::IsRenderable) -/// para ver si se puede renderizar. Si no es así, devuelve un [`Markup`] vacío. +/// 1. Ejecuta [`is_renderable()`](Component::is_renderable) para ver si puede renderizarse en el +/// contexto actual. Si no es así, devuelve un [`Markup`] vacío. /// 2. Ejecuta [`setup_before_prepare()`](Component::setup_before_prepare) para que el componente /// pueda ajustar su estructura interna o modificar el contexto. /// 3. Despacha [`action::theme::BeforeRender`](crate::base::action::theme::BeforeRender) para -/// que el tema pueda hacer ajustes en el componente o el contexto. +/// permitir que el tema realice ajustes previos. /// 4. Despacha [`action::component::BeforeRender`](crate::base::action::component::BeforeRender) -/// para que otras extensiones puedan hacer ajustes. +/// para que otras extensiones puedan también hacer ajustes previos. /// 5. **Prepara el renderizado del componente**: /// - Despacha [`action::theme::PrepareRender`](crate::base::action::theme::PrepareRender) -/// para permitir al tema preparar un renderizado diferente al predefinido. -/// - Si no es así, ejecuta [`prepare_component()`](Component::prepare_component) para preparar -/// el renderizado predefinido del componente. +/// para permitir al tema generar un renderizado alternativo. +/// - Si el tema no lo modifica, llama a [`prepare_component()`](Component::prepare_component) +/// para obtener el renderizado por defecto del componente. /// 6. Despacha [`action::theme::AfterRender`](crate::base::action::theme::AfterRender) para -/// que el tema pueda hacer sus últimos ajustes. +/// que el tema pueda aplicar ajustes finales. /// 7. Despacha [`action::component::AfterRender`](crate::base::action::component::AfterRender) /// para que otras extensiones puedan hacer sus últimos ajustes. -/// 8. Finalmente devuelve un [`Markup`] del renderizado preparado en el paso 5. +/// 8. Devuelve el [`Markup`] generado en el paso 5. impl ComponentRender for C { fn render(&mut self, cx: &mut Context) -> Markup { // Si no es renderizable, devuelve un bloque HTML vacío. - if !action::component::IsRenderable::dispatch(self, cx) { + if !self.is_renderable(cx) { return html! {}; }