diff --git a/src/template_utils.rs b/src/template_utils.rs index 697084c..1e0b9fe 100644 --- a/src/template_utils.rs +++ b/src/template_utils.rs @@ -5,7 +5,7 @@ use rocket::http::{Method, Status}; use rocket::request::Request; use rocket::response::{self, content::Html as HtmlCt, Responder, Response}; use rocket_i18n::Catalog; -use std::collections::hash_map::DefaultHasher; +use std::collections::{btree_map::BTreeMap, hash_map::DefaultHasher}; use std::hash::Hasher; use templates::Html; @@ -231,11 +231,108 @@ macro_rules! icon { }; } -macro_rules! input { - ($catalog:expr, $name:tt ($kind:tt), $label:expr, $optional:expr, $details:expr, $form:expr, $err:expr, $props:expr) => {{ - use std::borrow::Cow; - use validator::ValidationErrorsKind; - let cat = $catalog; +/// A builder type to generate `` tags in a type-safe way. +/// +/// # Example +/// +/// This example uses all options, but you don't have to specify everything. +/// +/// ```rust +/// # let current_email = "foo@bar.baz"; +/// # let catalog = gettext::Catalog::parse("").unwrap(); +/// Input::new("mail", "Your email address") +/// .input_type("email") +/// .default(current_email) +/// .optional() +/// .details("We won't use it for advertising.") +/// .set_prop("class", "email-input") +/// .to_html(catalog); +/// ``` +pub struct Input { + /// The name of the input (`name` and `id` in HTML). + name: String, + /// The description of this field. + label: String, + /// The `type` of the input (`text`, `email`, `password`, etc). + input_type: String, + /// The default value for this input field. + default: Option, + /// `true` if this field is not required (will add a little badge next to the label). + optional: bool, + /// A small message to display next to the label. + details: Option, + /// Additional HTML properties. + props: BTreeMap, + /// The error message to show next to this field. + error: Option, +} + +impl Input { + /// Creates a new input with a given name. + pub fn new(name: impl ToString, label: impl ToString) -> Input { + Input { + name: name.to_string(), + label: label.to_string(), + input_type: "text".into(), + default: None, + optional: false, + details: None, + props: BTreeMap::new(), + error: None, + } + } + + /// Set the `type` of this input. + pub fn input_type(mut self, t: impl ToString) -> Input { + self.input_type = t.to_string(); + self + } + + /// Marks this field as optional. + pub fn optional(mut self) -> Input { + self.optional = true; + self + } + + /// Fills the input with a default value (useful for edition form, to show the current values). + pub fn default(mut self, val: impl ToString) -> Input { + self.default = Some(val.to_string()); + self + } + + /// Adds additional information next to the label. + pub fn details(mut self, text: impl ToString) -> Input { + self.details = Some(text.to_string()); + self + } + + /// Defines an additional HTML property. + /// + /// This method can be called multiple times for the same input. + pub fn set_prop(mut self, key: impl ToString, val: impl ToString) -> Input { + self.props.insert(key.to_string(), val.to_string()); + self + } + + /// Shows an error message + pub fn error(mut self, errs: &validator::ValidationErrors) -> Input { + if let Some(field_errs) = errs.clone().field_errors().get(self.name.as_str()) { + self.error = Some( + field_errs[0] + .message + .clone() + .unwrap_or_default() + .to_string(), + ); + } + self + } + + /// Returns the HTML markup for this field. + pub fn html(mut self, cat: &Catalog) -> Html { + if !self.optional { + self = self.set_prop("required", true); + } Html(format!( r#" @@ -247,98 +344,30 @@ macro_rules! input { {error} "#, - name = stringify!($name), - label = i18n!(cat, $label), - kind = stringify!($kind), - optional = if $optional { + name = self.name, + label = self.label, + kind = self.input_type, + optional = if self.optional { format!("{}", i18n!(cat, "Optional")) } else { String::new() }, - details = if $details.len() > 0 { - format!("{}", i18n!(cat, $details)) - } else { - String::new() - }, - error = if let Some(ValidationErrorsKind::Field(errs)) = - $err.errors().get(stringify!($name)) - { - format!( - r#"

{}

"#, - errs[0] - .message - .clone() - .unwrap_or(Cow::from("Unknown error")) - ) - } else { - String::new() - }, - val = escape(&$form.$name), - props = $props + details = self + .details + .map(|d| format!("{}", d)) + .unwrap_or_default(), + error = self + .error + .map(|e| format!(r#"

{}

"#, e)) + .unwrap_or_default(), + val = escape(&self.default.unwrap_or_default()), + props = self + .props + .into_iter() + .fold(String::new(), |mut res, (key, val)| { + res.push_str(&format!("{}=\"{}\" ", key, val)); + res + }) )) - }}; - ($catalog:expr, $name:tt (optional $kind:tt), $label:expr, $details:expr, $form:expr, $err:expr, $props:expr) => { - input!( - $catalog, - $name($kind), - $label, - true, - $details, - $form, - $err, - $props - ) - }; - ($catalog:expr, $name:tt (optional $kind:tt), $label:expr, $form:expr, $err:expr, $props:expr) => { - input!( - $catalog, - $name($kind), - $label, - true, - "", - $form, - $err, - $props - ) - }; - ($catalog:expr, $name:tt ($kind:tt), $label:expr, $details:expr, $form:expr, $err:expr, $props:expr) => { - input!( - $catalog, - $name($kind), - $label, - false, - $details, - $form, - $err, - $props - ) - }; - ($catalog:expr, $name:tt ($kind:tt), $label:expr, $form:expr, $err:expr, $props:expr) => { - input!( - $catalog, - $name($kind), - $label, - false, - "", - $form, - $err, - $props - ) - }; - ($catalog:expr, $name:tt ($kind:tt), $label:expr, $form:expr, $err:expr) => { - input!($catalog, $name($kind), $label, false, "", $form, $err, "") - }; - ($catalog:expr, $name:tt ($kind:tt), $label:expr, $props:expr) => {{ - let cat = $catalog; - Html(format!( - r#" - - - "#, - name = stringify!($name), - label = i18n!(cat, $label), - kind = stringify!($kind), - props = $props - )) - }}; + } } diff --git a/templates/blogs/edit.rs.html b/templates/blogs/edit.rs.html index 0b1febb..fa14a10 100644 --- a/templates/blogs/edit.rs.html +++ b/templates/blogs/edit.rs.html @@ -19,7 +19,11 @@ - @input!(ctx.1, title (text), "Title", form, errors.clone(), "minlenght=\"1\"") + @(Input::new("title", i18n!(ctx.1, "Title")) + .default(&form.title) + .error(&errors) + .set_prop("minlenght", 1) + .html(ctx.1)) diff --git a/templates/blogs/new.rs.html b/templates/blogs/new.rs.html index b9a186b..8480814 100644 --- a/templates/blogs/new.rs.html +++ b/templates/blogs/new.rs.html @@ -9,7 +9,11 @@ @:base(ctx, i18n!(ctx.1, "New Blog"), {}, {}, {

@i18n!(ctx.1, "Create a blog")

- @input!(ctx.1, title (text), "Title", form, errors, "required minlength=\"1\"") + @(Input::new("title", i18n!(ctx.1, "Title")) + .default(&form.title) + .error(&errors) + .set_prop("minlength", 1) + .html(ctx.1))
}) diff --git a/templates/instance/admin.rs.html b/templates/instance/admin.rs.html index 50bde42..db4441c 100644 --- a/templates/instance/admin.rs.html +++ b/templates/instance/admin.rs.html @@ -17,7 +17,11 @@ ])
- @input!(ctx.1, name (text), "Name", form, errors.clone(), "props") + @(Input::new("name", i18n!(ctx.1, "Name")) + .default(&form.name) + .error(&errors) + .set_prop("minlength", 1) + .html(ctx.1))