Refactor all components to use expandable data #10
No files matched your search
+50
-21
@@ -1,4 +1,6 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
|
use crate::components::ValueType;
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Checkbox(
|
pub fn Checkbox(
|
||||||
@@ -42,7 +44,9 @@ pub fn Checkbox(
|
|||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn CheckboxList(
|
pub fn CheckboxList(
|
||||||
#[prop(default = RwSignal::new(vec![]))] selected: RwSignal<Vec<String>>,
|
#[prop(optional)] selected: RwSignal<Vec<String>>,
|
||||||
|
#[prop(into)] data_member: String,
|
||||||
|
#[prop()] data_map: RwSignal<HashMap<String, ValueType>>,
|
||||||
#[prop()] items: Vec<String>,
|
#[prop()] items: Vec<String>,
|
||||||
#[prop(default = RwSignal::new(false))] disabled: RwSignal<bool>,
|
#[prop(default = RwSignal::new(false))] disabled: RwSignal<bool>,
|
||||||
#[prop(optional, into)] label: String,
|
#[prop(optional, into)] label: String,
|
||||||
@@ -53,32 +57,57 @@ pub fn CheckboxList(
|
|||||||
{items
|
{items
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|item| {
|
.map(|item| {
|
||||||
let is_checked = {
|
let checked_signal = Signal::derive({
|
||||||
let item_name = item.clone();
|
let item_name = item.clone();
|
||||||
move || { selected.get().contains(&item_name) }
|
let data_member = data_member.clone();
|
||||||
|
move || {
|
||||||
|
data_map.get() // HashMap
|
||||||
|
.get(&data_member) // Option<ValueType>
|
||||||
|
.unwrap_or(&ValueType::List(None)) // ValueType
|
||||||
|
.as_list() // Result<Option<Vec<ValueType>>, ValueType>
|
||||||
|
.unwrap_or(Some(vec!())) // Option<Vec<ValueType>>
|
||||||
|
.unwrap_or(vec!()) // Vec<ValueType>
|
||||||
|
.contains(&ValueType::String(Some(item_name.clone()))) // bool
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let on_change = {
|
||||||
|
let item_name = item.clone();
|
||||||
|
let data_member = data_member.clone();
|
||||||
|
move || {
|
||||||
|
let result = ValueType::String(Some(item_name.clone()));
|
||||||
|
data_map.update(|map| {
|
||||||
|
if let Some(value_type) = map.get_mut(&data_member) {
|
||||||
|
// Get the list from the enum
|
||||||
|
if let Some(mut list) = value_type.as_list().unwrap_or(None) {
|
||||||
|
// Check if the value exists in the list or not. Both of these branches
|
||||||
|
// directly update the value within the hash map, otherwise the change
|
||||||
|
// isn't reactive.
|
||||||
|
if let Some(index) = list.iter().position(|val| *val == result) {
|
||||||
|
// Value is already selected, deselect it.
|
||||||
|
list.remove(index);
|
||||||
|
} else {
|
||||||
|
// Value is not selected, so select it.
|
||||||
|
list.push(result);
|
||||||
|
}
|
||||||
|
*value_type = ValueType::List(Some(list));
|
||||||
|
} else {
|
||||||
|
// Update the value at the current data_member
|
||||||
|
let temp = map.get_mut(&data_member.clone()).unwrap();
|
||||||
|
*temp = ValueType::List(Some(vec![result]));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Insert a new list into the hash map
|
||||||
|
map.insert(data_member.clone(), ValueType::List(Some(vec![result])));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let checked_signal = Signal::derive(is_checked);
|
|
||||||
// Check if this checkbox should be selected
|
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked=checked_signal
|
checked=checked_signal
|
||||||
on_change={
|
on_change=on_change
|
||||||
let item_name = item.clone();
|
|
||||||
move || {
|
|
||||||
selected
|
|
||||||
.update(|list| {
|
|
||||||
if let Some(index) = list
|
|
||||||
.iter()
|
|
||||||
.position(|val| *val == item_name)
|
|
||||||
{
|
|
||||||
list.remove(index);
|
|
||||||
} else {
|
|
||||||
list.push(item_name.clone());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// The actual selected values are tracked by this element, not by the checkboxes themselves.
|
// The actual selected values are tracked by this element, not by the checkboxes themselves.
|
||||||
value=item.clone()
|
value=item.clone()
|
||||||
disabled=disabled
|
disabled=disabled
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use leptos::either::either;
|
use leptos::either::either;
|
||||||
use crate::components::{ActionButton, OutlinedTextField};
|
use crate::components::{ActionButton, OutlinedTextField};
|
||||||
@@ -15,6 +16,7 @@ fn render_entry_component(entry_data: MultiEntryData, member_info: MultiEntryMem
|
|||||||
<OutlinedTextField
|
<OutlinedTextField
|
||||||
label=member_info.display_name
|
label=member_info.display_name
|
||||||
data_member=member_info.member_name
|
data_member=member_info.member_name
|
||||||
|
data_map=RwSignal::new(HashMap::default())
|
||||||
placeholder="testing"
|
placeholder="testing"
|
||||||
value=reactive
|
value=reactive
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use std::fmt::Display;
|
|||||||
/// Defines a set of possible input types. They allow for the type and value of every data member
|
/// Defines a set of possible input types. They allow for the type and value of every data member
|
||||||
/// to be stored within a single `HashMap` or `Vec`. This means that even though all the data is
|
/// to be stored within a single `HashMap` or `Vec`. This means that even though all the data is
|
||||||
/// created as a single type, the values are still able to be type safe.
|
/// created as a single type, the values are still able to be type safe.
|
||||||
#[derive(Clone, Serialize, Deserialize, Debug)]
|
#[derive(PartialEq, Clone, Serialize, Deserialize, Debug)]
|
||||||
pub enum ValueType {
|
pub enum ValueType {
|
||||||
/// Indicates a `String` value.
|
/// Indicates a `String` value.
|
||||||
String(Option<String>),
|
String(Option<String>),
|
||||||
|
|||||||
@@ -10,14 +10,13 @@ use crate::components::ValueType;
|
|||||||
/// by the `value` prop of the component.
|
/// by the `value` prop of the component.
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Select(
|
pub fn Select(
|
||||||
#[prop(default = vec!["Test Value".into()])] value_list: Vec<String>,
|
#[prop()] value_list: Vec<String>,
|
||||||
#[prop(default = RwSignal::new("".into()))] value: RwSignal<String>,
|
#[prop(optional)] value: RwSignal<String>,
|
||||||
#[prop(into)] data_member: String,
|
#[prop(into)] data_member: String,
|
||||||
#[prop()] data_map: RwSignal<HashMap<String, ValueType>>,
|
#[prop()] data_map: RwSignal<HashMap<String, ValueType>>,
|
||||||
#[prop(optional, into)] label: String,
|
#[prop(optional, into)] label: String,
|
||||||
#[prop(default = RwSignal::new(false))] disabled: RwSignal<bool>,
|
#[prop(default = RwSignal::new(false))] disabled: RwSignal<bool>,
|
||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
|
|
||||||
let on_change = {
|
let on_change = {
|
||||||
let data_member = data_member.clone();
|
let data_member = data_member.clone();
|
||||||
move |e: Targeted<Event, HtmlSelectElement>| {
|
move |e: Targeted<Event, HtmlSelectElement>| {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ pub fn OutlinedTextField(
|
|||||||
#[prop(optional, into)] placeholder: String,
|
#[prop(optional, into)] placeholder: String,
|
||||||
// #[prop()] value: Subfield<TStore, TInner, ValueType>,
|
// #[prop()] value: Subfield<TStore, TInner, ValueType>,
|
||||||
#[prop(into)] data_member: String, // should this be an RwSignal??
|
#[prop(into)] data_member: String, // should this be an RwSignal??
|
||||||
#[prop(optional)] data_map: RwSignal<HashMap<String, ValueType>>,
|
#[prop()] data_map: RwSignal<HashMap<String, ValueType>>,
|
||||||
#[prop(optional)] value: RwSignal<ValueType>,
|
#[prop(optional)] value: RwSignal<ValueType>,
|
||||||
#[prop(default = "text".to_owned(), into)] input_type: String,
|
#[prop(default = "text".to_owned(), into)] input_type: String,
|
||||||
#[prop(optional)] disabled: RwSignal<bool>,
|
#[prop(optional)] disabled: RwSignal<bool>,
|
||||||
@@ -86,13 +86,11 @@ pub fn OutlinedTextField(
|
|||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
prop:name=name
|
prop:name=name
|
||||||
prop:value={
|
prop:value=move || data_map
|
||||||
data_map
|
.get() // HashMap
|
||||||
.get() // HashMap
|
.get(&data_member) // Option<ValueType>
|
||||||
.get(&data_member) // Option<ValueType>
|
.unwrap_or(&ValueType::String(None)) // ValueType
|
||||||
.unwrap_or(&ValueType::String(None)) // ValueType
|
.to_string()
|
||||||
.to_string()
|
|
||||||
}
|
|
||||||
on:input=on_input
|
on:input=on_input
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
|
|||||||
+15
-6
@@ -8,7 +8,7 @@ use serde_dynamo::{from_item, to_item};
|
|||||||
|
|
||||||
use crate::app::Unauthenticated;
|
use crate::app::Unauthenticated;
|
||||||
use crate::common::{ExpandableInfo, StudentInfo, UserClaims};
|
use crate::common::{ExpandableInfo, StudentInfo, UserClaims};
|
||||||
use crate::components::{ActionButton, Loading, OutlinedTextField, Panel, RadioList, Row, Select, ValueType};
|
use crate::components::{ActionButton, CheckboxList, Loading, OutlinedTextField, Panel, RadioList, Row, Select, ValueType};
|
||||||
use leptos::leptos_dom::logging::console_log;
|
use leptos::leptos_dom::logging::console_log;
|
||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
use leptos_oidc::{Algorithm, AuthLoaded, AuthSignal, Authenticated};
|
use leptos_oidc::{Algorithm, AuthLoaded, AuthSignal, Authenticated};
|
||||||
@@ -92,7 +92,7 @@ pub async fn create_sample_submission(student_info: StudentInfo) -> Result<(), S
|
|||||||
pub async fn log_expandable(info: ExpandableInfo) -> Result<(), ServerFnError> {
|
pub async fn log_expandable(info: ExpandableInfo) -> Result<(), ServerFnError> {
|
||||||
console_log(format!("Was given info: {:?}", info).as_str());
|
console_log(format!("Was given info: {:?}", info).as_str());
|
||||||
// Manually specify the type here since we're not able to infer the type later.
|
// Manually specify the type here since we're not able to infer the type later.
|
||||||
let item: HashMap<String, AttributeValue> = to_item(info.clone())?;
|
let item: HashMap<String, AttributeValue> = to_item(&info)?;
|
||||||
console_log(format!("Converted into a DynamoDB item: {:?}", item).as_str());
|
console_log(format!("Converted into a DynamoDB item: {:?}", item).as_str());
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -181,9 +181,7 @@ pub fn HomePage() -> impl IntoView {
|
|||||||
</p>
|
</p>
|
||||||
</Row>
|
</Row>
|
||||||
<Row>
|
<Row>
|
||||||
// Each OutlinedTextField could be simplified later into an enum:
|
// Text { data_member, label, placeholder }
|
||||||
// Text { data_member, label, placeholder }, i.e.
|
|
||||||
// Text { "first_name", "First Name:", "John" }
|
|
||||||
<OutlinedTextField
|
<OutlinedTextField
|
||||||
label="First Name:"
|
label="First Name:"
|
||||||
placeholder="John"
|
placeholder="John"
|
||||||
@@ -237,7 +235,6 @@ pub fn HomePage() -> impl IntoView {
|
|||||||
/>
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
<Row>
|
<Row>
|
||||||
// Each RadioList could be simplified into an enum:
|
|
||||||
// Radio { data_member, label, items }
|
// Radio { data_member, label, items }
|
||||||
<RadioList
|
<RadioList
|
||||||
label="Town:"
|
label="Town:"
|
||||||
@@ -250,6 +247,18 @@ pub fn HomePage() -> impl IntoView {
|
|||||||
data_map = expandable_react.data
|
data_map = expandable_react.data
|
||||||
/>
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row>
|
||||||
|
<CheckboxList
|
||||||
|
label="Favorite Candies:"
|
||||||
|
items=vec!["Twizzlers", "Reese's", "Starburst"]
|
||||||
|
.into_iter()
|
||||||
|
.map(|s| s.into())
|
||||||
|
.collect()
|
||||||
|
disabled=elements_disabled
|
||||||
|
data_member="favorite_candies"
|
||||||
|
data_map=expandable_react.data
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
<Row>
|
<Row>
|
||||||
// Select { data_member, label, items }
|
// Select { data_member, label, items }
|
||||||
<Select
|
<Select
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
use crate::app::Unauthenticated;
|
use crate::app::Unauthenticated;
|
||||||
use crate::common::UserClaims;
|
use crate::common::UserClaims;
|
||||||
use crate::components::{Loading, MultiEntry, MultiEntryData, MultiEntryMember, OutlinedTextField, ValueType};
|
use crate::components::{Loading, MultiEntry, MultiEntryData, MultiEntryMember, OutlinedTextField, ValueType};
|
||||||
@@ -24,11 +25,13 @@ pub fn ProviderEntry() -> impl IntoView {
|
|||||||
<h1>This is the provider entry page</h1>
|
<h1>This is the provider entry page</h1>
|
||||||
<h2>It would usually have a series of questions that we ask the providers</h2>
|
<h2>It would usually have a series of questions that we ask the providers</h2>
|
||||||
<div>
|
<div>
|
||||||
|
// Much of this is just a placeholder.
|
||||||
<OutlinedTextField
|
<OutlinedTextField
|
||||||
placeholder = "Testing information..."
|
placeholder = "Testing information..."
|
||||||
name = "testing_input"
|
name = "testing_input"
|
||||||
label = "This is a testing question."
|
label = "This is a testing question."
|
||||||
data_member = "testing"
|
data_member = "testing"
|
||||||
|
data_map = RwSignal::new(HashMap::new())
|
||||||
value = RwSignal::new(ValueType::String(None))
|
value = RwSignal::new(ValueType::String(None))
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in new issue
Block a user