Replace the input! macro with an Input builder (#646)
* Replace the input! macro with an Input builder * Use a BTreeMap instead of an HashMap Followinf @fdb-hiroshima's advice * Rename Input::to_html to Input::html To make clippy happy * Wrap error messages in red paragraphs
This commit is contained in:
+124
-95
@@ -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 `<input>` 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<String>,
|
||||
/// `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<String>,
|
||||
/// Additional HTML properties.
|
||||
props: BTreeMap<String, String>,
|
||||
/// The error message to show next to this field.
|
||||
error: Option<String>,
|
||||
}
|
||||
|
||||
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<String> {
|
||||
if !self.optional {
|
||||
self = self.set_prop("required", true);
|
||||
}
|
||||
|
||||
Html(format!(
|
||||
r#"
|
||||
@@ -247,98 +344,30 @@ macro_rules! input {
|
||||
{error}
|
||||
<input type="{kind}" id="{name}" name="{name}" value="{val}" {props} dir="auto"/>
|
||||
"#,
|
||||
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!("<small>{}</small>", i18n!(cat, "Optional"))
|
||||
} else {
|
||||
String::new()
|
||||
},
|
||||
details = if $details.len() > 0 {
|
||||
format!("<small>{}</small>", i18n!(cat, $details))
|
||||
} else {
|
||||
String::new()
|
||||
},
|
||||
error = if let Some(ValidationErrorsKind::Field(errs)) =
|
||||
$err.errors().get(stringify!($name))
|
||||
{
|
||||
format!(
|
||||
r#"<p class="error" dir="auto">{}</p>"#,
|
||||
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!("<small>{}</small>", d))
|
||||
.unwrap_or_default(),
|
||||
error = self
|
||||
.error
|
||||
.map(|e| format!(r#"<p class="error" dir="auto">{}</p>"#, 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#"
|
||||
<label for="{name}" dir="auto">{label}</label>
|
||||
<input type="{kind}" id="{name}" name="{name}" {props} dir="auto"/>
|
||||
"#,
|
||||
name = stringify!($name),
|
||||
label = i18n!(cat, $label),
|
||||
kind = stringify!($kind),
|
||||
props = $props
|
||||
))
|
||||
}};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user