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