Senseless
Senseless

Reputation: 99

Convert imperial values to metric by dropdown lists and user input and javascript

I have these variables:

  var imperialConverterTable = {
    inch: 25.4,                 
    foot: 12*25.4,           
    yard: 3*12*25.4,    
    mile: 1760*3*12*25.4
  };
  var metricConverterTable = {
    millimeter: 1, 
    centimeter: 10,  
    decimeter:  100, 
    meter:      1000,  
    kilometer:  1E6,  
    mil:        1E7      
  };

And I want to set up a JavaScript that changes the values immediately on user input. Ideally it also changes values based on what choices the user makes in two different dropdown lists:

<input type="number" id="lengthVal" value=«10»>
    <select id="imperialBenevning">
    <option id="inch" type="number" oninput="imperialConverterTable(this.id,this.value)" onchange="imperialConverterTable(this.id,this.value)">inch</option>
    <option id="foot" type="number" oninput="imperialConverterTable(this.id,this.value)" onchange="imperialConverterTable(this.id,this.value)">foot</option>
    <option id="yard" type="number" oninput="imperialConverterTable(this.id,this.value)" onchange="imperialConverterTable(this.id,this.value)">yard</option>
    <option id="mile" type="number" oninput="imperialConverterTable(this.id,this.value)" onchange="imperialConverterTable(this.id,this.value)">mile</option>
    </select>

    <label> = </label>

    <select id="metricBenevning">
// Some kind of output-field here?
    <option id="millimeter" type="number" oninput="metricConverterTable(this.id,this.value)" onchange="metricConverterTable(this.id,this.value)">millimeter</option>
    <option id="centimeter" type="number" oninput="metricConverterTable(this.id,this.value)" onchange="metricConverterTable(this.id,this.value)">centimeter</option>
    <option id="decimeter" type="number" oninput="metricConverterTable(this.id,this.value)" onchange="metricConverterTable(this.id,this.value)">decimeter</option>
    <option id="meter" type="number" oninput="metricConverterTable(this.id,this.value)" onchange="metricConverterTable(this.id,this.value)">meter</option>
    <option id="kilometer" type="number" oninput="metricConverterTable(this.id,this.value)" onchange="metricConverterTable(this.id,this.value)">kilometer</option>
    <option id="mil" type="number" oninput="metricConverterTable(this.id,this.value)" onchange="metricConverterTable(this.id,this.value)">mil</option>
    </select>

I tried to make an if-function like this:

function lengthVal(source,valNum) {

  valNum = parseFloat(valNum);
  var foot = document.getElementById("foot");
  var inch = document.getElementById("inch");
  var yard = document.getElementById("yard");
  var mile = document.getElementById("mile");
  var centimeter = document.getElementById("centimeter");
  var decimeter = document.getElementById("decimeter");
  var meter = document.getElementById("meter");
  var kilometer = document.getElementById("kilometer");
  var mil = document.getElementById("mil");

  if (source=="foot") {
    meter.value=(valNum/3.2808).toFixed(2);
    inch.value=(valNum*12).toFixed(2);
    //and so on for all the values, but no luck so far..
  }

I guess this several questions at once, but just a few hints or comments would be really helpful.

Upvotes: 1

Views: 1168

Answers (1)

Thomas
Thomas

Reputation: 12657

something like this?

// JS' Numeric system sometimes has some issues that can not be properly represented in binary format
const fixFloat = value => Math.round(value * 1e10) / 1e10;

// a simple definition of the units, imo. very readable
// I made the meter = 1 since in the metric system 
// all other lengths are derived from that. It's literally in the name
// milli-meter == one thousandth meter
// this only affects the values, but not the conversions
const meter = 1,
  decimeter = meter / 10,
  centimeter = meter / 100,
  millimeter = meter / 1000,
  kilometer = meter * 1000,
  mil = 10 * kilometer, // Scandinavian mile, had to look this one up, imo. it's not common

  inch = 25.4 * millimeter,
  foot = 12 * inch,
  yard = 3 * foot,
  mile = 1760 * yard;

// Since I have them defined now, 
// I want to create the options for the prepared select-boxes from these values 

let options = [
    ["Imperial", {
      inch,
      foot,
      yard,
      mile
    }],

    ["Metric", {
      millimeter,
      centimeter,
      decimeter,
      meter,
      kilometer,
      mil
    }]
  ].map(([label, units]) => `
<optgroup label="${label}">
  ${ Object.entries(units)
           .sort((a,b) => a[1] - b[1])
           .map(([unit, value]) => `<option value="${fixFloat(value)}">${unit}</option>`)
           .join("\n  ")
  }
</optgroup>`)
  .join("")
  .trim();

console.log(options);

[...document.querySelectorAll('#aa, #bb')].forEach(select => {
  select.innerHTML = options;
});

//now let's bring some interactivity to this:

function convert(value, fromScale, toScale) {
  return fixFloat(value * fromScale / toScale);
}

function update(e) {
  // deciding in wich direction to compute, based on what field has been changed
  switch (e.target.id) {
    // -->
    case "a":
    case "bb":
      document.querySelector("#b").value = convert(
        document.querySelector("#a").value,
        document.querySelector("#aa").value,
        document.querySelector("#bb").value
      );
      break;
      
    // <--
    case "b":
    case "aa":
      document.querySelector("#a").value = convert(
        document.querySelector("#b").value,
        document.querySelector("#bb").value,
        document.querySelector("#aa").value
      );
      break;
  }
}

// add the event-listener to the input and select-boxes
[...document.querySelectorAll('select, input')].forEach(elm => {
  elm.oninput = elm.onchange = update;
});
<input type="number" id="a" value="1">
<select id="aa"></select>

<span>&nbsp;=&nbsp;</span>

<input type="number" id="b" value="1" >
<select id="bb"></select>

since the top half of this code is just defining the units to create the <option>, you can just write the generated markup directly in the <select> boxes and the JS only has to handle the computations:

// JS' Numeric system has some issues with numbers that 
// can not be properly represented in binary format:
// check: console.log(0.1 * 0.1);
const fixFloat = value => Math.round(value * 1e10) / 1e10;

function convert(value, fromScale, toScale) {
  return fixFloat(value * fromScale / toScale);
}

function update(e) {
  // deciding in wich direction to compute, based on what field has been changed
  switch (e.target.id) {
    // -->
    case "a":
    case "bb":
      document.querySelector("#b").value = convert(
        document.querySelector("#a").value,
        document.querySelector("#aa").value,
        document.querySelector("#bb").value
      );
      break;

      // <--
    case "b":
    case "aa":
      document.querySelector("#a").value = convert(
        document.querySelector("#b").value,
        document.querySelector("#bb").value,
        document.querySelector("#aa").value
      );
      break;
  }
}

// add the event-listener to the input and select-boxes
[...document.querySelectorAll('select, input')].forEach(elm => {
  elm.oninput = elm.onchange = update;
});
<input type="number" id="a" value="1">
<select id="aa">
  <!-- 
    the unit for these values is meter
    1 inch === 0.0254 meter
  -->
  <optgroup label="Imperial">
    <option value="0.0254">inch</option>
    <option value="0.3048">foot</option>
    <option value="0.9144">yard</option>
    <option value="1609.344">mile</option>
  </optgroup>
  <optgroup label="Metric">
    <option value="0.001">millimeter</option>
    <option value="0.01">centimeter</option>
    <option value="0.1">decimeter</option>
    <option value="1">meter</option>
    <option value="1000">kilometer</option>
    <option value="10000">mil</option>
  </optgroup>
</select>

<span>&nbsp;=&nbsp;</span>

<input type="number" id="b" value="1">
<select id="bb">
  <optgroup label="Imperial">
    <option value="0.0254">inch</option>
    <option value="0.3048">foot</option>
    <option value="0.9144">yard</option>
    <option value="1609.344">mile</option>
  </optgroup>
  <optgroup label="Metric">
    <option value="0.001">millimeter</option>
    <option value="0.01">centimeter</option>
    <option value="0.1">decimeter</option>
    <option value="1">meter</option>
    <option value="1000">kilometer</option>
    <option value="10000">mil</option>
  </optgroup>
</select>

Upvotes: 2

Related Questions