├── README.md ├── Screenshot_1.png ├── Screenshot_2.png ├── app.js ├── index.html ├── requirements.md └── style.css /README.md: -------------------------------------------------------------------------------- 1 | # WB Multi Converter 2 | 3 | A fully functional vanilla js multi converter project. The business logic is totally separated from DOM. Idea comes from googles multi converted project. This project is open to add multiple categories and units. Just need to change the configuration object. Do not need to touch inside any DOM related codes. 4 | 5 | [Live Demo](https://mrhm-dev.github.io/wb-multi-converter/) 6 | 7 | ![Screenshot 1](Screenshot_1.png) 8 | 9 | ![Screenshot 1](Screenshot_2.png) 10 | -------------------------------------------------------------------------------- /Screenshot_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrhm-dev/wb-multi-converter/4b50e4ba9373d7384730d2c29ed1408faba12674/Screenshot_1.png -------------------------------------------------------------------------------- /Screenshot_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrhm-dev/wb-multi-converter/4b50e4ba9373d7384730d2c29ed1408faba12674/Screenshot_2.png -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | window.onload = function () { 2 | main(); 3 | }; 4 | 5 | const converter = { 6 | area: { 7 | name: 'Area', 8 | units: { 9 | squareKm: 'Square Kilometer', 10 | squareM: 'Square Meter', 11 | squareMile: 'Square Mile', 12 | squareYard: 'Square Yard', 13 | squareFoot: 'Square Foot', 14 | }, 15 | variants: { 16 | 'squareKm:squareM': { 17 | formula: 'multiply the area value by 1000000', 18 | calculation(n) { 19 | return n * 1000000; 20 | }, 21 | }, 22 | 'squareKm:squareMile': { 23 | formula: 'divide the area value by 2.59', 24 | calculation(n) { 25 | return n / 2.59; 26 | }, 27 | }, 28 | 'squareKm:squareYard': { 29 | formula: 'multiply the area value by 1196000', 30 | calculation(n) { 31 | return n * 1196000; 32 | }, 33 | }, 34 | 'squareKm:squareFoot': { 35 | formula: 'multiply the area value by 10760000', 36 | calculation(n) { 37 | return n * 10760000; 38 | }, 39 | }, 40 | 'squareM:squareKm': { 41 | formula: 'divide the area value by 1e+6', 42 | calculation(n) { 43 | return n / new Number('1e+6'); 44 | }, 45 | }, 46 | 'squareM:squareMile': { 47 | formula: 'divide the area value by 2.59e+6', 48 | calculation(n) { 49 | return n / new Number('2.59e+6'); 50 | }, 51 | }, 52 | 'squareM:squareYard': { 53 | formula: 'multiply the area value by 1.196', 54 | calculation(n) { 55 | return n * 1.196; 56 | }, 57 | }, 58 | 'squareM:squareFoot': { 59 | formula: 'multiply the area value by 10.764', 60 | calculation(n) { 61 | return n * 10.764; 62 | }, 63 | }, 64 | 'squareMile:squareKm': { 65 | formula: 'multiply the area value by 2.59', 66 | calculation(n) { 67 | return n * 2.59; 68 | }, 69 | }, 70 | 'squareMile:squareM': { 71 | formula: 'multiply the area value by 2.59e+6', 72 | calculation(n) { 73 | return n * new Number('2.59e+6'); 74 | }, 75 | }, 76 | 'squareMile:squareYard': { 77 | formula: 78 | 'for an approximate result, multiply the area value by 3.098e+6', 79 | calculation(n) { 80 | return n * new Number('3.098e+6'); 81 | }, 82 | }, 83 | 'squareMile:squareFoot': { 84 | formula: 85 | 'for an approximate result, multiply the area value by 2.788e+7', 86 | calculation(n) { 87 | return n * new Number('2.788e+7'); 88 | }, 89 | }, 90 | 'squareYard:squareKm': { 91 | formula: 'divide the area value by 1.196e+6', 92 | calculation(n) { 93 | return n / new Number('1.196e+6'); 94 | }, 95 | }, 96 | 'squareYard:squareM': { 97 | formula: 'divide the area value by 1.196', 98 | calculation(n) { 99 | return n / 1.196; 100 | }, 101 | }, 102 | 'squareYard:squareMile': { 103 | formula: 104 | 'for an approximate result, divide the area value by 3.098e+6', 105 | calculation(n) { 106 | return n / new Number('3.098e+6'); 107 | }, 108 | }, 109 | 'squareYard:squareFoot': { 110 | formula: 'multiply the area value by 9', 111 | calculation(n) { 112 | return n / 9; 113 | }, 114 | }, 115 | 'squareFoot:squareKm': { 116 | formula: 117 | 'for an approximate result, divide the area value by 1.076e+7', 118 | calculation(n) { 119 | return n / new Number('1.076e+7'); 120 | }, 121 | }, 122 | 'squareFoot:squareM': { 123 | formula: 'divide the area value by 10.764', 124 | calculation(n) { 125 | return n / 10.764; 126 | }, 127 | }, 128 | 'squareFoot:squareMile': { 129 | formula: 130 | 'for an approximate result, divide the area value by 2.788e+7', 131 | calculation(n) { 132 | return n / new Number('2.788e+7'); 133 | }, 134 | }, 135 | 'squareFoot:squareYard': { 136 | formula: 'divide the area value by 9', 137 | calculation(n) { 138 | return n / new Number('9'); 139 | }, 140 | }, 141 | }, 142 | }, 143 | mass: { 144 | name: 'Mass', 145 | units: { 146 | tonne: 'Tonne', 147 | kilogram: 'Kilogram', 148 | gram: 'Gram', 149 | }, 150 | variants: { 151 | 'tonne:kilogram': { 152 | formula: 'multiply the mass value by 1000', 153 | calculation(n) { 154 | return n * 1000; 155 | }, 156 | }, 157 | 'tonne:gram': { 158 | formula: 'multiply the mass value by 1e+6', 159 | calculation(n) { 160 | return n * new Number('1e+6'); 161 | }, 162 | }, 163 | 'kilogram:tonne': { 164 | formula: 'divide the mass value by 1000', 165 | calculation(n) { 166 | return n / 1000; 167 | }, 168 | }, 169 | 'kilogram:gram': { 170 | formula: 'multiply the mass value by 1000', 171 | calculation(n) { 172 | return n * 1000; 173 | }, 174 | }, 175 | 'gram:tonne': { 176 | formula: 'divide the mass value by 1e+6', 177 | calculation(n) { 178 | return n / new Number('1e+6'); 179 | }, 180 | }, 181 | 'gram:kilogram': { 182 | formula: 'divide the length value by 1000', 183 | calculation(n) { 184 | return n / 1000; 185 | }, 186 | }, 187 | }, 188 | }, 189 | length: { 190 | name: 'Length', 191 | units: { 192 | kilometer: 'Kilometer', 193 | meter: 'Meter', 194 | centimeter: 'Centimeter', 195 | }, 196 | variants: { 197 | 'kilometer:meter': { 198 | formula: 'multiply the length value by 1000', 199 | calculation(n) { 200 | return n * 1000; 201 | }, 202 | }, 203 | 'kilometer:centimeter': { 204 | formula: 'multiply the length value by 100000', 205 | calculation(n) { 206 | return n / 100000; 207 | }, 208 | }, 209 | 'meter:kilometer': { 210 | formula: 'divide the length value by 1000', 211 | calculation(n) { 212 | return n / 1000; 213 | }, 214 | }, 215 | 'meter:centimeter': { 216 | formula: 'multiply the length value by 100', 217 | calculation(n) { 218 | return n * 100; 219 | }, 220 | }, 221 | 'centimeter:kilometer': { 222 | formula: 'divide the length value by 100000', 223 | calculation(n) { 224 | return n / 100000; 225 | }, 226 | }, 227 | 'centimeter:meter': { 228 | formula: 'divide the length value by 100', 229 | calculation(n) { 230 | return n / 100; 231 | }, 232 | }, 233 | }, 234 | }, 235 | time: { 236 | name: 'Time', 237 | units: { 238 | second: 'Second', 239 | minute: 'Minute', 240 | hour: 'Hour', 241 | }, 242 | variants: { 243 | 'second:minute': { 244 | formula: 'divide the time value by 60', 245 | calculation(n) { 246 | return n / 60; 247 | }, 248 | }, 249 | 'second:hour': { 250 | formula: 'divide the time value by 3600', 251 | calculation(n) { 252 | return n / 3600; 253 | }, 254 | }, 255 | 'minute:second': { 256 | formula: 'multiply the time value by 60', 257 | calculation(n) { 258 | return n * 60; 259 | }, 260 | }, 261 | 'minute:hour': { 262 | formula: 'divide the time value by 60', 263 | calculation(n) { 264 | return n / 60; 265 | }, 266 | }, 267 | 'hour:second': { 268 | formula: 'multiply the time value by 3600', 269 | calculation(n) { 270 | return n * 3600; 271 | }, 272 | }, 273 | 'hour:minute': { 274 | formula: 'multiply the time value by 60', 275 | calculation(n) { 276 | return n * 60; 277 | }, 278 | }, 279 | }, 280 | }, 281 | }; 282 | 283 | let lastLeftSelectedValue = ''; 284 | let lastRightSelectedValue = ''; 285 | 286 | function main() { 287 | const categorySelect = document.getElementById('category-select'); 288 | const leftSelect = document.getElementById('left-select'); 289 | const rightSelect = document.getElementById('right-select'); 290 | const leftInput = document.getElementById('left-inp'); 291 | const rightInput = document.getElementById('right-inp'); 292 | 293 | const converterKeys = Object.keys(converter).sort(); 294 | removeAllChild(categorySelect); 295 | converterKeys.forEach((item) => { 296 | addOption(categorySelect, { value: item, text: converter[item].name }); 297 | }); 298 | 299 | // set default category units 300 | updateCategoryChanges(categorySelect, leftSelect, rightSelect); 301 | 302 | categorySelect.addEventListener('change', function () { 303 | updateCategoryChanges(categorySelect, leftSelect, rightSelect); 304 | }); 305 | 306 | leftInput.addEventListener('keyup', function (event) { 307 | if (event.target.value && !isNaN(event.target.value)) { 308 | const converterName = categorySelect.value; 309 | const variants = converter[converterName].variants; 310 | const variantKey = `${leftSelect.value}:${rightSelect.value}`; 311 | const variant = variants[variantKey]; 312 | leftInput.value = Number(event.target.value); 313 | rightInput.value = variant.calculation(Number(event.target.value)); 314 | } else { 315 | rightInput.value = ''; 316 | } 317 | }); 318 | 319 | rightInput.addEventListener('keyup', function (event) { 320 | if (event.target.value && !isNaN(event.target.value)) { 321 | const converterName = categorySelect.value; 322 | const variants = converter[converterName].variants; 323 | const variantKey = `${leftSelect.value}:${rightSelect.value}`; 324 | const variant = variants[variantKey]; 325 | rightInput.value = Number(event.target.value); 326 | leftInput.value = variant.calculation(Number(event.target.value)); 327 | } else { 328 | leftInput.value = ''; 329 | } 330 | }); 331 | 332 | leftSelect.addEventListener('change', function (event) { 333 | if (event.target.value === rightSelect.value) { 334 | const options = rightSelect.getElementsByTagName('option'); 335 | for (let i = 0; i < options.length; i++) { 336 | if (lastLeftSelectedValue === options[i].value) { 337 | options[i].selected = 'selected'; 338 | lastRightSelectedValue = options[i].value; 339 | break; 340 | } 341 | } 342 | } 343 | lastLeftSelectedValue = event.target.value; 344 | calculateValue(categorySelect, leftSelect, rightSelect); 345 | }); 346 | 347 | rightSelect.addEventListener('change', function (event) { 348 | if (event.target.value === leftSelect.value) { 349 | const options = leftSelect.getElementsByTagName('option'); 350 | for (let i = 0; i < options.length; i++) { 351 | if (lastRightSelectedValue === options[i].value) { 352 | options[i].selected = 'selected'; 353 | lastLeftSelectedValue = options[i].value; 354 | break; 355 | } 356 | } 357 | } 358 | lastRightSelectedValue = event.target.value; 359 | calculateValue(categorySelect, leftSelect, rightSelect); 360 | }); 361 | } 362 | 363 | function addOption(parent, option) { 364 | const opt = document.createElement('option'); 365 | opt.setAttribute('value', option.value); 366 | opt.innerText = option.text; 367 | 368 | parent.appendChild(opt); 369 | } 370 | 371 | function removeAllChild(parent) { 372 | while (parent.firstChild) { 373 | parent.firstChild.remove(); 374 | } 375 | } 376 | 377 | function updateCategoryChanges(categorySelect, leftSelect, rightSelect) { 378 | const converterName = categorySelect.value; 379 | const units = converter[converterName].units; 380 | const options = Object.keys(units); 381 | 382 | // handle left select 383 | removeAllChild(leftSelect); 384 | options.forEach((item) => { 385 | addOption(leftSelect, { value: item, text: units[item] }); 386 | }); 387 | lastLeftSelectedValue = leftSelect.value; 388 | 389 | // handle right select 390 | removeAllChild(rightSelect); 391 | options.forEach((item) => { 392 | addOption(rightSelect, { value: item, text: units[item] }); 393 | }); 394 | 395 | // change default option of right select 396 | rightSelect.getElementsByTagName('option')[1].selected = 'selected'; 397 | lastRightSelectedValue = rightSelect.value; 398 | 399 | calculateValue(categorySelect, leftSelect, rightSelect); 400 | } 401 | 402 | function calculateValue(categorySelect, leftSelect, rightSelect) { 403 | const leftInput = document.getElementById('left-inp'); 404 | const rightInput = document.getElementById('right-inp'); 405 | const formulaText = document.getElementById('formula-text'); 406 | 407 | const converterName = categorySelect.value; 408 | const variants = converter[converterName].variants; 409 | const variantKey = `${leftSelect.value}:${rightSelect.value}`; 410 | const variant = variants[variantKey]; 411 | formulaText.innerText = variant.formula; 412 | leftInput.value = 1; 413 | rightInput.value = variant.calculation(1); 414 | } 415 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Multiple Converter 8 | 9 | 10 | 11 | 12 |
13 |

Multiple Converter

14 |
15 |
16 | 19 | 24 |
25 |
26 |
27 | 28 | 33 |
34 |
=
35 |
36 | 37 | 42 |
43 |
44 |
45 |

Formula

46 |

47 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Non 48 | eum, doloremque, ipsum ad ipsam sed fugit, eligendi dolor 49 | veritatis distinctio quo eius recusandae. Sapiente, non 50 | perspiciatis! Odit doloremque eligendi ipsa. 51 |

52 |
53 |
54 | 55 | 56 | -------------------------------------------------------------------------------- /requirements.md: -------------------------------------------------------------------------------- 1 | # Multiple Converter Vanilla JS DOM Project 2 | 3 | **Requirements:** 4 | 5 | - multiple category of converter. Ex. area, weight, height 6 | - each category can have multiple units. formula of variant - n \* (n-1) 7 | - swap selected option if select in both side 8 | - display the actual formula 9 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | *, 2 | *::before, 3 | *::after { 4 | margin: 0; 5 | padding: 0; 6 | box-sizing: border-box; 7 | } 8 | 9 | body { 10 | font-family: Arial, Helvetica, sans-serif; 11 | font-size: 16px; 12 | width: 100vw; 13 | min-height: 100vh; 14 | background-color: #dddeee; 15 | } 16 | 17 | .container { 18 | width: 600px; 19 | margin: 2rem auto; 20 | padding: 2rem; 21 | background-color: #fff; 22 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2); 23 | border-radius: 0.25rem; 24 | } 25 | 26 | .header-title { 27 | font-size: 22px; 28 | font-weight: 500; 29 | color: #222; 30 | text-align: center; 31 | } 32 | 33 | .btn { 34 | padding: 0.5rem 1rem; 35 | outline: none; 36 | border: none; 37 | border-radius: 0.25rem; 38 | background-color: #000; 39 | color: #fff; 40 | cursor: pointer; 41 | font-size: 0.9rem; 42 | } 43 | 44 | .divider { 45 | width: 100%; 46 | height: 1px; 47 | margin-top: 0.3rem; 48 | background-color: #dddeee; 49 | } 50 | 51 | .text-input, 52 | .text-input:disabled { 53 | width: 100%; 54 | background: transparent; 55 | outline: none; 56 | border: 1px solid #dddeee; 57 | border-radius: 0.15rem; 58 | padding: 0.5rem 1rem; 59 | margin-top: 0.25rem; 60 | } 61 | 62 | .text-input:focus { 63 | border: 1px solid #ff6733; 64 | } 65 | 66 | .lead { 67 | font-weight: 600; 68 | font-size: 0.9rem; 69 | color: #333333; 70 | display: block; 71 | user-select: none; 72 | } 73 | 74 | label { 75 | user-select: none; 76 | } 77 | 78 | .category-wrap { 79 | margin-top: 2rem; 80 | } 81 | 82 | .input-group-wrap { 83 | display: flex; 84 | gap: 1rem; 85 | align-items: center; 86 | margin-top: 1rem; 87 | } 88 | 89 | .equal { 90 | font-size: 2rem; 91 | } 92 | 93 | .formula-wrap { 94 | margin-top: 2rem; 95 | background-color: #dddeee; 96 | padding: 1rem; 97 | border-radius: 0.25rem; 98 | } 99 | 100 | .formula-wrap h3 { 101 | color: #333333; 102 | font-size: 1.1rem; 103 | font-weight: 500; 104 | } 105 | 106 | .formula-wrap p { 107 | font-size: 0.9rem; 108 | color: #666; 109 | margin-top: 0.5rem; 110 | line-height: 1.3; 111 | } 112 | --------------------------------------------------------------------------------