use structform::{ derive_form_input, impl_text_input_with_stringops, ParseAndFormat, ParseError, StructForm, }; // This example shows creating forms over nested lists of data structures. // This example builds on the [login example](./login_example.rs). // This example is written assuming that you're already familiar with // the login example, so if not please refer to that first. It also // helps to be familiar with the // [subforms example](./subforms_example.rs). // Often for larger forms, the strongly typed model isn't just a flat // series of fields. It often has nested structs. Sometimes, you might // need to enter an arbitrary number of those structs. In this case, a // user may have many addresses. #[derive(Default, Debug, PartialEq, Eq)] struct UserDetails { username: String, addresses: Vec
, } #[derive(Default, Clone, Debug, PartialEq, Eq)] struct Address { street_address: String, city: String, country: String, } // When we create our StructForm for capturing these user details, we // need a form for both UserDetails and Address. The Address form is // included in the UserDetails form as a Vec of subforms. The // derive macro can automatically identify Vecs as being Vecs of // subforms, so no additional annotations are needed. #[derive(Default, Clone, StructForm)] #[structform(model = "UserDetails")] struct UserDetailsForm { username: FormTextInput, addresses: Vec, } #[derive(Default, Clone, StructForm)] #[structform(model = "Address")] struct AddressForm { street_address: FormTextInput, city: FormTextInput, country: FormTextInput, } // These two derivations of StructForms generates the following field definitions: // ``` // pub enum UserDetailsFormField { // Username, // AddAddresses, // Addresses(usize, AddressFormField), // RemoveAddresses(usize), // } // pub enum AddressFormField { // StreetAddress, // City, // Country, // } // ``` // These inputs are the same as the login example. See that example // for more details. derive_form_input! {FormTextInput} impl_text_input_with_stringops!(FormTextInput, String); #[test] fn the_list_of_subforms_starts_empty() { let form = UserDetailsForm::default(); assert_eq!(form.addresses.len(), 0); } #[test] fn subforms_can_be_modified_by_their_index() { let mut form = UserDetailsForm::default(); // You can push a new entry onto a subform list with the add // field. The add field is always your subform field name with // `Add` in front, like `AddAddresses`. In this case, the string // passed to set_input is ignored. It works well if you tie this // message to an "Add" HTML button. form.set_input(UserDetailsFormField::AddAddresses, "".to_string()); assert_eq!(form.addresses.len(), 1); // When you add a new form, it starts empty, and you can fill it // in by calling `set_input` with the appropriate index. Indexing // starts from 0, like the Vec holding the subforms. A useful // pattern to follow when building HTML templates here is to // iterate over the subform in your HTML, and make use of // [enumerate](https://doc.rust-lang.org/stable/std/iter/trait.Iterator.html#method.enumerate) // for the indices. assert_eq!(form.addresses[0].city.input, "".to_string()); form.set_input( UserDetailsFormField::Addresses(0, AddressFormField::City), "Johannesburg".to_string(), ); assert_eq!(form.addresses[0].city.input, "Johannesburg".to_string()); } #[test] fn settings_an_out_of_range_input_does_nothing() { let mut form = UserDetailsForm::default(); // It's probably a logic error to try to set a field that doesn't // exist, but if you do at least it won't error out. form.set_input( UserDetailsFormField::Addresses(1, AddressFormField::City), "Hello".to_string(), ); assert_eq!(form.addresses.len(), 0); } #[test] fn any_subform_can_be_removed_from_the_list() { // If you're editing an existing model, you can construct your // StructForm from that model. Subforms will also be prepopulated // appropriately. let model = UserDetails { username: "justin".to_string(), addresses: vec![ Address { street_address: "123 StructForm Drive".to_string(), city: "Johannesburg".to_string(), country: "South Africa".to_string(), }, Address { street_address: "321 StructForm Laan".to_string(), city: "Pretoria".to_string(), country: "South Africa".to_string(), }, Address { street_address: "222 StructForm Crescent".to_string(), city: "Midrand".to_string(), country: "South Africa".to_string(), }, ], }; let mut form = UserDetailsForm::new(&model); assert_eq!(form.addresses.len(), 3); assert_eq!(form.addresses[0].city.input, "Johannesburg".to_string()); assert_eq!(form.addresses[1].city.input, "Pretoria".to_string()); assert_eq!(form.addresses[2].city.input, "Midrand".to_string()); // If you want to remove one of the forms, you can send the // appropriate remove field to `set_input`. The remove field is // always your subform field name with `Remove` in front, like // `RemoveAddresses`. It works well to tie this to a remove HTML // button next to each subform in your HTML. Like with `Add`, the // string passed in here does nothing. form.set_input(UserDetailsFormField::RemoveAddresses(1), "".to_string()); assert_eq!(form.addresses.len(), 2); assert_eq!(form.addresses[0].city.input, "Johannesburg".to_string()); assert_eq!(form.addresses[1].city.input, "Midrand".to_string()); } #[test] fn the_whole_form_can_be_completed() { let mut form = UserDetailsForm::default(); form.set_input(UserDetailsFormField::Username, "justin".to_string()); // It's valid to have an empty list of subforms. assert_eq!( form.submit(), Ok(UserDetails { username: "justin".to_string(), addresses: vec![] }) ); // However, if you've added a subform to the list, it is required. form.set_input(UserDetailsFormField::AddAddresses, "".to_string()); assert_eq!(form.submit(), Err(ParseError::Required)); form.set_input( UserDetailsFormField::Addresses(0, AddressFormField::StreetAddress), "123 StructForm Drive".to_string(), ); form.set_input( UserDetailsFormField::Addresses(0, AddressFormField::City), "Johannesburg".to_string(), ); form.set_input( UserDetailsFormField::Addresses(0, AddressFormField::Country), "South Africa".to_string(), ); assert_eq!( form.submit(), Ok(UserDetails { username: "justin".to_string(), addresses: vec![Address { street_address: "123 StructForm Drive".to_string(), city: "Johannesburg".to_string(), country: "South Africa".to_string(), }] }) ); form.set_input(UserDetailsFormField::AddAddresses, "".to_string()); assert_eq!(form.submit(), Err(ParseError::Required)); form.set_input( UserDetailsFormField::Addresses(1, AddressFormField::StreetAddress), "321 StructForm Laan".to_string(), ); form.set_input( UserDetailsFormField::Addresses(1, AddressFormField::City), "Pretoria".to_string(), ); form.set_input( UserDetailsFormField::Addresses(1, AddressFormField::Country), "South Africa".to_string(), ); assert_eq!( form.submit(), Ok(UserDetails { username: "justin".to_string(), addresses: vec![ Address { street_address: "123 StructForm Drive".to_string(), city: "Johannesburg".to_string(), country: "South Africa".to_string(), }, Address { street_address: "321 StructForm Laan".to_string(), city: "Pretoria".to_string(), country: "South Africa".to_string(), } ], }) ); }