♻️ (pagetop): Refactorización de Contextual
Los métodos `required_id()` y `push_message()` son operaciones de tiempo de renderizado, no de construcción. Se trasladan como métodos inherentes de `Context`. También se ajustan los métodos asociados a parámetros.
This commit is contained in:
parent
318d7de2b6
commit
0419658192
7 changed files with 121 additions and 189 deletions
|
|
@ -79,10 +79,10 @@ impl Component for Navbar {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Asegura que la barra tiene un `id` para poder asociarlo al colapso/offcanvas.
|
// Asegura que la barra tiene un `id` para poder asociarlo al colapso/offcanvas.
|
||||||
let id = cx.required_id::<Self>(self.id());
|
let id = cx.required_id::<Self>(self.id(), 1);
|
||||||
|
|
||||||
Ok(html! {
|
Ok(html! {
|
||||||
nav id=(id) class=[self.classes().get()] {
|
nav id=(&id) class=[self.classes().get()] {
|
||||||
div class="container-fluid" {
|
div class="container-fluid" {
|
||||||
@match self.layout() {
|
@match self.layout() {
|
||||||
// Barra más sencilla: sólo contenido.
|
// Barra más sencilla: sólo contenido.
|
||||||
|
|
@ -95,7 +95,7 @@ impl Component for Navbar {
|
||||||
@let id_content = util::join!(id, "-content");
|
@let id_content = util::join!(id, "-content");
|
||||||
|
|
||||||
(button(cx, TOGGLE_COLLAPSE, &id_content))
|
(button(cx, TOGGLE_COLLAPSE, &id_content))
|
||||||
div id=(id_content) class="collapse navbar-collapse" {
|
div id=(&id_content) class="collapse navbar-collapse" {
|
||||||
(items)
|
(items)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -112,7 +112,7 @@ impl Component for Navbar {
|
||||||
|
|
||||||
(brand.render(cx))
|
(brand.render(cx))
|
||||||
(button(cx, TOGGLE_COLLAPSE, &id_content))
|
(button(cx, TOGGLE_COLLAPSE, &id_content))
|
||||||
div id=(id_content) class="collapse navbar-collapse" {
|
div id=(&id_content) class="collapse navbar-collapse" {
|
||||||
(items)
|
(items)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -123,7 +123,7 @@ impl Component for Navbar {
|
||||||
|
|
||||||
(button(cx, TOGGLE_COLLAPSE, &id_content))
|
(button(cx, TOGGLE_COLLAPSE, &id_content))
|
||||||
(brand.render(cx))
|
(brand.render(cx))
|
||||||
div id=(id_content) class="collapse navbar-collapse" {
|
div id=(&id_content) class="collapse navbar-collapse" {
|
||||||
(items)
|
(items)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -152,7 +152,7 @@ impl Offcanvas {
|
||||||
return html! {};
|
return html! {};
|
||||||
}
|
}
|
||||||
|
|
||||||
let id = cx.required_id::<Self>(self.id());
|
let id = cx.required_id::<Self>(self.id(), 1);
|
||||||
let id_label = util::join!(id, "-label");
|
let id_label = util::join!(id, "-label");
|
||||||
let id_target = util::join!("#", id);
|
let id_target = util::join!("#", id);
|
||||||
|
|
||||||
|
|
@ -171,7 +171,7 @@ impl Offcanvas {
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
div
|
div
|
||||||
id=(id)
|
id=(&id)
|
||||||
class=[self.classes().get()]
|
class=[self.classes().get()]
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
data-bs-scroll=[body_scroll]
|
data-bs-scroll=[body_scroll]
|
||||||
|
|
@ -180,7 +180,7 @@ impl Offcanvas {
|
||||||
{
|
{
|
||||||
div class="offcanvas-header" {
|
div class="offcanvas-header" {
|
||||||
@if !title.is_empty() {
|
@if !title.is_empty() {
|
||||||
h5 class="offcanvas-title" id=(id_label) { (title) }
|
h5 id=(&id_label) class="offcanvas-title" { (title) }
|
||||||
}
|
}
|
||||||
button
|
button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
|
||||||
|
|
@ -36,10 +36,10 @@ impl Component for Block {
|
||||||
return Ok(html! {});
|
return Ok(html! {});
|
||||||
}
|
}
|
||||||
|
|
||||||
let id = cx.required_id::<Block>(self.id());
|
let id = cx.required_id::<Self>(self.id(), 1);
|
||||||
|
|
||||||
Ok(html! {
|
Ok(html! {
|
||||||
div id=(id) class=[self.classes().get()] {
|
div id=(&id) class=[self.classes().get()] {
|
||||||
@if let Some(title) = self.title().lookup(cx) {
|
@if let Some(title) = self.title().lookup(cx) {
|
||||||
h2 class="block__title" { span { (title) } }
|
h2 class="block__title" { span { (title) } }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ use std::sync::Arc;
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use pagetop::prelude::*;
|
/// # use pagetop::prelude::*;
|
||||||
/// let component = Html::with(|cx| {
|
/// let component = Html::with(|cx| {
|
||||||
/// let user = cx.param::<String>("username").cloned().unwrap_or("visitor".to_string());
|
/// let user = cx.param_or("username", "visitor".to_string());
|
||||||
/// html! {
|
/// html! {
|
||||||
/// h1 { "Hello, " (user) }
|
/// h1 { "Hello, " (user) }
|
||||||
/// }
|
/// }
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ pub use context::{AssetsOp, Context, ContextError, Contextual};
|
||||||
///
|
///
|
||||||
/// // Se instancia un componente que sólo se renderiza si `user_logged_in` es `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| {
|
/// let mut component = SampleComponent::new().with_renderable(Some(|cx: &Context| {
|
||||||
/// cx.param::<bool>("user_logged_in").copied().unwrap_or(false)
|
/// cx.param_or_default::<bool>("user_logged_in")
|
||||||
/// }));
|
/// }));
|
||||||
///
|
///
|
||||||
/// // Aquí simplemente se comprueba que compila y se puede invocar.
|
/// // Aquí simplemente se comprueba que compila y se puede invocar.
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::core::component::{ChildOp, MessageLevel, StatusMessage};
|
use crate::core::component::{ChildOp, Component, MessageLevel, StatusMessage};
|
||||||
use crate::core::theme::all::DEFAULT_THEME;
|
use crate::core::theme::all::DEFAULT_THEME;
|
||||||
use crate::core::theme::{ChildrenInRegions, DefaultRegion, RegionRef, TemplateRef, ThemeRef};
|
use crate::core::theme::{ChildrenInRegions, DefaultRegion, RegionRef, TemplateRef, ThemeRef};
|
||||||
use crate::core::TypeInfo;
|
use crate::core::TypeInfo;
|
||||||
|
|
@ -77,7 +77,6 @@ impl std::error::Error for ContextError {}
|
||||||
/// - Administrar **recursos** del documento como el icono [`Favicon`], las hojas de estilo
|
/// - Administrar **recursos** del documento como el icono [`Favicon`], las hojas de estilo
|
||||||
/// [`StyleSheet`] o los scripts [`JavaScript`] mediante [`AssetsOp`].
|
/// [`StyleSheet`] o los scripts [`JavaScript`] mediante [`AssetsOp`].
|
||||||
/// - Leer y mantener **parámetros dinámicos tipados** de contexto.
|
/// - Leer y mantener **parámetros dinámicos tipados** de contexto.
|
||||||
/// - Generar **identificadores únicos** por tipo de componente.
|
|
||||||
///
|
///
|
||||||
/// Lo implementan, típicamente, estructuras que manejan el contexto de renderizado, como
|
/// Lo implementan, típicamente, estructuras que manejan el contexto de renderizado, como
|
||||||
/// [`Context`](crate::core::component::Context) o [`Page`](crate::response::page::Page).
|
/// [`Context`](crate::core::component::Context) o [`Page`](crate::response::page::Page).
|
||||||
|
|
@ -94,7 +93,7 @@ impl std::error::Error for ContextError {}
|
||||||
/// .with_assets(AssetsOp::SetFavicon(Some(Favicon::new().with_icon("/favicon.ico"))))
|
/// .with_assets(AssetsOp::SetFavicon(Some(Favicon::new().with_icon("/favicon.ico"))))
|
||||||
/// .with_assets(AssetsOp::AddStyleSheet(StyleSheet::from("/css/app.css")))
|
/// .with_assets(AssetsOp::AddStyleSheet(StyleSheet::from("/css/app.css")))
|
||||||
/// .with_assets(AssetsOp::AddJavaScript(JavaScript::defer("/js/app.js")))
|
/// .with_assets(AssetsOp::AddJavaScript(JavaScript::defer("/js/app.js")))
|
||||||
/// .with_param("usuario_id", 42_i32)
|
/// .with_param("user_id", 42_i32)
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub trait Contextual: LangId {
|
pub trait Contextual: LangId {
|
||||||
|
|
@ -118,16 +117,17 @@ pub trait Contextual: LangId {
|
||||||
|
|
||||||
/// Añade o modifica un parámetro dinámico del contexto.
|
/// Añade o modifica un parámetro dinámico del contexto.
|
||||||
///
|
///
|
||||||
/// El valor se guardará conservando el *nombre del tipo* real para mejorar los mensajes de
|
/// El valor se almacena junto con el nombre de su tipo, lo que permite generar mensajes de
|
||||||
/// error posteriores.
|
/// error precisos al recuperarlo con [`param`](Contextual::param) si el tipo solicitado no
|
||||||
|
/// coincide.
|
||||||
///
|
///
|
||||||
/// # Ejemplos
|
/// # Ejemplo
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use pagetop::prelude::*;
|
/// # use pagetop::prelude::*;
|
||||||
/// let cx = Context::new(None)
|
/// let cx = Context::new(None)
|
||||||
/// .with_param("usuario_id", 42_i32)
|
/// .with_param("user_id", 42_i32)
|
||||||
/// .with_param("titulo", "Hola".to_string())
|
/// .with_param("title", "Hello".to_string())
|
||||||
/// .with_param("flags", vec!["a", "b"]);
|
/// .with_param("flags", vec!["a", "b"]);
|
||||||
/// ```
|
/// ```
|
||||||
#[builder_fn]
|
#[builder_fn]
|
||||||
|
|
@ -158,49 +158,43 @@ pub trait Contextual: LangId {
|
||||||
/// Devuelve la plantilla configurada para renderizar el documento.
|
/// Devuelve la plantilla configurada para renderizar el documento.
|
||||||
fn template(&self) -> TemplateRef;
|
fn template(&self) -> TemplateRef;
|
||||||
|
|
||||||
/// Recupera un parámetro como [`Option`], simplificando el acceso.
|
/// Recupera una *referencia tipada* al parámetro solicitado.
|
||||||
///
|
///
|
||||||
/// A diferencia de [`get_param`](Context::get_param), que devuelve un [`Result`] con
|
/// Devuelve:
|
||||||
/// información detallada de error, este método devuelve `None` tanto si la clave no existe como
|
|
||||||
/// si el valor guardado no coincide con el tipo solicitado.
|
|
||||||
///
|
///
|
||||||
/// Resulta útil en escenarios donde sólo interesa saber si el valor existe y es del tipo
|
/// - `Ok(&T)` si la clave existe y el tipo coincide.
|
||||||
/// correcto, sin necesidad de diferenciar entre error de ausencia o de tipo.
|
/// - `Err(ContextError::ParamNotFound)` si la clave no existe.
|
||||||
|
/// - `Err(ContextError::ParamTypeMismatch)` si la clave existe pero el tipo no coincide.
|
||||||
///
|
///
|
||||||
/// # Ejemplo
|
/// # Ejemplo
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use pagetop::prelude::*;
|
/// # use pagetop::prelude::*;
|
||||||
/// let cx = Context::new(None).with_param("username", "Alice".to_string());
|
/// let cx = Context::new(None)
|
||||||
|
/// .with_param("user_id", 42_i32)
|
||||||
|
/// .with_param("title", "Hello".to_string());
|
||||||
///
|
///
|
||||||
/// // Devuelve Some(&String) si existe y coincide el tipo.
|
/// let id: i32 = *cx.param("user_id").unwrap();
|
||||||
/// assert_eq!(cx.param::<String>("username").map(|s| s.as_str()), Some("Alice"));
|
/// let title: &String = cx.param("title").unwrap();
|
||||||
///
|
///
|
||||||
/// // Devuelve None si no existe o si el tipo no coincide.
|
/// // Error de tipo:
|
||||||
/// assert!(cx.param::<i32>("username").is_none());
|
/// assert!(cx.param::<String>("user_id").is_err());
|
||||||
/// assert!(cx.param::<String>("missing").is_none());
|
|
||||||
///
|
|
||||||
/// // Acceso con valor por defecto.
|
|
||||||
/// let user = cx.param::<String>("missing")
|
|
||||||
/// .cloned()
|
|
||||||
/// .unwrap_or_else(|| "visitor".to_string());
|
|
||||||
/// assert_eq!(user, "visitor");
|
|
||||||
/// ```
|
/// ```
|
||||||
fn param<T: 'static>(&self, key: &'static str) -> Option<&T>;
|
fn param<T: 'static>(&self, key: &'static str) -> Result<&T, ContextError>;
|
||||||
|
|
||||||
/// Devuelve el parámetro clonado o el **valor por defecto del tipo** (`T::default()`).
|
/// Devuelve el parámetro clonado o el **valor por defecto del tipo** (`T::default()`).
|
||||||
fn param_or_default<T: Default + Clone + 'static>(&self, key: &'static str) -> T {
|
fn param_or_default<T: Clone + Default + 'static>(&self, key: &'static str) -> T {
|
||||||
self.param::<T>(key).cloned().unwrap_or_default()
|
self.param::<T>(key).ok().cloned().unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Devuelve el parámetro clonado o un **valor por defecto** si no existe.
|
/// Devuelve el parámetro clonado o un **valor por defecto** si no existe.
|
||||||
fn param_or<T: Clone + 'static>(&self, key: &'static str, default: T) -> T {
|
fn param_or<T: Clone + 'static>(&self, key: &'static str, default: T) -> T {
|
||||||
self.param::<T>(key).cloned().unwrap_or(default)
|
self.param::<T>(key).ok().cloned().unwrap_or(default)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Devuelve el parámetro clonado o el **valor evaluado** por la función `f` si no existe.
|
/// Devuelve el parámetro clonado o el **valor evaluado** por la función `f` si no existe.
|
||||||
fn param_or_else<T: Clone + 'static, F: FnOnce() -> T>(&self, key: &'static str, f: F) -> T {
|
fn param_or_else<T: Clone + 'static, F: FnOnce() -> T>(&self, key: &'static str, f: F) -> T {
|
||||||
self.param::<T>(key).cloned().unwrap_or_else(f)
|
self.param::<T>(key).ok().cloned().unwrap_or_else(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Devuelve el Favicon de los recursos del contexto.
|
/// Devuelve el Favicon de los recursos del contexto.
|
||||||
|
|
@ -214,27 +208,17 @@ pub trait Contextual: LangId {
|
||||||
|
|
||||||
// **< Contextual HELPERS >*********************************************************************
|
// **< Contextual HELPERS >*********************************************************************
|
||||||
|
|
||||||
/// Devuelve el `id` proporcionado tal cual, o genera uno único para el tipo `T` si no se
|
/// Elimina un parámetro del contexto. Devuelve `true` si la clave existía y se eliminó.
|
||||||
/// proporciona ninguno.
|
|
||||||
///
|
|
||||||
/// Si `id` es `None`, construye un identificador en la forma `<tipo>-<n>`, donde `<tipo>` es el
|
|
||||||
/// nombre corto del tipo en minúsculas y `<n>` un contador incremental interno del contexto. Es
|
|
||||||
/// útil para asignar identificadores HTML predecibles cuando el componente no recibe uno
|
|
||||||
/// explícito.
|
|
||||||
fn required_id<T>(&self, id: Option<String>) -> String;
|
|
||||||
|
|
||||||
/// Acumula un [`StatusMessage`] en el contexto para notificar al visitante.
|
|
||||||
///
|
|
||||||
/// Pueden generarse en cualquier punto del ciclo de una petición web (manejadores, renderizado,
|
|
||||||
/// lógica de negocio, etc.) que tengan acceso al contexto, y mostrarlos luego, por ejemplo, en
|
|
||||||
/// la página final devuelta al usuario.
|
|
||||||
///
|
///
|
||||||
/// # Ejemplo
|
/// # Ejemplo
|
||||||
///
|
///
|
||||||
/// ```rust,ignore
|
/// ```rust
|
||||||
/// cx.push_message(MessageLevel::Warning, L10n::l("session-not-valid"));
|
/// # use pagetop::prelude::*;
|
||||||
|
/// let mut cx = Context::new(None).with_param("temp", 1u8);
|
||||||
|
/// assert!(cx.remove_param("temp"));
|
||||||
|
/// assert!(!cx.remove_param("temp")); // ya no existe
|
||||||
/// ```
|
/// ```
|
||||||
fn push_message(&mut self, level: MessageLevel, text: L10n);
|
fn remove_param(&mut self, key: &'static str) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Implementa un **contexto de renderizado** para un documento HTML.
|
/// Implementa un **contexto de renderizado** para un documento HTML.
|
||||||
|
|
@ -264,7 +248,7 @@ pub trait Contextual: LangId {
|
||||||
/// // Añade un script JavaScript.
|
/// // Añade un script JavaScript.
|
||||||
/// .with_assets(AssetsOp::AddJavaScript(JavaScript::defer("/js/main.js")))
|
/// .with_assets(AssetsOp::AddJavaScript(JavaScript::defer("/js/main.js")))
|
||||||
/// // Añade un parámetro dinámico al contexto.
|
/// // Añade un parámetro dinámico al contexto.
|
||||||
/// .with_param("usuario_id", 42)
|
/// .with_param("user_id", 42)
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
|
|
@ -272,34 +256,38 @@ pub trait Contextual: LangId {
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use pagetop::prelude::*;
|
/// # use pagetop::prelude::*;
|
||||||
|
/// # #[derive(AutoDefault, Clone, Debug)]
|
||||||
|
/// # struct Menu;
|
||||||
|
/// # impl Component for Menu {
|
||||||
|
/// # fn new() -> Self { Self::default() }
|
||||||
|
/// # }
|
||||||
/// fn use_context(cx: &mut Context) {
|
/// fn use_context(cx: &mut Context) {
|
||||||
/// // Recupera el tema seleccionado.
|
/// // Recupera el tema seleccionado.
|
||||||
/// let active_theme = cx.theme();
|
/// let active_theme = cx.theme();
|
||||||
/// assert_eq!(active_theme.short_name(), "aliner");
|
/// assert_eq!(active_theme.short_name(), "aliner");
|
||||||
///
|
///
|
||||||
/// // Recupera el parámetro a su tipo original.
|
/// // Recupera el parámetro a su tipo original.
|
||||||
/// let id: i32 = *cx.get_param::<i32>("usuario_id").unwrap();
|
/// let id: i32 = *cx.param::<i32>("user_id").unwrap();
|
||||||
/// assert_eq!(id, 42);
|
/// assert_eq!(id, 42);
|
||||||
///
|
///
|
||||||
/// // Genera un identificador para un componente de tipo `Menu`.
|
/// // Genera un identificador para un componente de tipo `Menu`.
|
||||||
/// struct Menu;
|
/// let unique_id = cx.required_id::<Menu>(None, 1);
|
||||||
/// let unique_id = cx.required_id::<Menu>(None);
|
|
||||||
/// assert_eq!(unique_id, "menu-1"); // Si es el primero generado.
|
/// assert_eq!(unique_id, "menu-1"); // Si es el primero generado.
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
pub struct Context {
|
pub struct Context {
|
||||||
request : Option<HttpRequest>, // Petición HTTP de origen.
|
request : Option<HttpRequest>, // Petición HTTP de origen.
|
||||||
locale : RequestLocale, // Idioma asociado a la petición.
|
locale : RequestLocale, // Idioma asociado a la petición.
|
||||||
theme : ThemeRef, // Referencia al tema usado para renderizar.
|
theme : ThemeRef, // Referencia al tema usado para renderizar.
|
||||||
template : TemplateRef, // Plantilla usada para renderizar.
|
template : TemplateRef, // Plantilla usada para renderizar.
|
||||||
favicon : Option<Favicon>, // Favicon, si se ha definido.
|
favicon : Option<Favicon>, // Favicon, si se ha definido.
|
||||||
stylesheets: Assets<StyleSheet>, // Hojas de estilo CSS.
|
stylesheets: Assets<StyleSheet>, // Hojas de estilo CSS.
|
||||||
javascripts: Assets<JavaScript>, // Scripts JavaScript.
|
javascripts: Assets<JavaScript>, // Scripts JavaScript.
|
||||||
regions : ChildrenInRegions, // Regiones de componentes para renderizar.
|
regions : ChildrenInRegions, // Regiones de componentes para renderizar.
|
||||||
params : HashMap<&'static str, (Box<dyn Any>, &'static str)>, // Parámetros en ejecución.
|
params : HashMap<&'static str, (Box<dyn Any>, &'static str)>, // Parámetros en ejecución.
|
||||||
id_counter : Cell<usize>, // Cell permite incrementarlo desde &self en required_id().
|
id_counter : Cell<usize>, // Cell permite incrementar desde &self en required_id().
|
||||||
messages : Vec<StatusMessage>, // Mensajes de usuario acumulados.
|
messages : Vec<StatusMessage>, // Mensajes de usuario acumulados.
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Context {
|
impl Default for Context {
|
||||||
|
|
@ -366,90 +354,6 @@ impl Context {
|
||||||
.render(self)
|
.render(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
// **< Context PARAMS >*************************************************************************
|
|
||||||
|
|
||||||
/// Recupera una *referencia tipada* al parámetro solicitado.
|
|
||||||
///
|
|
||||||
/// Devuelve:
|
|
||||||
///
|
|
||||||
/// - `Ok(&T)` si la clave existe y el tipo coincide.
|
|
||||||
/// - `Err(ContextError::ParamNotFound)` si la clave no existe.
|
|
||||||
/// - `Err(ContextError::ParamTypeMismatch)` si la clave existe pero el tipo no coincide.
|
|
||||||
///
|
|
||||||
/// # Ejemplos
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # use pagetop::prelude::*;
|
|
||||||
/// let cx = Context::new(None)
|
|
||||||
/// .with_param("usuario_id", 42_i32)
|
|
||||||
/// .with_param("titulo", "Hola".to_string());
|
|
||||||
///
|
|
||||||
/// let id: &i32 = cx.get_param("usuario_id").unwrap();
|
|
||||||
/// let titulo: &String = cx.get_param("titulo").unwrap();
|
|
||||||
///
|
|
||||||
/// // Error de tipo:
|
|
||||||
/// assert!(cx.get_param::<String>("usuario_id").is_err());
|
|
||||||
/// ```
|
|
||||||
pub fn get_param<T: 'static>(&self, key: &'static str) -> Result<&T, ContextError> {
|
|
||||||
let (any, type_name) = self.params.get(key).ok_or(ContextError::ParamNotFound)?;
|
|
||||||
any.downcast_ref::<T>()
|
|
||||||
.ok_or_else(|| ContextError::ParamTypeMismatch {
|
|
||||||
key,
|
|
||||||
expected: TypeInfo::FullName.of::<T>(),
|
|
||||||
saved: type_name,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Recupera el parámetro solicitado y lo elimina del contexto.
|
|
||||||
///
|
|
||||||
/// Devuelve:
|
|
||||||
///
|
|
||||||
/// - `Ok(T)` si la clave existía y el tipo coincide.
|
|
||||||
/// - `Err(ContextError::ParamNotFound)` si la clave no existe.
|
|
||||||
/// - `Err(ContextError::ParamTypeMismatch)` si el tipo no coincide.
|
|
||||||
///
|
|
||||||
/// # Ejemplos
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # use pagetop::prelude::*;
|
|
||||||
/// let mut cx = Context::new(None)
|
|
||||||
/// .with_param("contador", 7_i32)
|
|
||||||
/// .with_param("titulo", "Hola".to_string());
|
|
||||||
///
|
|
||||||
/// let n: i32 = cx.take_param("contador").unwrap();
|
|
||||||
/// assert!(cx.get_param::<i32>("contador").is_err()); // ya no está
|
|
||||||
///
|
|
||||||
/// // Error de tipo:
|
|
||||||
/// assert!(cx.take_param::<i32>("titulo").is_err());
|
|
||||||
/// ```
|
|
||||||
pub fn take_param<T: 'static>(&mut self, key: &'static str) -> Result<T, ContextError> {
|
|
||||||
let (boxed, saved) = self.params.remove(key).ok_or(ContextError::ParamNotFound)?;
|
|
||||||
boxed
|
|
||||||
.downcast::<T>()
|
|
||||||
.map(|b| *b)
|
|
||||||
.map_err(|_| ContextError::ParamTypeMismatch {
|
|
||||||
key,
|
|
||||||
expected: TypeInfo::FullName.of::<T>(),
|
|
||||||
saved,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Elimina un parámetro del contexto. Devuelve `true` si la clave existía y se eliminó.
|
|
||||||
///
|
|
||||||
/// Devuelve `false` en caso contrario. Usar cuando sólo interesa borrar la entrada.
|
|
||||||
///
|
|
||||||
/// # Ejemplos
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # use pagetop::prelude::*;
|
|
||||||
/// let mut cx = Context::new(None).with_param("temp", 1u8);
|
|
||||||
/// assert!(cx.remove_param("temp"));
|
|
||||||
/// assert!(!cx.remove_param("temp")); // ya no existe
|
|
||||||
/// ```
|
|
||||||
pub fn remove_param(&mut self, key: &'static str) -> bool {
|
|
||||||
self.params.remove(key).is_some()
|
|
||||||
}
|
|
||||||
|
|
||||||
// **< Context HELPERS >************************************************************************
|
// **< Context HELPERS >************************************************************************
|
||||||
|
|
||||||
/// Construye una ruta aplicada al contexto actual.
|
/// Construye una ruta aplicada al contexto actual.
|
||||||
|
|
@ -470,6 +374,51 @@ impl Context {
|
||||||
route
|
route
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Garantiza un identificador único para un componente `C`, generándolo si no se proporciona
|
||||||
|
/// ninguno.
|
||||||
|
///
|
||||||
|
/// Si `id` es `None`, crea un identificador usando los últimos segmentos del *path* completo
|
||||||
|
/// del tipo `C`, separados por `-` y en minúsculas, seguidos de un contador incremental interno
|
||||||
|
/// del contexto. Por ejemplo, para un componente `MyApp::ui::Menu` con `parts = 2` podría
|
||||||
|
/// devolver un identificador como `ui-menu-1` si ha sido el primero en generarse.
|
||||||
|
///
|
||||||
|
/// Con `parts = 1` se usa el nombre corto del tipo. Si `parts` es `0` o supera el número de
|
||||||
|
/// segmentos del *path*, entonces se usará el *path* completo.
|
||||||
|
///
|
||||||
|
/// Es útil para asignar identificadores HTML predecibles cuando el componente no recibe uno
|
||||||
|
/// explícito.
|
||||||
|
pub fn required_id<C: Component>(&self, id: Option<String>, parts: usize) -> String {
|
||||||
|
if let Some(id) = id {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
let segments: Vec<&str> = TypeInfo::FullName.of::<C>().split("::").collect();
|
||||||
|
let parts = if parts == 0 || parts >= segments.len() {
|
||||||
|
segments.len()
|
||||||
|
} else {
|
||||||
|
parts
|
||||||
|
};
|
||||||
|
self.id_counter.set(self.id_counter.get() + 1);
|
||||||
|
let prefix = segments[segments.len() - parts..].join("-").to_lowercase();
|
||||||
|
util::join!(prefix, "-", self.id_counter.get().to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Acumula un [`StatusMessage`] en el contexto para notificar al visitante.
|
||||||
|
///
|
||||||
|
/// Pueden generarse en cualquier punto del ciclo de una petición web (manejadores, renderizado,
|
||||||
|
/// lógica de negocio, etc.) que tengan acceso al contexto, y mostrarlos luego, por ejemplo, en
|
||||||
|
/// la página final devuelta al usuario.
|
||||||
|
///
|
||||||
|
/// # Ejemplo
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use pagetop::prelude::*;
|
||||||
|
/// # let mut cx = Context::new(None);
|
||||||
|
/// cx.push_message(MessageLevel::Warning, L10n::n("Session is not valid"));
|
||||||
|
/// ```
|
||||||
|
pub fn push_message(&mut self, level: MessageLevel, text: L10n) {
|
||||||
|
self.messages.push(StatusMessage::new(level, text));
|
||||||
|
}
|
||||||
|
|
||||||
/// Devuelve todos los mensajes de usuario acumulados.
|
/// Devuelve todos los mensajes de usuario acumulados.
|
||||||
pub fn messages(&self) -> &[StatusMessage] {
|
pub fn messages(&self) -> &[StatusMessage] {
|
||||||
&self.messages
|
&self.messages
|
||||||
|
|
@ -589,8 +538,14 @@ impl Contextual for Context {
|
||||||
self.template
|
self.template
|
||||||
}
|
}
|
||||||
|
|
||||||
fn param<T: 'static>(&self, key: &'static str) -> Option<&T> {
|
fn param<T: 'static>(&self, key: &'static str) -> Result<&T, ContextError> {
|
||||||
self.get_param::<T>(key).ok()
|
let (any, type_name) = self.params.get(key).ok_or(ContextError::ParamNotFound)?;
|
||||||
|
any.downcast_ref::<T>()
|
||||||
|
.ok_or_else(|| ContextError::ParamTypeMismatch {
|
||||||
|
key,
|
||||||
|
expected: TypeInfo::FullName.of::<T>(),
|
||||||
|
saved: type_name,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn favicon(&self) -> Option<&Favicon> {
|
fn favicon(&self) -> Option<&Favicon> {
|
||||||
|
|
@ -607,26 +562,7 @@ impl Contextual for Context {
|
||||||
|
|
||||||
// **< Contextual HELPERS >*********************************************************************
|
// **< Contextual HELPERS >*********************************************************************
|
||||||
|
|
||||||
fn required_id<T>(&self, id: Option<String>) -> String {
|
fn remove_param(&mut self, key: &'static str) -> bool {
|
||||||
if let Some(id) = id {
|
self.params.remove(key).is_some()
|
||||||
id
|
|
||||||
} else {
|
|
||||||
let prefix = TypeInfo::ShortName
|
|
||||||
.of::<T>()
|
|
||||||
.trim()
|
|
||||||
.replace(' ', "_")
|
|
||||||
.to_lowercase();
|
|
||||||
let prefix = if prefix.is_empty() {
|
|
||||||
"prefix".to_string()
|
|
||||||
} else {
|
|
||||||
prefix
|
|
||||||
};
|
|
||||||
self.id_counter.set(self.id_counter.get() + 1);
|
|
||||||
util::join!(prefix, "-", self.id_counter.get().to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_message(&mut self, level: MessageLevel, text: L10n) {
|
|
||||||
self.messages.push(StatusMessage::new(level, text));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ pub use error::ErrorPage;
|
||||||
pub use actix_web::Result as ResultPage;
|
pub use actix_web::Result as ResultPage;
|
||||||
|
|
||||||
use crate::base::action;
|
use crate::base::action;
|
||||||
use crate::core::component::{AssetsOp, ChildOp, Context, Contextual};
|
use crate::core::component::{AssetsOp, ChildOp, Context, ContextError, Contextual};
|
||||||
use crate::core::theme::{DefaultRegion, Region, RegionRef, TemplateRef, ThemeRef};
|
use crate::core::theme::{DefaultRegion, Region, RegionRef, TemplateRef, ThemeRef};
|
||||||
use crate::html::{html, Markup, DOCTYPE};
|
use crate::html::{html, Markup, DOCTYPE};
|
||||||
use crate::html::{Assets, Favicon, JavaScript, StyleSheet};
|
use crate::html::{Assets, Favicon, JavaScript, StyleSheet};
|
||||||
|
|
@ -349,7 +349,7 @@ impl Contextual for Page {
|
||||||
self.context.template()
|
self.context.template()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn param<T: 'static>(&self, key: &'static str) -> Option<&T> {
|
fn param<T: 'static>(&self, key: &'static str) -> Result<&T, ContextError> {
|
||||||
self.context.param(key)
|
self.context.param(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -367,11 +367,7 @@ impl Contextual for Page {
|
||||||
|
|
||||||
// **< Contextual HELPERS >*********************************************************************
|
// **< Contextual HELPERS >*********************************************************************
|
||||||
|
|
||||||
fn required_id<T>(&self, id: Option<String>) -> String {
|
fn remove_param(&mut self, key: &'static str) -> bool {
|
||||||
self.context.required_id::<T>(id)
|
self.context.remove_param(key)
|
||||||
}
|
|
||||||
|
|
||||||
fn push_message(&mut self, level: crate::prelude::MessageLevel, text: L10n) {
|
|
||||||
self.context.push_message(level, text);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue