Skip to content

Example: Temperature Converter

example
js
import { compose } from 'paintor'
import { TemperatureConverter } from './components/TemperatureConverter.js'

compose(
  TemperatureConverter({ scales: ['celsius', 'fahrenheit', 'kelvin'] })
).paint('#temperature-converter')
js
import { template } from 'paintor'
import { TemperatureInput } from './TemperatureInput.js'

export function TemperatureConverter({ scales }) {
  const temperatureInputs = []

  for (const scale of scales) {
    temperatureInputs.push(TemperatureInput(scale))
  }

  return template(temperatureInputs)
}
js
import { state, template } from 'paintor'

const temperaturesState = state({
  celsius: '',
  fahrenheit: '',
  kelvin: ''
})

const scaleNames = {
  celsius: 'Celsius',
  fahrenheit: 'Fahrenheit',
  kelvin: 'Kelvin'
}

const converters = {
  celsius: {
    fahrenheit(celsius) {
      return (celsius * 9 / 5) + 32
    },
    kelvin(celsius) {
      return celsius + 273.15
    }
  },
  fahrenheit: {
    celsius(fahrenheit) {
      return (fahrenheit - 32) * 5 / 9
    },
    kelvin(fahrenheit) {
      return ((fahrenheit - 32) * 5 / 9) + 273.15
    }
  },
  kelvin: {
    celsius(kelvin) {
      return kelvin - 273.15
    },
    fahrenheit(kelvin) {
      return ((kelvin - 273.15) * 9 / 5) + 32
    },
  }
}

function getConverterFunction(fromScale, toScale) {
  const converterFn = converters[fromScale][toScale]

  if (!converterFn) {
    throw new Error(`There is no converter function to convert ${fromScale} to ${toScale}`)
  }

  return converterFn
}

function convert(fromScale, toScale, temperature) {
  const input = parseFloat(temperature)

  if (Number.isNaN(input)) {
    return ''
  }

  const output = getConverterFunction(fromScale, toScale)(input)
  const rounded = Math.round(output * 1000) / 1000

  return rounded.toString()
}

export function TemperatureInput(fromScale) {
  return template((x) => {
    const handleChange = ({target}) => {
      temperaturesState[fromScale] = target.value

      const toScales = Object.keys(converters[fromScale])

      for (const toScale of toScales) {
        temperaturesState[toScale] = convert(fromScale, toScale, target.value)
      }
    }

    x.div(
      x.input({
        onKeyUp: handleChange,
        value: () => temperaturesState[fromScale],
      }),
      x.label(scaleNames[fromScale])
    )
  })
}
css
#temperature-converter {
  padding: 0.4rem;
  width: fit-content;
  border-radius: 0.4rem;
  border: 0.1rem solid currentColor;

  & div {
    display: table;

    & label {
      display: table-cell;
      padding: 0.3rem;
      width: 5rem;
    }
    & input {
      display: table-cell;
      margin: 0.3rem;
      padding: 0 0.3rem;
      width: 5rem;
      border: 0.1rem solid deepskyblue;
      border-radius: 0.4rem;
    }
  }
}
html
<div id="temperature-converter"></div>