Skip to content

<intput> with f64 values and interactive editing #1956

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
qknight opened this issue Oct 27, 2023 · 6 comments
Closed

<intput> with f64 values and interactive editing #1956

qknight opened this issue Oct 27, 2023 · 6 comments
Labels

Comments

@qknight
Copy link

qknight commented Oct 27, 2023

Describe the bug

Big picture:

  • I have several input fields for f64 type and combined those are used to compute some value from an equation
  • I also want to have a button "load example data" which sets values (other than the default values at form startup)

Not sure this is a bug or me using the signals/input typs wrong.

question

Example Input() uses only a f64 and updates itself on changes. This breaks editing, as going from "1.2" (with backspace) rewrites the number to "1" instead of "1.".

Question: Is using signals in a loop a bad thing?

the code

pub fn Input() -> impl IntoView {
    let input_float = create_rw_signal(1.0);
    view! {
        <h2>input2</h2>
        <p>Using american f64 float notation type internally and externally. No conversion from , into "." so only use american notation!</p>
        <p>Enter: 1.23, then backspace yields 1.2 (which is ok), hit backspace again, yields 1 instead of 1.</p>
        <p>Problem: the "." is removed when it should not</p>
        <input data-testid="Input2" type="number" inputmode="decimal" lang="us-US"  on:input=move |ev| {
            let old_value = input_float.get();
            let input: String = event_target_value(&ev);
            info!("input = {}", input);
            match input.parse::<f64>() {
              Ok(v) => {
                if v != old_value {
                  input_float.set(v);
                  info!("input_float = {}", input_float.get());
                }
              },
              Err(e) => info!("{}: zz not parsable", e)
            }
          }
          prop:value={ move || input_float.get() } />
        <p>
        You selected: { move || input_float.get()
        }
        </p>
    }
}
@qknight
Copy link
Author

qknight commented Oct 27, 2023

I think I understand using european vs. us number formatting. I need to use the browser locale setting, or something similar to this code:

<input data-testid="Input2" type="number" inputmode="decimal" lang="us-US" on:input=move |ev| {

https://stackoverflow.com/questions/13412204/localization-of-input-type-number

I'll update the question to make it shorter now.

@tqwewe
Copy link
Contributor

tqwewe commented Oct 27, 2023

The way I typically do this is using a String for the input value, and then a derived signal parsing the value into f64.

let value = RwSignal::new(String::new());
let num: Signal<Option<f64>> = Signal::derive(move || value.with(|value| value.parse().ok()));

@tqwewe
Copy link
Contributor

tqwewe commented Oct 28, 2023

Nevermind, I also just came across this issue!
I found this PR for React: facebook/react#7359

In my case I'm going to use uncontrolled inputs to let my browser control the value.

let num = RwSignal::new(0.0);

view! {
    <input type="number" value=num on:input=move |ev| {
        let input: web_sys::HtmlInputElement = event_target(&ev);
        let value = input.value_as_number();
        if !value.is_nan() {
            num.set(value);
        }
    } />
}

Note: I don't use prop:value=num, only value=num for the initial value.

@gbj gbj added the support label Oct 29, 2023
@gbj
Copy link
Collaborator

gbj commented Oct 29, 2023

I do not think this is a bug in Leptos. Specifically, when you use prop:value you are saying, "every time input_float changes, set the .value field of the input to the value of input_float".

If I just open up the example and pull the input into the console, input.value = "1.0" sets the text to "1.0", and input.value = 1.0 sets the text to "1".

Using an uncontrolled input (so, not using prop:value) should work, and using a string signal for prop:value should work. But otherwise this is just the browser doing what the browser does.

@gbj gbj closed this as not planned Won't fix, can't repro, duplicate, stale Oct 29, 2023
@qknight
Copy link
Author

qknight commented Nov 5, 2023

@tqwewe thanks for pointing out how to modify the input value directly!

        let input: web_sys::HtmlInputElement = event_target(&ev);
        let value = input.value_as_number();

@gbj thanks for clearifications. i will decouple the f64 value from the prop:value and use a string instead!

i do this now:

  • on:focus i rewrite the 1.000.000,73 number into 1000000,73 (EU notation) and the user can edit this number. once he is finished the
  • on:blur converts it into a f64 and also updates the input_element.set_value(xxx) string
  • on:input i try to parse it into f64 and if successful i update the outgoing signal value

thanks so much!

@qknight
Copy link
Author

qknight commented Nov 23, 2023

This is the code I think i'll use:

Internally it uses a String so I can even have incomplete values like: "1,".

When compared to using a float as internal value I had this problem: Say you were editing 1,2 into 1,3 and you would remove the 3 at the end, it would auto-correct into 1 instead of 1, so one would always have to enter ,3 instead of just 3. Hard to explain. But for whoever cares, this is the code I think works nice:

// parses 1.100.100,23 to 1100100.23
fn parse_de_string_into_f64(input: String) -> Result<f64, String> {
    let f = input.replace(".", "").replace(",", ".").parse::<f64>();
    match f {
        Ok(v) => {
            Ok(v)
        },
        Err(e) => {
            Err(format!("{}", e))
        }
    }
}

// formats 23222221231.7666 to 23.222.221.231,7666
// formats 23222221231 to 23.222.221.231
// formats 2, to 2
fn format_f64_into_de_string(number: f64) -> String {
    let mut num_str = format!("{:.}", number);
    let mut integer="";
    let mut decimal="";
    if num_str.find('.').is_none() {
        num_str.push_str(".");
        (integer, _) = num_str.split_at(num_str.find('.').unwrap());
    } else {
        (integer, decimal) = num_str.split_at(num_str.find('.').unwrap());
    }
    let integer_str = integer.chars().rev().enumerate().map(|(i, c)| {
        let z: &str = if i!= 0 &&i % 3==0 && i!=integer.len() {"."} else {""};
        format!("{}{}", z, c.to_string())}).collect::<String>();
    let v = integer_str.chars().rev().collect::<String>();

    format!("{}{}", v, decimal.replace(".", ","))
}

pub fn Input4() -> impl IntoView {
    let input_string = create_rw_signal(String::new());
    let output_float = RwSignal::new(Option::<f64>::None);
    let infoX = create_rw_signal("".to_string());
    view! {
        <h2>input4</h2>
        <p>With focus events: Using the same f64 type internally and as signal externally</p>
        <input data-testid="Input4" type="text"
          on:focusin=move |ev| {
            let z = input_string.get().replace(".", "");
            input_string.set(format!("{}", z));
          }
          on:focusout=move |ev| {
            let zz = parse_de_string_into_f64(input_string.get());
            match zz {
              Ok(v) => {
                let s = format_f64_into_de_string(v);
                infoX.set("".to_string());
                input_string.set(s);
              },
              Err(e) => {
                infoX.set("Can't update input_string because float is invalid".to_string());
                info!("{}: zz not parsable", e)
              }
            }
          }
          on:input=move |ev| {
            let input: String = event_target_value(&ev);
            input_string.set(input);
          }
          prop:value = move || {
            let zz = parse_de_string_into_f64(input_string.get());
            match zz {
              Ok(v) => {
                  infoX.set("".to_string());
                  output_float.set(Some(v));
              },
              Err(e) => {
                infoX.set("Can't update output_float from prop:value because float is invalid".to_string());
                output_float.set(None);
              }
            }
            input_string.get()
            }
        />
        <p>
        input_string: { move || input_string.get()}
        </p>
        <p>
        output_float: { move || {let o = output_float.get();  if o.is_some() {format!("{}", o.unwrap())} else {"None".to_string()} }}
        </p>
        <p>
        "Status: " { move || infoX.get() }
        </p>
        <button on:click=move |_| {
          input_string.set("1,23".to_string());
        }
        >Reset</button>
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants