Search code examples
javascripthtmluser-interfacenumber-formatting

Dual (linked) text field number format entry for a single number in JavaScript?


I've tried to implement a similar functionality in the past, but it somehow always resulted with recursive calls and headaches.

The functionality is:

I want two input textfields, that will change a single number in memory, but with different formats - and will both automatically update upon change of value of either textfield (and in that sense, the textfields would be "linked" or "synchronized").

In the example below, I want to manage the "main" number variable mynum_perc internally in integer percent; and one of the text fields should allow for display and changing of mynum_perc as integer percent directly; the other text field should display (and allow for change) of the same variable, but expressed as a "relative" floating number between 0.0 and 1.0

(In principle you can apply the same thinking for, say, dual text inputs, one showing float in Celsius, the other showing synchronized float in Fahrenheit - but internally you manage only a single float variable, say the Celsius one).

So now I came to needing this again, and I thought I'd prepare an example preemptively, so as to ask when I get some trouble - but surprisingly for me, the example below works:

var in_perc_tf = document.getElementById('perc');
var in_rel_tf = document.getElementById('rel');

var mynum_perc = 20;

function perc_change(event) {
  console.log("perc_change");
  var elem = event.target;
  // assign new value
  mynum_perc = parseInt(elem.value);
  // recalc and assign to rel
  in_rel_tf.value = mynum_perc/100.0;
}

function rel_change(event) {
  console.log("rel_change");
  var elem = event.target;
  // get new value
  let newrelval = parseFloat(elem.value);
  // recalc and assign to perc
  mynum_perc = Math.round(newrelval*100);
  in_perc_tf.value = mynum_perc;
}

in_perc_tf.addEventListener('change', perc_change);
in_rel_tf.addEventListener('change', rel_change);

in_perc_tf.value = mynum_perc;
<input type="text" id="perc"></input>
<input type="text" id="rel"></input>

It works in the sense that when you enter a number in one field, and press ENTER or TAB, the number in the other field updates as expected.

The problem is, that if I assign the value programmatically, e.g. in_perc_tf.value = mynum_perc;, then only the one field updates, not both. (now that I think of it, it's possible that in the past I used to get recursive calls, precisely because I tried to override the default behavior, so as to achieve update of both text field upon programmatic change on either of them).

So, I guess, my primary question is - how can I keep the current UI keyboard functionality, and have both text fields update upon a value assignment of either of them?

On the other hand, I was persuaded someone would have made a JS library for something like this already, but surprisingly, I couldn't find anything even close. Since SO is against asking for software recommendations, let me try to ask in a different way: is there a different terminology for "dual format linked/synchronized text-field entries for a single number", so I could try look up an existing JS library myself?


Solution

  • You can create a function which updates both inputs. In the example below, the function is named update and it is used to programmatically update the values immediately, then again after 1 second:

    const in_perc_tf = document.getElementById("perc");
    const in_rel_tf = document.getElementById("rel");
    
    let mynum_perc = 20;
    
    function update(integer) {
      mynum_perc = integer;
      in_perc_tf.value = integer;
      in_rel_tf.value = integer / 100;
    }
    
    function perc_change(event) {
      console.log("perc_change");
      update(parseInt(event.currentTarget.value));
    }
    
    function rel_change(event) {
      console.log("rel_change");
      update(Math.round(parseFloat(event.currentTarget.value) * 100));
    }
    
    in_perc_tf.addEventListener("change", perc_change);
    in_rel_tf.addEventListener("change", rel_change);
    
    update(mynum_perc);
    setTimeout(() => update(40), 1000);
    <input type="text" id="perc"></input>
    <input type="text" id="rel"></input>