diff --git a/src/global.rs b/src/global.rs index 6be0774..c81eec9 100644 --- a/src/global.rs +++ b/src/global.rs @@ -48,14 +48,13 @@ pub struct App { pub description: String, /// Tema predeterminado. pub theme: String, - /// Idioma por defecto para la aplicación. + /// Idioma por defecto de la aplicación. /// - /// Si no se especifica un valor válido, normalmente se usará el idioma devuelto por la - /// implementación de [`LangId`](crate::locale::LangId) para [`Context`](crate::html::Context), - /// en el siguiente orden: primero, el idioma establecido explícitamente con - /// [`Context::with_langid()`](crate::html::Context::with_langid); si no se ha definido, se - /// usará el indicado en la cabecera `Accept-Language` del navegador; y, si ninguno aplica, se - /// empleará el idioma de respaldo ("en-US"). + /// Si este valor no es válido, el idioma efectivo para el renderizado se resolverá mediante la + /// implementación de [`LangId`](crate::locale::LangId) en este orden: primero, el establecido + /// explícitamente con [`Contextual::with_langid()`](crate::html::Contextual::with_langid); si + /// no se ha definido, se usará el indicado en la cabecera `Accept-Language` del navegador; y, + /// si ninguno aplica, se empleará el idioma de respaldo ("en-US"). pub language: String, /// Banner ASCII mostrado al inicio: *"Off"* (desactivado), *"Slant"*, *"Small"*, *"Speed"* o /// *"Starwars"*. diff --git a/src/html.rs b/src/html.rs index 784457e..9f3d70c 100644 --- a/src/html.rs +++ b/src/html.rs @@ -9,12 +9,12 @@ mod assets; pub use assets::favicon::Favicon; pub use assets::javascript::JavaScript; pub use assets::stylesheet::{StyleSheet, TargetMedia}; -pub(crate) use assets::Assets; +pub use assets::{Asset, Assets}; // HTML DOCUMENT CONTEXT *************************************************************************** mod context; -pub use context::{AssetsOp, Context, ErrorParam}; +pub use context::{AssetsOp, Context, Contextual, ErrorParam}; // HTML ATTRIBUTES ********************************************************************************* diff --git a/src/html/assets.rs b/src/html/assets.rs index 894b7e8..e53e8e3 100644 --- a/src/html/assets.rs +++ b/src/html/assets.rs @@ -5,20 +5,20 @@ pub mod stylesheet; use crate::html::{html, Markup, Render}; use crate::{AutoDefault, Weight}; -pub trait AssetsTrait: Render { - // Devuelve el nombre del recurso, utilizado como clave única. +pub trait Asset: Render { + /// Devuelve el nombre del recurso, utilizado como clave única. fn name(&self) -> &str; - // Devuelve el peso del recurso, durante el renderizado se procesan de menor a mayor peso. + /// Devuelve el peso del recurso, durante el renderizado se procesan de menor a mayor peso. fn weight(&self) -> Weight; } #[derive(AutoDefault)] -pub(crate) struct Assets(Vec); +pub struct Assets(Vec); -impl Assets { +impl Assets { pub fn new() -> Self { - Assets::(Vec::::new()) + Self(Vec::new()) } pub fn add(&mut self, asset: T) -> bool { @@ -49,14 +49,14 @@ impl Assets { } } -impl Render for Assets { +impl Render for Assets { fn render(&self) -> Markup { let mut assets = self.0.iter().collect::>(); assets.sort_by_key(|a| a.weight()); html! { @for a in assets { - (a.render()) + (a) } } } diff --git a/src/html/assets/javascript.rs b/src/html/assets/javascript.rs index db5754e..89b5261 100644 --- a/src/html/assets/javascript.rs +++ b/src/html/assets/javascript.rs @@ -1,4 +1,4 @@ -use crate::html::assets::AssetsTrait; +use crate::html::assets::Asset; use crate::html::{html, Markup, Render}; use crate::{join, join_pair, AutoDefault, Weight}; @@ -137,7 +137,7 @@ impl JavaScript { } } -impl AssetsTrait for JavaScript { +impl Asset for JavaScript { // Para *scripts* externos es la ruta; para *scripts* embebidos, un identificador. fn name(&self) -> &str { match &self.source { diff --git a/src/html/assets/stylesheet.rs b/src/html/assets/stylesheet.rs index bb60b01..a553726 100644 --- a/src/html/assets/stylesheet.rs +++ b/src/html/assets/stylesheet.rs @@ -1,4 +1,4 @@ -use crate::html::assets::AssetsTrait; +use crate::html::assets::Asset; use crate::html::{html, Markup, PreEscaped, Render}; use crate::{join_pair, AutoDefault, Weight}; @@ -142,7 +142,7 @@ impl StyleSheet { } } -impl AssetsTrait for StyleSheet { +impl Asset for StyleSheet { // Para hojas de estilos externas es la ruta; para las embebidas, un identificador. fn name(&self) -> &str { match &self.source { diff --git a/src/html/context.rs b/src/html/context.rs index 4ebd510..7af884c 100644 --- a/src/html/context.rs +++ b/src/html/context.rs @@ -10,7 +10,7 @@ use crate::{builder_fn, join}; use std::any::Any; use std::collections::HashMap; -/// Operaciones para modificar el contexto ([`Context`]) del documento. +/// Operaciones para modificar el contexto ([`Context`]) de un documento. pub enum AssetsOp { // Favicon. /// Define el *favicon* del documento. Sobrescribe cualquier valor anterior. @@ -47,7 +47,64 @@ pub enum ErrorParam { }, } -/// Representa el contexto de un documento HTML. +pub trait Contextual: LangId { + // Contextual BUILDER ************************************************************************** + + /// Asigna la fuente de idioma del documento. + #[builder_fn] + fn with_langid(self, language: &impl LangId) -> Self; + + /// Asigna la solicitud HTTP al contexto. + #[builder_fn] + fn with_request(self, request: Option) -> Self; + + /// Asigna el tema para renderizar el documento. + #[builder_fn] + fn with_theme(self, theme_name: &'static str) -> Self; + + /// Asigna la composición para renderizar el documento. + #[builder_fn] + fn with_layout(self, layout_name: &'static str) -> Self; + + /// Añade o modifica un parámetro dinámico del contexto. + #[builder_fn] + fn with_param(self, key: &'static str, value: T) -> Self; + + /// Define los recursos del contexto usando [`AssetsOp`]. + #[builder_fn] + fn with_assets(self, op: AssetsOp) -> Self; + + // Contextual GETTERS ************************************************************************** + + /// Devuelve una referencia a la solicitud HTTP asociada, si existe. + fn request(&self) -> Option<&HttpRequest>; + + /// Devuelve el tema que se usará para renderizar el documento. + fn theme(&self) -> ThemeRef; + + /// Devuelve la composición para renderizar el documento. Por defecto es `"default"`. + fn layout(&self) -> &str; + + /// Recupera un parámetro como [`Option`], simplificando el acceso. + fn param(&self, key: &'static str) -> Option<&T>; + + /// Devuelve el Favicon de los recursos del contexto. + fn favicon(&self) -> Option<&Favicon>; + + /// Devuelve las hojas de estilo de los recursos del contexto. + fn stylesheets(&self) -> &Assets; + + /// Devuelve los scripts JavaScript de los recursos del contexto. + fn javascripts(&self) -> &Assets; + + // Contextual HELPERS ************************************************************************** + + /// Devuelve un identificador único dentro del contexto para el tipo `T`, si no se proporciona + /// un `id` explícito. + fn required_id(&mut self, id: Option) -> String; +} + +/// Implementa el contexto de un documento HTML. /// /// Se crea internamente para manejar información relevante del documento, como la solicitud HTTP de /// origen, el idioma, tema y composición para el renderizado, los recursos *favicon* ([`Favicon`]), @@ -107,7 +164,7 @@ pub struct Context { favicon : Option, // Favicon, si se ha definido. stylesheets: Assets, // Hojas de estilo CSS. javascripts: Assets, // Scripts JavaScript. - params : HashMap<&'static str, (Box, &'static str)>, // Parámetros definidos en tiempo de ejecución. + params : HashMap<&'static str, (Box, &'static str)>, // Parámetros en ejecución. id_counter : usize, // Contador para generar identificadores únicos. } @@ -151,80 +208,6 @@ impl Context { } } - // Context BUILDER ***************************************************************************** - - /// Modifica la fuente de idioma del documento. - #[builder_fn] - pub fn with_langid(mut self, language: &impl LangId) -> Self { - self.langid = language.langid(); - self - } - - /// Modifica el tema que se usará para renderizar el documento. - /// - /// Localiza el tema por su [`short_name()`](crate::core::AnyInfo::short_name), y si no aplica - /// ninguno entonces usará el tema por defecto. - #[builder_fn] - pub fn with_theme(mut self, theme_name: &'static str) -> Self { - self.theme = theme_by_short_name(theme_name).unwrap_or(*DEFAULT_THEME); - self - } - - /// Modifica la composición para renderizar el documento. - #[builder_fn] - pub fn with_layout(mut self, layout_name: &'static str) -> Self { - self.layout = layout_name; - self - } - - /// Define los recursos del contexto usando [`AssetsOp`]. - #[builder_fn] - pub fn with_assets(mut self, op: AssetsOp) -> Self { - match op { - // Favicon. - AssetsOp::SetFavicon(favicon) => { - self.favicon = favicon; - } - AssetsOp::SetFaviconIfNone(icon) => { - if self.favicon.is_none() { - self.favicon = Some(icon); - } - } - // Stylesheets. - AssetsOp::AddStyleSheet(css) => { - self.stylesheets.add(css); - } - AssetsOp::RemoveStyleSheet(path) => { - self.stylesheets.remove(path); - } - // JavaScripts. - AssetsOp::AddJavaScript(js) => { - self.javascripts.add(js); - } - AssetsOp::RemoveJavaScript(path) => { - self.javascripts.remove(path); - } - } - self - } - - // Context GETTERS ***************************************************************************** - - /// Devuelve una referencia a la solicitud HTTP asociada, si existe. - pub fn request(&self) -> Option<&HttpRequest> { - self.request.as_ref() - } - - /// Devuelve el tema que se usará para renderizar el documento. - pub fn theme(&self) -> ThemeRef { - self.theme - } - - /// Devuelve la composición para renderizar el documento. Por defecto es `"default"`. - pub fn layout(&self) -> &str { - self.layout - } - // Context RENDER ****************************************************************************** /// Renderiza los recursos del contexto. @@ -240,61 +223,6 @@ impl Context { // Context PARAMS ****************************************************************************** - /// Añade o modifica un parámetro dinámico del contexto. - /// - /// El valor se guarda conservando el *nombre del tipo* real para mejorar los mensajes de error - /// posteriores. - /// - /// # Ejemplos - /// - /// ```rust - /// use pagetop::prelude::*; - /// - /// let cx = Context::new(None) - /// .with_param("usuario_id", 42_i32) - /// .with_param("titulo", String::from("Hola")) - /// .with_param("flags", vec!["a", "b"]); - /// ``` - #[builder_fn] - pub fn with_param(mut self, key: &'static str, value: T) -> Self { - let type_name = TypeInfo::FullName.of::(); - self.params.insert(key, (Box::new(value), type_name)); - self - } - - /// Recupera un parámetro como [`Option`], simplificando el acceso. - /// - /// A diferencia de [`get_param`](Self::get_param), que devuelve un [`Result`] con 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 - /// correcto, sin necesidad de diferenciar entre error de ausencia o de tipo. - /// - /// # Ejemplo - /// - /// ```rust - /// use pagetop::prelude::*; - /// - /// let cx = Context::new(None).with_param("username", String::from("Alice")); - /// - /// // Devuelve Some(&String) si existe y coincide el tipo. - /// assert_eq!(cx.param::("username").map(|s| s.as_str()), Some("Alice")); - /// - /// // Devuelve None si no existe o si el tipo no coincide. - /// assert!(cx.param::("username").is_none()); - /// assert!(cx.param::("missing").is_none()); - /// - /// // Acceso con valor por defecto. - /// let user = cx.param::("missing") - /// .cloned() - /// .unwrap_or_else(|| "visitor".to_string()); - /// assert_eq!(user, "visitor"); - /// ``` - pub fn param(&self, key: &'static str) -> Option<&T> { - self.get_param::(key).ok() - } - /// Recupera una *referencia tipada* al parámetro solicitado. /// /// Devuelve: @@ -328,23 +256,6 @@ impl Context { }) } - /// Elimina un parámetro del contexto. Devuelve `true` si la clave existía y se eliminó. - /// - /// Devuelve `false` en caso contrario. Usar cuando solo 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() - } - /// Recupera el parámetro solicitado y lo elimina del contexto. /// /// Devuelve: @@ -380,30 +291,21 @@ impl Context { }) } - // Context EXTRAS ****************************************************************************** - - /// Genera un identificador único si no se proporciona uno explícito. + /// Elimina un parámetro del contexto. Devuelve `true` si la clave existía y se eliminó. /// - /// Si no se proporciona un `id`, se genera un identificador único en la forma `-` - /// donde `` es el nombre corto del tipo en minúsculas (sin espacios) y `` es un - /// contador interno incremental. - pub fn required_id(&mut self, id: Option) -> String { - if let Some(id) = id { - id - } else { - let prefix = TypeInfo::ShortName - .of::() - .trim() - .replace(' ', "_") - .to_lowercase(); - let prefix = if prefix.is_empty() { - "prefix".to_owned() - } else { - prefix - }; - self.id_counter += 1; - join!(prefix, "-", self.id_counter.to_string()) - } + /// Devuelve `false` en caso contrario. Usar cuando solo 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() } } @@ -423,3 +325,173 @@ impl LangId for Context { self.langid } } + +impl Contextual for Context { + // Contextual BUILDER ************************************************************************** + + #[builder_fn] + fn with_request(mut self, request: Option) -> Self { + self.request = request; + self + } + + #[builder_fn] + fn with_langid(mut self, language: &impl LangId) -> Self { + self.langid = language.langid(); + self + } + + /// Asigna el tema para renderizar el documento. + /// + /// Localiza el tema por su [`short_name()`](crate::core::AnyInfo::short_name), y si no aplica + /// ninguno entonces usará el tema por defecto. + #[builder_fn] + fn with_theme(mut self, theme_name: &'static str) -> Self { + self.theme = theme_by_short_name(theme_name).unwrap_or(*DEFAULT_THEME); + self + } + + #[builder_fn] + fn with_layout(mut self, layout_name: &'static str) -> Self { + self.layout = layout_name; + self + } + + /// Añade o modifica un parámetro dinámico del contexto. + /// + /// El valor se guarda conservando el *nombre del tipo* real para mejorar los mensajes de error + /// posteriores. + /// + /// # Ejemplos + /// + /// ```rust + /// use pagetop::prelude::*; + /// + /// let cx = Context::new(None) + /// .with_param("usuario_id", 42_i32) + /// .with_param("titulo", String::from("Hola")) + /// .with_param("flags", vec!["a", "b"]); + /// ``` + #[builder_fn] + fn with_param(mut self, key: &'static str, value: T) -> Self { + let type_name = TypeInfo::FullName.of::(); + self.params.insert(key, (Box::new(value), type_name)); + self + } + + #[builder_fn] + fn with_assets(mut self, op: AssetsOp) -> Self { + match op { + // Favicon. + AssetsOp::SetFavicon(favicon) => { + self.favicon = favicon; + } + AssetsOp::SetFaviconIfNone(icon) => { + if self.favicon.is_none() { + self.favicon = Some(icon); + } + } + // Stylesheets. + AssetsOp::AddStyleSheet(css) => { + self.stylesheets.add(css); + } + AssetsOp::RemoveStyleSheet(path) => { + self.stylesheets.remove(path); + } + // JavaScripts. + AssetsOp::AddJavaScript(js) => { + self.javascripts.add(js); + } + AssetsOp::RemoveJavaScript(path) => { + self.javascripts.remove(path); + } + } + self + } + + // Contextual GETTERS ************************************************************************** + + fn request(&self) -> Option<&HttpRequest> { + self.request.as_ref() + } + + fn theme(&self) -> ThemeRef { + self.theme + } + + fn layout(&self) -> &str { + self.layout + } + + /// Recupera un parámetro como [`Option`], simplificando el acceso. + /// + /// A diferencia de [`get_param`](Self::get_param), que devuelve un [`Result`] con 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 + /// correcto, sin necesidad de diferenciar entre error de ausencia o de tipo. + /// + /// # Ejemplo + /// + /// ```rust + /// use pagetop::prelude::*; + /// + /// let cx = Context::new(None).with_param("username", String::from("Alice")); + /// + /// // Devuelve Some(&String) si existe y coincide el tipo. + /// assert_eq!(cx.param::("username").map(|s| s.as_str()), Some("Alice")); + /// + /// // Devuelve None si no existe o si el tipo no coincide. + /// assert!(cx.param::("username").is_none()); + /// assert!(cx.param::("missing").is_none()); + /// + /// // Acceso con valor por defecto. + /// let user = cx.param::("missing") + /// .cloned() + /// .unwrap_or_else(|| "visitor".to_string()); + /// assert_eq!(user, "visitor"); + /// ``` + fn param(&self, key: &'static str) -> Option<&T> { + self.get_param::(key).ok() + } + + fn favicon(&self) -> Option<&Favicon> { + self.favicon.as_ref() + } + + fn stylesheets(&self) -> &Assets { + &self.stylesheets + } + + fn javascripts(&self) -> &Assets { + &self.javascripts + } + + // Contextual HELPERS ************************************************************************** + + /// Devuelve un identificador único dentro del contexto para el tipo `T`, si no se proporciona + /// un `id` explícito. + /// + /// Si no se proporciona un `id`, se genera un identificador único en la forma `-` + /// donde `` es el nombre corto del tipo en minúsculas (sin espacios) y `` es un + /// contador interno incremental. + fn required_id(&mut self, id: Option) -> String { + if let Some(id) = id { + id + } else { + let prefix = TypeInfo::ShortName + .of::() + .trim() + .replace(' ', "_") + .to_lowercase(); + let prefix = if prefix.is_empty() { + "prefix".to_owned() + } else { + prefix + }; + self.id_counter += 1; + join!(prefix, "-", self.id_counter.to_string()) + } + } +} diff --git a/src/response/page.rs b/src/response/page.rs index 0942f8c..77bc9c4 100644 --- a/src/response/page.rs +++ b/src/response/page.rs @@ -8,7 +8,8 @@ use crate::builder_fn; use crate::core::component::{Child, ChildOp, Component}; use crate::core::theme::{ChildrenInRegions, ThemeRef, REGION_CONTENT}; use crate::html::{html, Markup, DOCTYPE}; -use crate::html::{AssetsOp, Context}; +use crate::html::{Assets, Favicon, JavaScript, StyleSheet}; +use crate::html::{AssetsOp, Context, Contextual}; use crate::html::{AttrClasses, ClassesOp}; use crate::html::{AttrId, AttrL10n}; use crate::locale::{CharacterDirection, L10n, LangId, LanguageIdentifier}; @@ -25,9 +26,9 @@ pub struct Page { description : AttrL10n, metadata : Vec<(&'static str, &'static str)>, properties : Vec<(&'static str, &'static str)>, - context : Context, body_id : AttrId, body_classes: AttrClasses, + context : Context, regions : ChildrenInRegions, } @@ -43,9 +44,9 @@ impl Page { description : AttrL10n::default(), metadata : Vec::default(), properties : Vec::default(), - context : Context::new(request), body_id : AttrId::default(), body_classes: AttrClasses::default(), + context : Context::new(request), regions : ChildrenInRegions::default(), } } @@ -80,40 +81,6 @@ impl Page { self } - /// Modifica la fuente de idioma de la página ([`Context::with_langid()`]). - #[builder_fn] - pub fn with_langid(mut self, language: &impl LangId) -> Self { - self.context.alter_langid(language); - self - } - - /// Modifica el tema que se usará para renderizar la página ([`Context::with_theme()`]). - #[builder_fn] - pub fn with_theme(mut self, theme_name: &'static str) -> Self { - self.context.alter_theme(theme_name); - self - } - - /// Modifica la composición para renderizar la página ([`Context::with_layout()`]). - #[builder_fn] - pub fn with_layout(mut self, layout_name: &'static str) -> Self { - self.context.alter_layout(layout_name); - self - } - - /// Define los recursos de la página usando [`AssetsOp`]. - #[builder_fn] - pub fn with_assets(mut self, op: AssetsOp) -> Self { - self.context.alter_assets(op); - self - } - - #[builder_fn] - pub fn with_param(mut self, key: &'static str, value: T) -> Self { - self.context.alter_param(key, value); - self - } - /// Establece el atributo `id` del elemento ``. #[builder_fn] pub fn with_body_id(mut self, id: impl AsRef) -> Self { @@ -205,25 +172,6 @@ impl Page { &self.properties } - /// Devuelve la solicitud HTTP asociada. - pub fn request(&self) -> Option<&HttpRequest> { - self.context.request() - } - - /// Devuelve el tema que se usará para renderizar la página. - pub fn theme(&self) -> ThemeRef { - self.context.theme() - } - - /// Devuelve la composición para renderizar la página. Por defecto es `"default"`. - pub fn layout(&self) -> &str { - self.context.layout() - } - - pub fn param(&self, key: &'static str) -> Option<&T> { - self.context.param(key) - } - /// Devuelve el identificador del elemento ``. pub fn body_id(&self) -> &AttrId { &self.body_id @@ -233,19 +181,19 @@ impl Page { pub fn body_classes(&self) -> &AttrClasses { &self.body_classes } - /* - /// Devuelve una referencia mutable al [`Context`] de la página. - /// - /// El [`Context`] actúa como intermediario para muchos métodos de `Page` (idioma, tema, - /// *layout*, recursos, solicitud HTTP, etc.). Resulta especialmente útil cuando un componente - /// o un tema necesita recibir el contexto como parámetro. - pub fn context(&mut self) -> &mut Context { - &mut self.context - } - */ + + /// Devuelve una referencia mutable al [`Context`] de la página. + /// + /// El [`Context`] actúa como intermediario para muchos métodos de `Page` (idioma, tema, + /// *layout*, recursos, solicitud HTTP, etc.). Resulta especialmente útil cuando un componente + /// o un tema necesita recibir el contexto como parámetro. + pub fn context(&mut self) -> &mut Context { + &mut self.context + } + // Page RENDER ********************************************************************************* - /// Renderiza los componentes de una región (`regiona_name`) de la página. + /// Renderiza los componentes de una región (`region_name`) de la página. pub fn render_region(&mut self, region_name: &'static str) -> Markup { self.regions .merge_all_components(self.context.theme(), region_name) @@ -302,3 +250,79 @@ impl LangId for Page { self.context.langid() } } + +impl Contextual for Page { + // Contextual BUILDER ************************************************************************** + + #[builder_fn] + fn with_request(mut self, request: Option) -> Self { + self.context.alter_request(request); + self + } + + #[builder_fn] + fn with_langid(mut self, language: &impl LangId) -> Self { + self.context.alter_langid(language); + self + } + + #[builder_fn] + fn with_theme(mut self, theme_name: &'static str) -> Self { + self.context.alter_theme(theme_name); + self + } + + #[builder_fn] + fn with_layout(mut self, layout_name: &'static str) -> Self { + self.context.alter_layout(layout_name); + self + } + + #[builder_fn] + fn with_param(mut self, key: &'static str, value: T) -> Self { + self.context.alter_param(key, value); + self + } + + #[builder_fn] + fn with_assets(mut self, op: AssetsOp) -> Self { + self.context.alter_assets(op); + self + } + + // Contextual GETTERS ************************************************************************** + + fn request(&self) -> Option<&HttpRequest> { + self.context.request() + } + + fn theme(&self) -> ThemeRef { + self.context.theme() + } + + fn layout(&self) -> &str { + self.context.layout() + } + + fn param(&self, key: &'static str) -> Option<&T> { + self.context.param(key) + } + + fn favicon(&self) -> Option<&Favicon> { + self.context.favicon() + } + + fn stylesheets(&self) -> &Assets { + self.context.stylesheets() + } + + fn javascripts(&self) -> &Assets { + self.context.javascripts() + } + + // Contextual HELPERS ************************************************************************** + + fn required_id(&mut self, id: Option) -> String { + self.context.required_id::(id) + } +} diff --git a/src/response/page/error.rs b/src/response/page/error.rs index ab56338..be48e3e 100644 --- a/src/response/page/error.rs +++ b/src/response/page/error.rs @@ -1,4 +1,5 @@ use crate::base::component::Html; +use crate::html::Contextual; use crate::locale::L10n; use crate::response::ResponseError; use crate::service::http::{header::ContentType, StatusCode};