feat: sweeping improvements to home_page.rs
Completely refactors the ownership of all data such that server resources are loaded into a signal asynchronously, then displayed in the form properly. All button handlers are also now separately defined functions, instead of being inline.
This commit is contained in:
1 parent
74e7111820
commit
85ec857e38
4 files changed
+259
-322
No files matched your search
+76
-126
@@ -1,136 +1,86 @@
|
||||
Forms should be built for this application with a series of JSON-type files.
|
||||
Each form will have a series of sections, and each section will have a series of
|
||||
questions. Each question has a type and a label - question styles may come later.
|
||||
It may be possible for us to call out specific question types without needing
|
||||
to parse the types separately, but that feels not-really-worth-it.
|
||||
To track errors and force form input validation, we're going to create a custom `ValidatedForm` component.
|
||||
|
||||
Example:
|
||||
This component will do two things:
|
||||
- Provide a submit button (that is disabled if the form is invalid, still thinking about that)
|
||||
- Define a validation method that will be called on submit that's updated on a per-input basis
|
||||
(this likely looks like a `Vec<RwSignal<ValidationState>>`)
|
||||
|
||||
```
|
||||
{
|
||||
"SectionDefinitions": [
|
||||
"SectionTitle": [
|
||||
"text": {
|
||||
"label": "First name question!",
|
||||
"placeholder": "John",
|
||||
},
|
||||
"text": {
|
||||
"label": "Last name question!",
|
||||
"placeholder": "Smith",
|
||||
}
|
||||
],
|
||||
"Section Two": [
|
||||
"text": {
|
||||
"label": "Section two testing question",
|
||||
"placeholder": "Some example here",
|
||||
},
|
||||
"select": {
|
||||
"label": "This is a dropdown element!",
|
||||
"placeholder": "option1",
|
||||
"possibleValues": [
|
||||
"option1": "something",
|
||||
"option2": "another something",
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
```
|
||||
In order for this to work, the form will `provide_context` to the `Vec`, and then it will check this
|
||||
vector on submit. It doesn't worry about the states inbetween, only during the submission logic.
|
||||
|
||||
...and so on. Here's a likely set of structures to use:
|
||||
The inputs start off with a default state, which will be defined by that input's validation method.
|
||||
This means that every input will run the validation method on mount and on each change. The first time the
|
||||
function is called should be during mount, which will set the default state. This does mean that any inputs
|
||||
that are marked as required will always be invalid on mount, and so we need to determine when these
|
||||
error messages should be shown and updated.
|
||||
|
||||
```
|
||||
struct FormDefinitionDoc {
|
||||
Vec<Section> section_defs;
|
||||
// and any extra values that should be applied
|
||||
// across the entire form, like settings.
|
||||
I think they should be shown in these situations:
|
||||
- Text inputs: on blur - we don't want to scream at users as they type.
|
||||
- Selectable inputs: on change and form submit - only show the message if the value is invalid, and not at mount.
|
||||
|
||||
To check values for the selectable inputs at submit time, we'll probably need to provide a refresh method
|
||||
that can be called on submit. This method must somehow be callable from the outside of the component and
|
||||
will show the error messages. We'd likely store all this in a struct of some kind, which could contain an
|
||||
`error` and a `show_message` signal.
|
||||
|
||||
We already know that we can send any `Signal` to another component, so we can just send the `show_message`
|
||||
signal to the parent component without any issues. We'll just use that signal to show the error messages
|
||||
in the component as well. Usually we don't want to edit too many signals outside their local context,
|
||||
but this is a special case – documentation is therefore incredibly important! Remember that we may also
|
||||
edit the `show_message` signal within its local context as well, so we need to make sure there are no cyclical
|
||||
dependencies. Alternatively, we could not edit the `show_message` signal at all locally and just use it
|
||||
to trigger side effects that show messages. Have to think about this more before implementation.
|
||||
|
||||
Here's a rough sketch of it all put together:
|
||||
```rust
|
||||
enum ValidationState {
|
||||
/// A valid input!
|
||||
Valid,
|
||||
/// An invalid input with an error message.
|
||||
Invalid(String)
|
||||
}
|
||||
|
||||
struct Section {
|
||||
Vec<QuestionComponent> questions;
|
||||
// each section may have a series of settings including how to submit,
|
||||
// when to submit, and where to submit to at the very least.
|
||||
// We'll store this in a Vec<RwSignal<InputState>>
|
||||
struct InputState {
|
||||
/// Determines if there is an error and provides an error message.
|
||||
error: Signal<ValidationState>,
|
||||
/// Determines if the form has requested to show error messages.
|
||||
show_message: RwSignal<bool>
|
||||
}
|
||||
|
||||
struct QuestionComponent {
|
||||
label: String,
|
||||
placeholder: String,
|
||||
possible_values: HashMap<str, str>,
|
||||
// there are definitely values that i'll find while creating this structure.
|
||||
// Define a new type for ease of use.
|
||||
type ValidationVec = Vec<RwSignal<InputState>>;
|
||||
|
||||
// Parent form component
|
||||
#[component]
|
||||
fn Form() -> impl IntoView {
|
||||
let validation_states: ValidationVec = Vec::new();
|
||||
|
||||
provide_context(validation_states);
|
||||
|
||||
/* All other form logic */
|
||||
}
|
||||
|
||||
fn validation_function(input: String) -> ValidationState {
|
||||
// Validation logic is specific to each input type.
|
||||
}
|
||||
|
||||
// Child component
|
||||
#[component]
|
||||
fn AnyInput() -> impl IntoView {
|
||||
let validation_states = use_context::<ValidationVec>().expect("Couldn't find form validation context");
|
||||
let current_input = RwSignal::new(String::new()); // Obviously, this would have a default value.
|
||||
let error_signal = RwSignal::new(validation_function(current_input.get()));
|
||||
let show_errors = RwSignal::new(false); // Don't show them on mount!
|
||||
|
||||
// See below for InputState definition
|
||||
let error_state = InputState {
|
||||
error: error_signal.clone().into(),
|
||||
show_message: show_errors.clone()
|
||||
};
|
||||
|
||||
validation_states.push(RwSignal::new())
|
||||
|
||||
/* All other input logic */
|
||||
}
|
||||
```
|
||||
|
||||
Types of questions that are required, starting with the providers.
|
||||
|
||||
Providers must be able to:
|
||||
|
||||
- Log into the website using a username and password (handled by AWS)
|
||||
- Create and manage multiple scholarships.
|
||||
- Input scholarship information for each scholarship.
|
||||
- View students that have applied.
|
||||
- View student application information (should just be labels??)
|
||||
- Buttons are a given. Duh.
|
||||
|
||||
Students must be able to:
|
||||
|
||||
- Log in using their Region 15 email (handled by AWS)
|
||||
- Create and manage their general application.
|
||||
- Create and manage applications specific to a single scholarship.
|
||||
- View their acceptance status (need more confirmation)
|
||||
|
||||
Administrators must be able to:
|
||||
|
||||
- Log in using their Region 15 email (handled by AWS)
|
||||
- Manage student scholarship applications.
|
||||
- Manage scholarship provider account information.
|
||||
- Create/manage scholarship information.
|
||||
- Download student applications if needed.
|
||||
- Statistics would be nice, but are likely a future thing.
|
||||
|
||||
Right off the bad, I think I can see a few types of buttons and text inputs,
|
||||
along with dropdowns, checkboxes, our custom `MultiEntry` element from last
|
||||
time. That means no new options, but it does mean that the `FormDefinition`
|
||||
needs to be able to keep track of all this information.
|
||||
|
||||
These are some example `QuestionComponent` objects:
|
||||
|
||||
Text fields:
|
||||
|
||||
```
|
||||
{
|
||||
question_type: "TEXT" | "EMAIL" | "NUMBER",
|
||||
label: "The question label",
|
||||
name: "input_name[jsdoc]",
|
||||
placeholder: "An example input"
|
||||
}
|
||||
```
|
||||
|
||||
Dropdowns/checkboxes/radios:
|
||||
|
||||
```
|
||||
{
|
||||
question_type: "DROPDOWN" | "CHECKBOX" | "RADIO",
|
||||
label: "The question label",
|
||||
name: "input_name[jsdoc]",
|
||||
placeholder: "The pre-selected option value",
|
||||
options: [
|
||||
"optionValue": "Option Text"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`MultiEntry`:
|
||||
|
||||
```
|
||||
{
|
||||
question_type: "MULTI",
|
||||
label: "The question label",
|
||||
name: "input_name[jsdoc]"
|
||||
}
|
||||
```
|
||||
|
||||
The real question here: how can we store this information in a database? And
|
||||
this means specifically within DynamoDB, because this is not an easy structure
|
||||
to store in there.
|
||||
That's going to be the hardest apart and I'll likely involve the AI in that
|
||||
planning.
|
||||
Reference in new issue
Block a user