1use web_sys::HtmlSelectElement;
4use yew::callback::Callback;
5use yew::html::{ChangeData, Component, ComponentLink, Html, NodeRef, ShouldRender};
6use yew::{html, Properties};
7
8#[derive(Debug)]
53pub struct Select<T: ToString + PartialEq + Clone + 'static> {
54 props: Props<T>,
55 select_ref: NodeRef,
56 link: ComponentLink<Self>,
57}
58
59#[derive(Debug)]
61pub enum Msg {
62 Selected(Option<usize>),
64}
65
66#[derive(PartialEq, Clone, Properties, Debug)]
68pub struct Props<T: Clone> {
69 #[prop_or_default]
71 pub selected: Option<T>,
72 #[prop_or_default]
74 pub disabled: bool,
75 #[prop_or_default]
77 pub options: Vec<T>,
78 #[prop_or_default]
80 pub class: String,
81 #[prop_or_default]
83 pub id: String,
84 #[prop_or(String::from("↪"))]
86 pub placeholder: String,
87 pub on_change: Callback<T>,
89}
90
91impl<T> Component for Select<T>
92where
93 T: ToString + PartialEq + Clone + 'static,
94{
95 type Message = Msg;
96 type Properties = Props<T>;
97
98 fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
99 Self {
100 props,
101 select_ref: NodeRef::default(),
102 link,
103 }
104 }
105
106 fn update(&mut self, msg: Self::Message) -> ShouldRender {
107 match msg {
108 Msg::Selected(value) => {
109 if let Some(idx) = value {
110 let item = self.props.options.get(idx - 1);
111 if let Some(value) = item {
112 self.props.on_change.emit(value.clone());
113 }
114 }
115 }
116 }
117 true
118 }
119
120 fn change(&mut self, props: Self::Properties) -> ShouldRender {
121 if self.props.selected != props.selected {
122 if let Some(select) = self.select_ref.cast::<HtmlSelectElement>() {
123 let val = props
124 .selected
125 .as_ref()
126 .map(|v| v.to_string())
127 .unwrap_or_default();
128 select.set_value(&val);
129 }
130 }
131 self.props = props;
132 true
133 }
134
135 fn view(&self) -> Html {
136 let selected = self.props.selected.as_ref();
137 let view_option = |value: &T| {
138 let flag = selected == Some(value);
139 html! {
140 <option value=value.to_string() selected=flag>{ value.to_string() }</option>
141 }
142 };
143
144 html! {
145 <select
146 ref=self.select_ref.clone()
147 id=self.props.id.clone()
148 class=self.props.class.clone()
149 disabled=self.props.disabled
150 onchange=self.on_change()
151 >
152 <option value="" disabled=true selected=selected.is_none()>
153 { self.props.placeholder.clone() }
154 </option>
155 { for self.props.options.iter().map(view_option) }
156 </select>
157 }
158 }
159}
160
161impl<T> Select<T>
162where
163 T: ToString + PartialEq + Clone + 'static,
164{
165 fn on_change(&self) -> Callback<ChangeData> {
166 self.link.callback(|event| match event {
167 ChangeData::Select(elem) => {
168 let value = elem.selected_index();
169 Msg::Selected(Some(value as usize))
170 }
171 _ => unreachable!(),
172 })
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179
180 #[test]
181 fn can_create_select() {
182 let on_change = Callback::<u8>::default();
183 html! {
184 <Select<u8> on_change=on_change />
185 };
186 }
187
188 #[test]
189 fn can_create_select_with_class() {
190 let on_change = Callback::<u8>::default();
191 html! {
192 <Select<u8> on_change=on_change class="form-control" />
193 };
194 }
195
196 #[test]
197 fn can_create_select_with_id() {
198 let on_change = Callback::<u8>::default();
199 html! {
200 <Select<u8> on_change=on_change id="test-select" />
201 };
202 }
203
204 #[test]
205 fn can_create_select_with_placeholder() {
206 let on_change = Callback::<u8>::default();
207 html! {
208 <Select<u8> on_change=on_change placeholder="--Please choose an option--" />
209 };
210 }
211}