1 use webapi::element::{Element, IElement};
2 use webapi::event_target::{EventTarget, IEventTarget};
3 use webapi::html_element::{HtmlElement, IHtmlElement};
4 use webapi::node::{INode, Node};
5 use webcore::try_from::TryInto;
6 use webcore::value::Reference;
7 use webapi::html_collection::HtmlCollection;
8 use webapi::html_elements::OptionElement;
9 
10 /// Indicates that an invalid value is setted to an `SelectElement`.
11 /// It means there is no `<option>` element that has the given value.
12 #[derive(Clone, Debug, PartialEq, Eq)]
13 pub struct UnknownValueError(String);
14 
15 impl std::fmt::Display for UnknownValueError {
fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result16     fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
17         write!(formatter, "There is no `<option>` element that has value='{}'", self.0)
18     }
19 }
20 
21 impl std::error::Error for UnknownValueError {
description(&self) -> &str22     fn description(&self) -> &str {
23         "There is no `<option>` element that has the given value"
24     }
25 }
26 
27 /// The HTML `<select>` element represents a control that provides a menu of options.
28 ///
29 /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select)
30 // https://html.spec.whatwg.org/#htmlselectelement
31 #[derive(Clone, Debug, PartialEq, Eq, ReferenceType)]
32 #[reference(instance_of = "HTMLSelectElement")]
33 #[reference(subclass_of(EventTarget, Node, Element, HtmlElement))]
34 pub struct SelectElement(Reference);
35 
36 impl IEventTarget for SelectElement {}
37 impl INode for SelectElement {}
38 impl IElement for SelectElement {}
39 impl IHtmlElement for SelectElement {}
40 
41 impl SelectElement {
42     /// Returns the value attribute of the first selected `<option>` element or
43     /// if it is missing, the text attribute. If there is no selection, return empty string.
44     /// This method is just a wrapper for getting `HTMLSelectElement.value` directly
45     // https://html.spec.whatwg.org/#the-select-element:dom-select-value
raw_value(&self) -> String46     pub fn raw_value(&self) -> String {
47         js!(
48             return @{self}.value
49         ).try_into().unwrap()
50     }
51 
52     /// Set the given value to `HTMLSelectElement.value` directly.
53     // https://html.spec.whatwg.org/#the-select-element:dom-select-value
set_raw_value(&self, value: &str)54     pub fn set_raw_value(&self, value: &str) {
55         js!{
56             @(no_return)
57             @{self}.value = @{value};
58         }
59     }
60 
61     /// Returns the `Some(index)` of the first selected item, if any, or `None` if there is no selected item.
62     // https://html.spec.whatwg.org/#the-select-element:dom-select-selectedindex
selected_index(&self) -> Option<u32>63     pub fn selected_index(&self) -> Option<u32> {
64         js! (
65             var self = @{self};
66             if (self.selectedIndex < 0) {
67                 return null;
68             }else{
69                 return self.selectedIndex;
70             }
71         ).try_into().unwrap()
72     }
73 
74     /// Change selected index to the given value.
75     // https://html.spec.whatwg.org/#the-select-element:dom-select-selectedindex
set_selected_index(&self, selected_index: Option<u32>)76     pub fn set_selected_index(&self, selected_index: Option<u32>) {
77         match selected_index {
78             Some(si) => js!{
79                 @(no_return)
80                 @{self}.selectedIndex = @{si};
81             },
82             None => js!{
83                 @(no_return)
84                 @{self}.selectedIndex = -1;
85             }
86         };
87     }
88 
89     /// Returns the `Some(value)` of the first selected item, if any, or `None` if there is no selected item.
90     // https://html.spec.whatwg.org/#the-select-element:dom-select-value
value(&self) -> Option<String>91     pub fn value(&self) -> Option<String> {
92         match self.selected_index(){
93             None => None,
94             Some(_) => Some(self.raw_value())
95         }
96     }
97 
98     /// Change the selected value to the given value. If you provide an invalid value,
99     /// the `<select>` element will have no item selected, and an `UnknownValueError` is returned.
100     // https://html.spec.whatwg.org/#the-select-element:dom-select-value
set_value(&self, value: Option<&str>) -> Result<(), UnknownValueError>101     pub fn set_value(&self, value: Option<&str>) -> Result<(), UnknownValueError> {
102         match value{
103             Some(value) => {
104                 self.set_raw_value(value);
105                 if self.selected_index().is_none(){
106                     Err(UnknownValueError(value.to_string()))
107                 }else{
108                     Ok(())
109                 }
110             },
111             None => {
112                 self.set_selected_index(None);
113                 Ok(())
114             }
115         }
116     }
117 
118     /// Indicates whether multiple items can be selected
119     // https://html.spec.whatwg.org/#the-select-element:dom-select-multiple
multiple(&self) -> bool120     pub fn multiple(&self) -> bool {
121         js!(
122             return @{self}.multiple;
123         ).try_into().unwrap()
124     }
125 
126     /// An `HtmlCollection` representing
127     /// the set of `<option>` elements that are selected.
128     // https://html.spec.whatwg.org/#the-select-element:dom-select-selectedoptions
selected_options(&self) -> HtmlCollection129     pub fn selected_options(&self) -> HtmlCollection {
130         js!(
131             return @{self}.selectedOptions;
132         ).try_into().unwrap()
133     }
134 
135     /// A convenience method to get values of all selected `<option>` elements
selected_values(&self) -> Vec<String>136     pub fn selected_values(&self) -> Vec<String> {
137         self.selected_options()
138             .iter().map(|e|{
139                 let e: OptionElement = e.try_into().unwrap();
140                 e.value()
141             }).collect::<Vec<String>>()
142     }
143 
144     /// A convenience method to get indices of all selected `<option>` elements
selected_indices(&self) -> Vec<i32>145     pub fn selected_indices(&self) -> Vec<i32> {
146         self.selected_options()
147             .iter().map(|e|{
148                 let e: OptionElement = e.try_into().unwrap();
149                 e.index()
150             }).collect::<Vec<i32>>()
151     }
152 }
153 
154 #[cfg(all(test, feature = "web_test"))]
155 mod tests{
156     use super::{SelectElement, UnknownValueError};
157     use webapi::node::Node;
158     use webcore::try_from::TryInto;
159     #[test]
test_select_one()160     fn test_select_one() {
161         let html = r#"<select><option value='first'>First option</option>
162             <option value='second'>Second option</option>
163             <option value='third' selected>Third option</option>
164             <option value=''>Empty</option></select>"#;
165         let se: SelectElement = Node::from_html(html).unwrap().try_into().unwrap();
166 
167         assert_eq!(se.multiple(), false);
168 
169         assert_eq!(se.selected_index(), Some(2));
170         assert_eq!(se.value(), Some("third".to_string()));
171 
172         se.set_selected_index(Some(1));
173         assert_eq!(se.selected_index(), Some(1));
174         assert_eq!(se.value(), Some("second".to_string()));
175 
176         se.set_selected_index(None);
177         assert_eq!(se.selected_index(), None);
178         assert_eq!(se.value(), None);
179 
180         let rs = se.set_value(Some("first"));
181         assert_eq!(rs, Ok(()));
182         assert_eq!(se.selected_index(), Some(0));
183         assert_eq!(se.value(), Some("first".to_string()));
184 
185         let rs = se.set_value(None);
186         assert_eq!(rs, Ok(()));
187         assert_eq!(se.selected_index(), None);
188         assert_eq!(se.value(), None);
189 
190         let rs = se.set_value(Some(""));
191         assert_eq!(rs, Ok(()));
192         assert_eq!(se.selected_index(), Some(3));
193         assert_eq!(se.value(), Some("".to_string()));
194 
195         let rs = se.set_value(Some("invalid_option"));
196         assert_eq!(rs, Err(UnknownValueError("invalid_option".to_string())));
197         assert_eq!(se.selected_index(), None);
198         assert_eq!(se.value(), None);
199     }
200 
201     #[test]
test_select_multiple_noselection()202     fn test_select_multiple_noselection(){
203          let html = r#"<select multiple><option value='first'>First option</option>
204             <option value='second'>Second option</option>
205             <option value='third'>Third option</option>
206             <option value='4th'>4th option</option></select>"#;
207         let se: SelectElement = Node::from_html(html).unwrap().try_into().unwrap();
208 
209         assert_eq!(se.multiple(), true);
210 
211         let empy_i32_vec: Vec<i32> = Vec::new();
212         let empy_string_vec: Vec<String> = Vec::new();
213         assert_eq!(se.selected_indices(), empy_i32_vec);
214         assert_eq!(se.selected_values(), empy_string_vec);
215     }
216 
217     #[test]
test_select_multiple()218     fn test_select_multiple(){
219          let html = r#"<select multiple><option value='first' selected>First option</option>
220             <option value='second'>Second option</option>
221             <option value='third' selected>Third option</option>
222             <option value='4th'>4th option</option>
223             <option value='' selected>Empty</option></select>"#;
224         let se: SelectElement = Node::from_html(html).unwrap().try_into().unwrap();
225 
226         assert_eq!(se.multiple(), true);
227 
228         assert_eq!(se.selected_indices(), vec![0,2,4]);
229         assert_eq!(se.selected_values(), vec!["first".to_string(), "third".to_string(), "".to_string()]);
230     }
231 }
232