Improvements over HtmlSelect to allow using hashable objects (#47)

This commit is contained in:
Cecile Tonglet 2020-10-09 20:56:09 +02:00 committed by GitHub
parent 5b6b643290
commit 58933276c9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 87 additions and 29 deletions

View file

@ -3,6 +3,8 @@ use yewprint::HtmlSelect;
pub struct Example { pub struct Example {
props: ExampleProps, props: ExampleProps,
link: ComponentLink<Self>,
log_level: LogLevel,
} }
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
@ -14,14 +16,19 @@ pub struct ExampleProps {
} }
impl Component for Example { impl Component for Example {
type Message = (); type Message = LogLevel;
type Properties = ExampleProps; type Properties = ExampleProps;
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self { fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
Example { props } Example {
props,
link,
log_level: LogLevel::Info,
}
} }
fn update(&mut self, _msg: Self::Message) -> ShouldRender { fn update(&mut self, msg: Self::Message) -> ShouldRender {
self.log_level = msg;
true true
} }
@ -37,21 +44,34 @@ impl Component for Example {
fn view(&self) -> Html { fn view(&self) -> Html {
html! { html! {
<div style="width: 400px; text-align: center;"> <div style="width: 400px; text-align: center;">
<HtmlSelect <HtmlSelect<LogLevel>
options={vec![ options={vec![
("trace".to_string(), "TRACE".to_string()), (LogLevel::Trace, "TRACE".to_string()),
("debug".to_string(), "DEBUG".to_string()), (LogLevel::Debug, "DEBUG".to_string()),
("info".to_string(), "INFO".to_string()), (LogLevel::Info, "INFO".to_string()),
("warn".to_string(), "WARN".to_string()), (LogLevel::Warn, "WARN".to_string()),
("error".to_string(), "ERROR".to_string()), (LogLevel::Error, "ERROR".to_string()),
("off".to_string(), "OFF".to_string()), (LogLevel::Off, "OFF".to_string()),
]} ]}
minimal=self.props.minimal minimal=self.props.minimal
fill=self.props.fill fill=self.props.fill
disabled=self.props.disabled disabled=self.props.disabled
large=self.props.large large=self.props.large
value=Some(self.log_level)
onchange=self.link.callback(|x| x)
title=format!("Selected: {:?}", self.log_level)
/> />
</div> </div>
} }
} }
} }
#[derive(Debug, Clone, Copy, Hash, PartialEq)]
pub enum LogLevel {
Trace,
Debug,
Info,
Warn,
Error,
Off,
}

View file

@ -1,12 +1,15 @@
use crate::{ConditionalClass, Icon, IconName}; use crate::{ConditionalClass, Icon, IconName};
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use yew::prelude::*; use yew::prelude::*;
pub struct HtmlSelect { pub struct HtmlSelect<T: Clone + PartialEq + Hash + 'static> {
props: Props, props: Props<T>,
link: ComponentLink<Self>,
} }
#[derive(Clone, PartialEq, Properties)] #[derive(Clone, PartialEq, Properties)]
pub struct Props { pub struct Props<T: Clone + PartialEq + 'static> {
#[prop_or_default] #[prop_or_default]
pub fill: ConditionalClass, pub fill: ConditionalClass,
#[prop_or_default] #[prop_or_default]
@ -20,23 +23,32 @@ pub struct Props {
#[prop_or_default] #[prop_or_default]
pub title: Option<String>, pub title: Option<String>,
#[prop_or_default] #[prop_or_default]
pub onchange: Callback<ChangeData>, pub onchange: Callback<T>,
pub options: Vec<(T, String)>,
#[prop_or_default] #[prop_or_default]
pub options: Vec<(String, String)>, pub value: Option<T>,
#[prop_or_default]
pub value: String,
} }
impl Component for HtmlSelect { impl<T: Clone + PartialEq + Hash + 'static> Component for HtmlSelect<T> {
type Message = (); type Message = ChangeData;
type Properties = Props; type Properties = Props<T>;
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self { fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
HtmlSelect { props } Self { props, link }
} }
fn update(&mut self, _msg: Self::Message) -> ShouldRender { fn update(&mut self, msg: Self::Message) -> ShouldRender {
true let i = if let ChangeData::Select(select) = msg {
select.selected_index()
} else {
unreachable!("unexpected ChangeData variant: {:?}", msg);
};
if i >= 0 {
let i = i as usize;
let variant = self.props.options[i].0.clone();
self.props.onchange.emit(variant);
}
false
} }
fn change(&mut self, props: Self::Properties) -> ShouldRender { fn change(&mut self, props: Self::Properties) -> ShouldRender {
@ -53,7 +65,25 @@ impl Component for HtmlSelect {
.props .props
.options .options
.iter() .iter()
.map(|(value, label)| html!(<option value=value>{label}</option>)) .map(|(value, label)| {
let selected = self
.props
.value
.as_ref()
.map(|x| value == x)
.unwrap_or_default();
let value = {
let mut hasher = DefaultHasher::new();
value.hash(&mut hasher);
hasher.finish()
};
html! {
<option selected=selected value=value>
{label}
</option>
}
})
.collect::<Html>(); .collect::<Html>();
html! { html! {
@ -68,8 +98,16 @@ impl Component for HtmlSelect {
> >
<select <select
disabled=*self.props.disabled disabled=*self.props.disabled
onchange={self.props.onchange.clone()} onchange={self.link.callback(|x| x)}
value={self.props.value.clone()} value?={
self.props.value
.as_ref()
.map(|value| {
let mut hasher = DefaultHasher::new();
value.hash(&mut hasher);
hasher.finish()
})
}
title?={self.props.title.clone()} title?={self.props.title.clone()}
> >
{option_children} {option_children}