bootstrap-rs/src/input/mod.rs

159 lines
4.5 KiB
Rust

pub use self::{group::InputGroup, props::Props, textarea::TextArea};
use crate::{prelude::*, render};
use std::{
borrow::Cow,
fmt::{Display, Formatter, Result as FmtResult},
};
#[cfg(feature = "validate")]
use validator::ValidationErrors;
use yew::{html::IntoOptPropValue, prelude::*};
mod group;
mod props;
mod textarea;
#[derive(Clone, PartialEq)]
pub enum InputType {
Text,
Date,
DateTime,
Checkbox,
Color,
}
impl IntoOptPropValue<Cow<'static, str>> for &InputType {
fn into_opt_prop_value(self) -> Option<Cow<'static, str>> {
match self {
InputType::Text => Some(Cow::from("text")),
InputType::Date => Some(Cow::from("date")),
InputType::DateTime => Some(Cow::from("datetime-local")),
InputType::Checkbox => Some(Cow::from("checkbox")),
InputType::Color => Some(Cow::from("color")),
}
}
}
impl Display for InputType {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
match self {
Self::Text => write!(f, "text"),
Self::Date => write!(f, "date"),
Self::DateTime => write!(f, "datetime-local"),
Self::Checkbox => write!(f, "checkbox"),
Self::Color => write!(f, "color"),
}
}
}
pub struct Input {
link: ComponentLink<Self>,
state: String,
props: Props,
}
#[derive(Debug)]
pub struct InputChange(ChangeData);
impl Component for Input {
type Message = InputChange;
type Properties = Props;
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
let state = props.value.clone();
Self { props, state, link }
}
fn update(&mut self, msg: Self::Message) -> ShouldRender {
if let InputChange(ChangeData::Value(value)) = msg {
self.state = value.clone();
self.props.on_change.emit(value);
true
} else {
false
}
}
fn change(&mut self, props: Self::Properties) -> ShouldRender {
if self.props.value != props.value {
self.state = props.value.clone();
}
render_if_ne(&mut self.props, props)
}
#[cfg(not(feature = "validate"))]
fn view(&self) -> Html {
let input_type = self.props.input_type.as_ref().unwrap_or(&InputType::Text);
let mut prefix = if self.props.readonly {
vec!["form-control-plaintext"]
} else {
vec!["form-control"]
};
prefix.push(valid_as_class(&self.props.valid));
let html = html! {
<input
type=input_type
value=self.state.clone()
readonly=self.props.readonly
onchange=self.link.callback(InputChange)
/>
};
render::render_with_prefix(&self.props, prefix, html)
}
#[cfg(feature = "validate")]
fn view(&self) -> Html {
let input_type = self.props.input_type.as_ref().unwrap_or(&InputType::Text);
let mut prefix = if self.props.readonly {
vec!["form-control-plaintext"]
} else {
vec!["form-control"]
};
prefix.push(valid_as_class(&self.props.valid));
let html = html! {
<>
<input
type=input_type
value=&self.state
readonly=self.props.readonly
onchange=self.link.callback(|evt| InputChange(evt))
/>
{ crate::input::render_validation_feedback_from_props(&self.props) }
</>
};
render::render_with_prefix(&self.props, prefix, html)
}
}
#[cfg(feature = "validate")]
pub fn render_validation_feedback(
error_key: &Option<String>,
errors: &Option<ValidationErrors>,
) -> Html {
// TODO keep chipping away
if error_key.is_none {
html! {}
} else if let Some(ref errors) = errors {
let errors = errors.field_errors();
if let Some(errors) = errors.get(field.as_ref()) {
html! {
<div class="invalid-feedback">
{ for errors.iter().filter_map(|error| error.message.as_ref()) }
</div>
}
} else {
html! {}
}
} else {
html! {}
}
}
#[cfg(feature = "validate")]
fn render_validation_feedback_from_props(props: &Props) -> Html {
if let Some(error_code) = props.error_code.as_ref() {
render_validation_feedback(error_code, &props.errors)
} else {
html! {}
}
}