1 use webapi::blob::IBlob; 2 use webapi::dom_exception::InvalidStateError; 3 use webapi::element::IElement; 4 use webapi::error::TypeError; 5 use webapi::file::File; 6 use webcore::try_from::TryFrom; 7 use webcore::try_from::TryInto; 8 use webcore::value::ConversionError; 9 use webcore::value::Reference; 10 use webcore::value::Value; 11 12 /// The `FormData` interface provides a way to easily construct a set of key/value pairs 13 /// representing form fields and their values, which can then be easily sent using the 14 /// `XMLHttpRequest.send()` method. It uses the same format a form would use if the encoding type 15 /// were set to `"multipart/form-data"`. 16 /// 17 /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/FormData) 18 // https://xhr.spec.whatwg.org/#formdata 19 #[derive(Clone, Debug, PartialEq, Eq, ReferenceType)] 20 #[reference(instance_of = "FormData")] 21 pub struct FormData( Reference ); 22 23 /// Represents a type of data stores in FormData. 24 #[derive(Clone, Debug, PartialEq)] 25 pub enum FormDataEntry { 26 /// File data 27 File( File ), 28 /// Text data 29 String( String ) 30 } 31 32 error_enum_boilerplate! { 33 FormDataFromElementError, 34 InvalidStateError, TypeError 35 } 36 37 impl TryFrom< Value > for FormDataEntry { 38 type Error = ConversionError; 39 try_from(value: Value) -> Result< Self, Self::Error >40 fn try_from(value: Value) -> Result< Self, Self::Error > { 41 let entry = match value { 42 Value::String(s) => FormDataEntry::String(s), 43 Value::Reference(r) => FormDataEntry::File(File(r)), 44 _ => return Err(ConversionError::type_mismatch(&value, "string or reference".into())), 45 }; 46 47 Ok(entry) 48 } 49 } 50 51 impl TryFrom< Value > for Option< FormDataEntry > { 52 type Error = ConversionError; 53 try_from(value: Value) -> Result< Self, Self::Error >54 fn try_from(value: Value) -> Result< Self, Self::Error > { 55 let entry = match value { 56 Value::Null|Value::Undefined => None, 57 Value::String(s) => Some(FormDataEntry::String(s)), 58 Value::Reference(r) => Some(FormDataEntry::File(File(r))), 59 _ => return Err(ConversionError::type_mismatch(&value, "null, string or reference".into())), 60 }; 61 62 Ok(entry) 63 } 64 } 65 66 impl FormData { 67 /// Creates a new `FormData`. 68 /// 69 /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/FormData/FormData) new() -> Self70 pub fn new() -> Self { 71 js! ( 72 return new FormData(); 73 ).try_into().unwrap() 74 } 75 76 /// Creates a new `FormData` from a form element. 77 /// 78 /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/FormData/FormData) from_element<T>( form: &T ) -> Result< Self, FormDataFromElementError > where T: IElement79 pub fn from_element<T>( form: &T ) -> Result< Self, FormDataFromElementError > where T: IElement { 80 js_try! ( 81 let form = @{form.as_ref()}; 82 83 if ( ! (form instanceof HTMLFormElement) ) { 84 throw new TypeError("Argument 1 of FormData::from_element does not implement interface HTMLFormElement."); 85 } 86 87 return new FormData(form); 88 ).unwrap() 89 } 90 91 /// Appends a new value onto an existing key, or adds the key if it does not already exist. 92 /// 93 /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/FormData/append) 94 // https://xhr.spec.whatwg.org/#dom-formdata-append append_string( &self, name: &str, value: &str )95 pub fn append_string( &self, name: &str, value: &str ) { 96 js! { @(no_return) 97 @{self}.append(@{name}, @{value}); 98 } 99 } 100 101 /// Appends a new blob onto an existing key, or adds the key if it does not already exist. 102 /// 103 /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/FormData/append) 104 // https://xhr.spec.whatwg.org/#dom-formdata-append-blob append_blob<T>( &self, name: &str, value: &T, filename: Option< &str > ) where T: IBlob105 pub fn append_blob<T>( &self, name: &str, value: &T, filename: Option< &str > ) where T: IBlob { 106 js! { @(no_return) 107 @{self}.append(@{name}, @{value.as_ref()}, @{filename}); 108 } 109 } 110 111 /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/FormData/delete) 112 // https://xhr.spec.whatwg.org/#dom-formdata-delete delete( &self, name: &str )113 pub fn delete( &self, name: &str ) { 114 js! { @(no_return) 115 @{self}.delete(@{name}); 116 } 117 } 118 119 /// Deletes a key and its value(s). 120 /// 121 /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/FormData/get) 122 // https://xhr.spec.whatwg.org/#dom-formdata-get get( &self, name: &str ) -> Option< FormDataEntry >123 pub fn get( &self, name: &str ) -> Option< FormDataEntry > { 124 js! ( 125 return @{self}.get(@{name}); 126 ).try_into().unwrap() 127 } 128 129 /// Returns all the values associated with a given key. 130 /// 131 /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/FormData/getAll) 132 // https://xhr.spec.whatwg.org/#dom-formdata-getall get_all( &self, name: &str ) -> Vec< FormDataEntry >133 pub fn get_all( &self, name: &str ) -> Vec< FormDataEntry > { 134 js! ( 135 return @{self}.getAll(@{name}); 136 ).try_into().unwrap() 137 } 138 139 /// Returns a boolean stating whether a `FormData` object contains a certain key. 140 /// 141 /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/FormData/has) 142 // https://xhr.spec.whatwg.org/#dom-formdata-has has( &self, name: &str ) -> bool143 pub fn has( &self, name: &str ) -> bool { 144 js! ( 145 return @{self}.has(@{name}); 146 ).try_into().unwrap() 147 } 148 149 /// Sets a new value for an existing key, or adds the key/value if it does not already exist. 150 /// 151 /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/FormData/set) 152 // https://xhr.spec.whatwg.org/#dom-formdata-set set_string( &self, name: &str, value: &str )153 pub fn set_string( &self, name: &str, value: &str ) { 154 js! { @(no_return) 155 @{self}.set(@{name}, @{value}); 156 } 157 } 158 159 /// Sets a new blob for an existing key, or adds the key/value if it does not already exist. 160 /// 161 /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/FormData/set) 162 // https://xhr.spec.whatwg.org/#dom-formdata-set-blob set_blob<T>( &self, name: &str, value: &T, filename: Option< &str > ) where T: IBlob163 pub fn set_blob<T>( &self, name: &str, value: &T, filename: Option< &str > ) where T: IBlob { 164 js! { @(no_return) 165 @{self}.set(@{name}, @{value.as_ref()}, @{filename}); 166 } 167 } 168 } 169 170 #[cfg(all(test, feature = "web_test"))] 171 mod tests { 172 use stdweb::webapi::blob::Blob; 173 use stdweb::webapi::element::Element; 174 use stdweb::webcore::try_from::TryInto; 175 use super::*; 176 form() -> Element177 fn form() -> Element { 178 js!( 179 let form = document.createElement("form"); 180 181 let inputs = []; 182 183 for (let i = 1; i < 4; i++) { 184 inputs[i] = document.createElement("input"); 185 inputs[i].name = "key" + i; 186 inputs[i].value = "value" + i; 187 form.appendChild(inputs[i]); 188 } 189 190 inputs[3].name = "key2"; 191 192 return form; 193 ).try_into().unwrap() 194 } 195 data() -> FormData196 fn data() -> FormData { 197 let form = form(); 198 199 FormData::from_element(&form) 200 .unwrap() 201 } 202 203 #[test] test_new()204 fn test_new() { 205 FormData::new(); 206 } 207 208 #[test] test_from_invalid_element()209 fn test_from_invalid_element() { 210 use webapi::document::document; 211 212 let div = document().create_element("div") 213 .unwrap(); 214 215 assert!(FormData::from_element(&div).is_err()); 216 } 217 218 #[test] test_append_string()219 fn test_append_string() { 220 let data = data(); 221 assert!(data.get("key0").is_none()); 222 223 data.append_string("key0", "value0"); 224 assert_eq!(data.get("key0"), Some(FormDataEntry::String(String::from("value0")))); 225 } 226 227 #[test] test_append_blob()228 fn test_append_blob() { 229 let data = data(); 230 assert!(data.get("key0").is_none()); 231 232 data.append_blob("blob", &Blob::new(), Some("file.jpg")); 233 assert!(data.get("blob").is_some()); 234 } 235 236 #[test] test_delete()237 fn test_delete() { 238 let data = data(); 239 assert!(data.get("key1").is_some()); 240 241 data.delete("key1"); 242 assert!(data.get("key1").is_none()); 243 } 244 245 #[test] test_get()246 fn test_get() { 247 let data = data(); 248 249 assert_eq!(data.get("key1"), Some(FormDataEntry::String(String::from("value1")))); 250 } 251 252 #[test] test_get_all()253 fn test_get_all() { 254 let data = data(); 255 256 assert_eq!(data.get_all("key2"), vec![ 257 FormDataEntry::String(String::from("value2")), 258 FormDataEntry::String(String::from("value3")) 259 ]); 260 assert_eq!(data.get_all("unknow"), Vec::<FormDataEntry>::new()); 261 } 262 263 #[test] test_has()264 fn test_has() { 265 let data = data(); 266 267 assert_eq!(data.has("key1"), true); 268 } 269 270 #[test] test_set_string()271 fn test_set_string() { 272 let data = data(); 273 assert_eq!(data.get("key1"), Some(FormDataEntry::String(String::from("value1")))); 274 275 data.set_string("key1", "value"); 276 assert_eq!(data.get("key1"), Some(FormDataEntry::String(String::from("value")))); 277 } 278 279 #[test] test_set_blob()280 fn test_set_blob() { 281 let data = data(); 282 assert_eq!(data.get("key1"), Some(FormDataEntry::String(String::from("value1")))); 283 284 data.set_blob("key1", &Blob::new(), None); 285 assert!(data.get("key1").is_some()); 286 } 287 } 288