use structform::{ derive_form_input, impl_text_input_with_stringops, ParseAndFormat, ParseError, StructForm, }; // This example shows creating forms over nested 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. // Often for larger forms, the strongly typed model isn't just a flat // series of fields. It often has nested structs, like the addresses // here. #[derive(Default, Debug, PartialEq, Eq)] struct UserDetails { username: String, primary_address: Address, secondary_address: Option
, } #[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 subform. The derive macro can // automatically identify optional subforms, but it needs the // `#[structform(subform)]` annotation to help it identify required // subforms. #[derive(Default, Clone, StructForm)] #[structform(model = "UserDetails")] struct UserDetailsForm { username: FormTextInput, #[structform(subform)] primary_address: AddressForm, secondary_address: Option, } #[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, // PrimaryAddress(AddressFormField), // ToggleSecondaryAddress, // SecondaryAddress(AddressFormField), // } // 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 set_input_delegates_to_subform() { let mut form = UserDetailsForm::default(); // The `UserDetailsFormField` has one field for the whole of a // required subform, that can contain any of the subform's fields. assert_eq!(form.primary_address.city.value, Err(ParseError::Required)); form.set_input( UserDetailsFormField::PrimaryAddress(AddressFormField::City), "Johannesburg".to_string(), ); assert_eq!( form.primary_address.city.value, Ok("Johannesburg".to_string()) ); } #[test] fn optional_subforms_can_be_toggled_on_and_off() { let mut form = UserDetailsForm::default(); // The `UserDetailsFormField` has two fields for an optional // subform: one that toggles it between `Some` and `None`, and // another that sends data. // By default, an optional subform will not be included. assert!(form.secondary_address.is_none()); // You can send `set_update` for the secondary form, and it won't // crash, but it also won't do anything. Actually doing this is // probably a logic error in your frontend. form.set_input( UserDetailsFormField::SecondaryAddress(AddressFormField::City), "Johannesburg".to_string(), ); assert!(form.secondary_address.is_none()); // Rather before using the secondary address, you need to toggle // it to `Some`. The toggle field is always your subform name with // `Toggle` in front, like `ToggleSecondaryAddress`. In this // case, the string passed to set_input is ignored. It works well // if you tie this message to the changed event on an HTML // checkbox, and only show the rest of the secondary address in // your HTML if `secondary_address` is Some. form.set_input(UserDetailsFormField::ToggleSecondaryAddress, "".to_string()); assert!(form.secondary_address.is_some()); assert_eq!( form.secondary_address.as_ref().unwrap().city.value, Err(ParseError::Required) ); // Now that we've toggled secondary_address to Some, we can fill // in its fields. form.set_input( UserDetailsFormField::SecondaryAddress(AddressFormField::City), "Johannesburg".to_string(), ); assert_eq!( form.secondary_address.as_ref().unwrap().city.value, Ok("Johannesburg".to_string()) ); } #[test] fn the_subforms_are_populated_when_initializing_from_an_existing_model() { // 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(), primary_address: Address { street_address: "123 StructForm Drive".to_string(), city: "Johannesburg".to_string(), country: "South Africa".to_string(), }, secondary_address: Some(Address { street_address: "321 StructForm Laan".to_string(), city: "Pretoria".to_string(), country: "South Africa".to_string(), }), }; let form = UserDetailsForm::new(&model); assert_eq!(form.username.input, "justin".to_string()); assert_eq!( form.primary_address.street_address.input, "123 StructForm Drive".to_string() ); assert!(form.secondary_address.is_some()); assert_eq!( form.secondary_address.unwrap().street_address.input, "321 StructForm Laan".to_string() ); } #[test] fn the_whole_form_can_be_completed() { let mut form = UserDetailsForm::default(); form.set_input(UserDetailsFormField::Username, "justin".to_string()); // Any required fields in subforms are also required to submit the // main form. assert_eq!(form.submit(), Err(ParseError::Required)); form.set_input( UserDetailsFormField::PrimaryAddress(AddressFormField::StreetAddress), "123 StructForm Drive".to_string(), ); form.set_input( UserDetailsFormField::PrimaryAddress(AddressFormField::City), "Johannesburg".to_string(), ); form.set_input( UserDetailsFormField::PrimaryAddress(AddressFormField::Country), "South Africa".to_string(), ); // Optional subforms are not required assert_eq!( form.submit(), Ok(UserDetails { username: "justin".to_string(), primary_address: Address { street_address: "123 StructForm Drive".to_string(), city: "Johannesburg".to_string(), country: "South Africa".to_string(), }, secondary_address: None, }) ); // However, if an optional subform is toggled to Some, it is required. form.set_input(UserDetailsFormField::ToggleSecondaryAddress, "".to_string()); assert_eq!(form.submit(), Err(ParseError::Required)); form.set_input( UserDetailsFormField::SecondaryAddress(AddressFormField::StreetAddress), "321 StructForm Laan".to_string(), ); form.set_input( UserDetailsFormField::SecondaryAddress(AddressFormField::City), "Pretoria".to_string(), ); form.set_input( UserDetailsFormField::SecondaryAddress(AddressFormField::Country), "South Africa".to_string(), ); assert_eq!( form.submit(), Ok(UserDetails { username: "justin".to_string(), primary_address: Address { street_address: "123 StructForm Drive".to_string(), city: "Johannesburg".to_string(), country: "South Africa".to_string(), }, secondary_address: Some(Address { street_address: "321 StructForm Laan".to_string(), city: "Pretoria".to_string(), country: "South Africa".to_string(), }), }) ); }