diff --git a/src/app.rs b/src/app.rs index e90cecb..5755079 100644 --- a/src/app.rs +++ b/src/app.rs @@ -10,7 +10,7 @@ use serde::{Deserialize, Serialize}; use crate::answers::{Answer, SelfTransitionAnswer, TransitionAnswers, TransitionItem}; use crate::auth::{current_user, User}; use crate::content::{is_qualified, Alternative, Question, Responsible, Transition}; -use crate::resource::{get_requirement_options, get_resource}; +use crate::resource::{get_requirement_binding, get_requirement_options, get_resource}; /// The visible site name/wordmark - "portal" is just this codebase's /// working title, not necessarily what any given deployment is called. @@ -758,6 +758,58 @@ fn AlternativeCard( let sig = field_map.get(&req.name).copied().unwrap_or_else(|| RwSignal::new(String::new())); + // A bound field watches a sibling and loads its own value from + // the bind's resource whenever the sibling changes. An empty + // sibling never fetches - and never clears an edit. + if let Some(bind) = req + .bind + .clone() + .filter(|_| !matches!(req.kind.as_str(), "select" | "file")) + { + let watched_select = select_field_map.get(&bind.field).copied(); + let watched_text = field_map.get(&bind.field).copied(); + let param_name = bind.param_name().to_string(); + let q = question_id.clone(); + let a = alt_name.clone(); + let f = feature_name.clone(); + let rname = req.name.clone(); + let bound = Resource::new( + move || { + let value = watched_select + .map(|s| s.get().first().cloned().unwrap_or_default()) + .or_else(|| watched_text.map(|s| s.get())) + .unwrap_or_default(); + (q.clone(), a.clone(), f.clone(), rname.clone(), param_name.clone(), value) + }, + |(q, a, f, r, p, value)| async move { + if value.is_empty() { + return Ok(serde_json::Value::Null); + } + let params = std::collections::HashMap::from([(p, value)]); + get_requirement_binding(q, a, f, r, params).await + }, + ); + Effect::new(move |_| { + if let Some(Ok(value)) = bound.get() { + let text = match value { + serde_json::Value::Null => return, + serde_json::Value::String(s) => s, + // apply_jq wraps every yield in an array - a + // single string yield unwraps to itself. + serde_json::Value::Array(items) if items.len() == 1 => { + match &items[0] { + serde_json::Value::String(s) => s.clone(), + other => serde_json::to_string_pretty(other).unwrap_or_default(), + } + } + other => serde_json::to_string_pretty(&other).unwrap_or_default(), + }; + sig.set(text); + } + }); + } + let is_bound = req.bind.is_some(); + if req.kind == "select" { let select_sig = select_field_map .get(&req.name) @@ -864,6 +916,7 @@ fn AlternativeCard( view! {