Lines
14.71 %
Functions
10.34 %
Branches
100 %
use std::cell::RefCell;
use std::rc::Rc;
use wasm_bindgen::JsCast;
use web_sys::{Element, HtmlInputElement};
use super::account::get_input_by_id;
use super::component::{Autocomplete, AutocompleteConfig, attach_autocomplete};
use crate::data_attrs::{AutocompleteAttr, selectors};
use crate::types::CommoditySuggestion;
pub struct CommodityAutocomplete;
impl CommodityAutocomplete {
pub fn attach(wrapper: Element) -> Option<()> {
let document = web_sys::window()?.document()?;
let fetch_url = wrapper
.get_attribute(AutocompleteAttr::FetchUrl.as_str())
.unwrap_or_else(|| "/api/commodity/search".to_string());
let display_input_id = wrapper.get_attribute(AutocompleteAttr::DisplayInput.as_str())?;
let hidden_input_id = wrapper.get_attribute(AutocompleteAttr::HiddenInput.as_str())?;
let currency_input_id = wrapper.get_attribute(AutocompleteAttr::CurrencyInput.as_str());
let display_input = get_input_by_id(&document, &display_input_id)?;
let hidden_input = get_input_by_id(&document, &hidden_input_id)?;
let currency_input = currency_input_id.and_then(|id| get_input_by_id(&document, &id));
let container = wrapper
.query_selector(selectors::RESULTS_CONTAINER)
.ok()??;
let autocomplete = Autocomplete::<CommoditySuggestion>::new(
display_input.clone(),
hidden_input,
currency_input,
container,
);
let mut config = AutocompleteConfig::post(fetch_url, commodity_body_builder);
// Auto-fill "to" commodity when "from" commodity is selected
if display_input
.get_attribute("data-field")
.is_some_and(|f| f == "from-commodity")
{
config = config.with_on_select(Rc::new(create_autofill_callback(display_input)));
}
let ac = Rc::new(RefCell::new(autocomplete));
attach_autocomplete(ac, config);
Some(())
fn create_autofill_callback(
from_input: HtmlInputElement,
) -> impl Fn(&HtmlInputElement, &str, &str) {
move |_input, id, _secondary| {
let Some(split_entry) = from_input.closest(".split-entry").ok().flatten() else {
return;
};
let to_display: Option<HtmlInputElement> = split_entry
.query_selector(r#".commodity-display[data-field="to-commodity"]"#)
.ok()
.flatten()
.and_then(|el| el.dyn_into().ok());
let to_hidden: Option<HtmlInputElement> = split_entry
.query_selector(r#".commodity-value[data-field="to-commodity"]"#)
if let Some(to_display) = to_display
&& to_display.value().is_empty()
to_display.set_value(&from_input.value());
if let Some(to_hidden) = to_hidden {
to_hidden.set_value(id);
if let Ok(event) = web_sys::Event::new("change") {
let _ = to_hidden.dispatch_event(&event);
#[must_use]
pub fn commodity_body_builder(query: &str) -> String {
format!(r#"{{"commodity-search":"{query}"}}"#)
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn body_builder_formats_correctly() {
assert_eq!(
commodity_body_builder("USD"),
r#"{"commodity-search":"USD"}"#
fn body_builder_handles_empty_query() {
assert_eq!(commodity_body_builder(""), r#"{"commodity-search":""}"#);