Skip to content

Commit

Permalink
to converter.js added appendto method, that appends converter by sele…
Browse files Browse the repository at this point in the history
…ctor. now html code reduced a lot. this method allows to create a lot of different converters on page, without knowing how it works. also modified convert and tohtmllist methods due to big.js library functions. added support of nanometer. precisionrange.js and main.js were filled with function to create converter items and handle their work
  • Loading branch information
mhevyk committed Aug 5, 2022
1 parent a01fa53 commit 98d040c
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 130 deletions.
1 change: 1 addition & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

<script src="libs/fontawesome.js"></script>
<script src="libs/jquerymin.js"></script>
<script src="libs/big.js"></script>

<script src="js/main.js" defer></script>
<script src="js/propotypeExtension.js"></script>
Expand Down
177 changes: 65 additions & 112 deletions js/converter.js
Original file line number Diff line number Diff line change
@@ -1,40 +1,40 @@
class Converter{
static measurementUnitGroups = {
getById: function(id){
return this.list.find(unit => unit.id === id);
},
area: {
list: [
{id: "mcm2", value: 1e12, names: {short: "mcm&sup2", full: "квадратний мікрометр"}, group: "Метрична система"},
{id: "mm2", value: 1000000, names: {short: "mm&sup2", full: "квадратний міліметр"}, group: "Метрична система"},
{id: "sm2", value: 10000, names: {short: "sm&sup2", full: "квадратний сантиметр"}, group: "Метрична система"},
{id: "dm2", value: 100, names: {short: "dm&sup2", full: "квадратний дециметр"}, group: "Метрична система"},
{id: "m2", value: 1, names: {short: "m&sup2", full: "квадратний метр"}, group: "Метрична система"},
{id: "ar", value: 0.01, names: {short: "a", full: "ар/сотка"}, group: "Метрична система"},
{id: "ha", value: 0.0001, names: {short: "ha", full: "гектар"}, group: "Метрична система"},
{id: "km2", value: 0.000001, names: {short: "km&sup2", full: "квадратний кілометр"}, group: "Метрична система"},

{id: "township", value: 1.072506e-8, names: {short: "twsp", full: "тауншип"}, group: "Англо-американські одиниці"},
{id: "mi2", value: 3.861022e-7, names: {short: "mi&sup2", full: "квадратна миля"}, group: "Англо-американські одиниці"},
{id: "homestead", value: 0.000001544409, names: {short: "hmst", full: "хоумстед"}, group: "Англо-американські одиниці"},
{id: "acre", value: 0.0002471055, names: {short: "acre", full: "акр"}, group: "Англо-американські одиниці"},
{id: "rod", value: 0.0009884220, names: {short: "rod", full: "род"}, group: "Англо-американські одиниці"},
{id: "rod2", value: 0.03953687, names: {short: "rod&sup2", full: "квадратний род"}, group: "Англо-американські одиниці"},
{id: "yd2", value: 1.195990, names: {short: "yd&sup2", full: "квадратний ярд"}, group: "Англо-американські одиниці"},
{id: "ft2", value: 10.76391, names: {short: "ft&sup2", full: "квадратний фут"}, group: "Англо-американські одиниці"},
{id: "in2", value: 1550, names: {short: "in&sup2", full: "квадратний дюйм"}, group: "Англо-американські одиниці"}
{id: "nm2", value: 1e18, names: {short: "nm&sup2", full: "square nanometer"}, group: "Metric system"},
{id: "mcm2", value: 1e12, names: {short: "mcm&sup2", full: "square micrometer"}, group: "Metric system"},
{id: "mm2", value: 1000000, names: {short: "mm&sup2", full: "square millimeter"}, group: "Metric system"},
{id: "sm2", value: 10000, names: {short: "cm&sup2", full: "square centimeter"}, group: "Metric system"},
{id: "dm2", value: 100, names: {short: "dm&sup2", full: "square decimeter"}, group: "Metric system"},
{id: "m2", value: 1, names: {short: "m&sup2", full: "square meter"}, group: "Metric system"},
{id: "ar", value: 0.01, names: {short: "a", full: "ar/hectare"}, group: "Metric system"},
{id: "ha", value: 0.0001, names: {short: "ha", full: "hectare"}, group: "Metric system"},
{id: "km2", value: 0.000001, names: {short: "km&sup2", full: "square kilometer"}, group: "Metric system"},

{id: "township", value: 1.072506e-8, names: {short: "twsp", full: "township"}, group: "Anglo-american units"},
{id: "mi2", value: 3.861022e-7, names: {short: "mi&sup2", full: "square mile"}, group: "Anglo-american units"},
{id: "homestead", value: 0.000001544409, names: {short: "hmst", full: "homestead"}, group: "Anglo-american units"},
{id: "acre", value: 0.0002471055, names: {short: "acre", full: "acre"}, group: "Anglo-american units"},
{id: "rod", value: 0.0009884220, names: {short: "rod", full: "rod"}, group: "Anglo-american units"},
{id: "rod2", value: 0.03953687, names: {short: "rod&sup2", full: "square rod"}, group: "Anglo-american units"},
{id: "yd2", value: 1.195990, names: {short: "yd&sup2", full: "square yard"}, group: "Anglo-american units"},
{id: "ft2", value: 10.76391, names: {short: "ft&sup2", full: "square foot"}, group: "Anglo-american units"},
{id: "in2", value: 1550, names: {short: "in&sup2", full: "square inch"}, group: "Anglo-american units"}
],
},
length:{
list: []
}
};
constructor(props){
constructor(props = {}){
//unit of measurement group
this.type = props.type || "area";
//object, that contains all units of measurement from current group (for example if type is 'area', it contains 'm2', 'mm2' etc.)
this.dataByType = Converter.measurementUnitGroups[this.type];
this.appendTo(props.selector || "body");
if(props.selector){
this.appendTo(props.selector);
}
}
appendTo(selector){
//avoid multiple appending one converter to page
Expand All @@ -49,84 +49,32 @@ class Converter{

//title and select for "from" unit of measurement
const fromTitle = createConverterTitle(`Convert ${this.type} from:`);
const fromSelect = createConverterSelect({
role: "from",
type: this.type,
defaultOptionText: "Choose unit of measurement...",
disableDefaultOption: true
});
const fromSelect = createFromConverterSelect(this.type);

//title and select for "to" unit of measurement
const toTitle = createConverterTitle("to:");
const toSelect = createConverterSelect({
role: "to",
type: this.type,
defaultOptionText: "All measurement units",
});

//fill selects with same options using this.dataByType.list objects list
const groups = getUniqueElements(this.dataByType.list.map(a => a.group));
const optgroups = [];
for(let group of groups){
const groupContent = this.dataByType.list.filter(a => a.group === group);
optgroups.push(
`<optgroup label="${group}">
${groupContent.map(a => `<option value="${a.id}">${a.names.full} (${a.names.short})</option>`).join("")}
</optgroup>`
)
}
fromSelect.innerHTML += optgroups.join("");
toSelect.innerHTML += optgroups.join("");
const toSelect = createToConverterSelect(this.type);

//link, that opens more options
const toggleLink = createContainerWithClasses("div", "converter-more-header");
toggleLink.innerHTML = `<span>Click here for more options...</span>`;
toggleLink.querySelector("span").onclick = event => $(event.target.parentNode.nextElementSibling).slideToggle();
const optgroups = createOptgroups(this.dataByType.list);
fromSelect.innerHTML += optgroups;
toSelect.innerHTML += optgroups;

const precisionRange = createConverterPrecisionRange(this.type);

//contents, that toggles due to toggle link
const toggleContent = createContainerWithClasses("div", "converter-more-content");
toggleContent.appendChild(precisionRange);
//link, that opens more options
const moreToggle = createConverterMoreToggle(precisionRange);

const result = createContainerWithClasses("div", "converter-result");

//reads value from input, from and to units of measurements from selects and precision from range and passes it to converter, that prints result of error
const startConverting = () => {
try{
const value = parseFloat(valueInput.value);
if(!value){
throw new Error(`Invalid input! Type ${this.type} value again!`);
}
const from = getSelectedOption(fromSelect);
if(!from){
throw new Error("You forgot to choose <span class='primary-text'>from</span> unit of measurement!");
}

const to = getSelectedOption(toSelect);

const precision = parseInt(precisionRange.querySelector("input").value);

//list of objects, that contain converted values
const converted = this.convert({value, from, to});
//get short name of selected "from" value
const fromShortName = this.getById(from).names.short;
//list of options containing all converted units of measurement
const liList = this.toHtml({converted, precision});

const resultHeader = `<div class="result-header">${value} ${fromShortName} is:</div>`;
const resultContent = `<ul class="result-list">${liList}</ul>`;
result.innerHTML = resultHeader + resultContent;
}
catch(error){
result.innerHTML = error.message;
}
};
const startConverting = converterHandler.bind(this, {valueInput, fromSelect, toSelect, precisionRange, result});

//limit length of field
//limit length of value input field
valueInput.oninput = event => {
//max symbols limit
const LIMIT = 10;
const value = event.target.value;
if(value.length > 9){
if(value.length >= LIMIT){
event.target.value = value.slice(0, -1);
}
else startConverting();
Expand All @@ -146,53 +94,58 @@ class Converter{
wrapper.appendChild(fromSelect);
wrapper.appendChild(toTitle);
wrapper.appendChild(toSelect);
wrapper.appendChild(toggleLink);
wrapper.appendChild(toggleContent);
wrapper.appendChild(moreToggle);
wrapper.appendChild(result);
document.querySelector(selector).appendChild(wrapper);
}
getById(id){
return Converter.measurementUnitGroups.getById.call(this.dataByType, id);
//created separate method, because it has to work with mupliple lists
getById(id, from){
return from.find(record => record.id === id);
}
convert(props){
console.log(`${props.value} ${props.from} to ${props.to || "all"}`);
convert(props = {}){
console.log(`Converting ${props.value} ${props.from} to ${props.to || "all"}`);
if(!props.value) return "Value is invalid!";

//value of unit, that we are converting from
const fromUnitOfMeasurement = this.getById(props.from);
//deep copy of array
const unitsOfMeasurementList = this.dataByType.list.createDeepCopy();

//copy array to avoid it`s change
const unitsOfMeasurementList = this.dataByType.list.slice();
//value of unit, that we are converting from
const fromUnitOfMeasurement = this.getById(props.from, unitsOfMeasurementList);
if(!fromUnitOfMeasurement) return "From value is invalid!";

/*it is special coefitient, that makes first (props.from) unit of measurement value equal 1 (because a * (1/a) = 1)
* other units of measurement, multiplied by this value become value of conversion from 1 "from-unit" to 1 "to-unit"
* for example we want to convert km2 to m2. By default m2 is reset (because it is equal 1)
* firstly, we get km2 value, it is 0.000001. Then we multiply all by 1/0.000001. km becomes 1 (reset), m2 becomes 1000000.
* Now we know, that 1 km2 is 1000000 m2
*/
let reduceFraction = 1 / fromUnitOfMeasurement.value;
let reduceFraction = new Big(1 / fromUnitOfMeasurement.value);
//If we want to have (props.value) km2, we multiply it by reduceFraction
const convertedValue = props.value * reduceFraction;
const convertedValue = reduceFraction.times(props.value);

for(const unit of unitsOfMeasurementList){
unit.value *= convertedValue;
}
//multiply unit value ny converted value
unitsOfMeasurementList.forEach(unit => unit.value = convertedValue.times(unit.value));

const toUnitOfMeasurement = this.getById(props.to);
const toUnitOfMeasurement = this.getById(props.to, unitsOfMeasurementList);

return toUnitOfMeasurement || unitsOfMeasurementList;
}
toHtml(props){
console.log(props.converted)
const data = props.converted;
const toStandardForm = converted => converted.value.toStandardForm(props.precision);
const tableRow = record => {
toHtmlList(props = {}){
const converted = props.converted;
const liWrapper = record => {
if(record.error) throw record.error;
return `<li>${toStandardForm(record)}<span class="primary-text">${record.names.short}</span></li>`;
return `
<li>
${record.value.toStandardForm(props.precision)}
<span class="primary-text">${record.names.short}</span>
</li>`;
};

const liList = Array.isArray(converted)
? converted.map(liContent => liWrapper(liContent)).join("")
: liWrapper(converted);

return Array.isArray(props.converted)
? data.map(a => tableRow(a)).join("")
: tableRow(data);
return `<ul class="result-list">${liList}</ul>`;
}
}

Expand Down
98 changes: 82 additions & 16 deletions js/main.js
Original file line number Diff line number Diff line change
@@ -1,36 +1,102 @@
function getUniqueElements(array){
return Array.from(new Set(array));
}
function getSelectedOption(select){
return select.options[select.selectedIndex].value;
}
function createContainerWithClasses(tagName, ...classes){
const getSelectedOption = select => select.options[select.selectedIndex].value;

const createContainerWithClasses = (tagName, ...classes) => {
const container = document.createElement(tagName);
container.classList.add(...classes);
return container;
}
function createConverterTitle(title){
};

const createConverterTitle = title => {
const titleContainer = createContainerWithClasses("div", "converter-title");
titleContainer.textContent = title;
return titleContainer;
}
function createConverterSelect(props){
};

const createConverterSelect = props => {
const select = createContainerWithClasses("select", "converter-select", `converter-${props.role}`, "converter-action-item")
select.dataset.type = props.type;
select.innerHTML = `<option value="" selected ${props.disableDefaultOption ? "disabled" : ""}>${props.defaultOptionText}</option>`;
return select;
}
function createConverterValueInput(type){
};

const createFromConverterSelect = type => createConverterSelect({
role: "from",
type: type,
defaultOptionText: "Choose unit of measurement...",
disableDefaultOption: true
});

const createToConverterSelect = type => createConverterSelect({
role: "to",
type: type,
defaultOptionText: "All measurement units",
});

const createOptgroups = list => {
const groups = list.map(record => record.group).getUniqueElements();
const optgroups = groups.map(group => {
const groupContent = list.filter(record => record.group === group);
const optionsList = groupContent.map(record => `<option value="${record.id}">${record.names.full} (${record.names.short})</option>`);
return `<optgroup label="${group}">${optionsList.join("")}</optgroup>`;
});
return optgroups.join("");
};

const createConverterMoreToggle = toggleContent => {
const header = createContainerWithClasses("div", "converter-more-header");
header.innerHTML = `<span>Click here for more options...</span>`;
header.querySelector("span").onclick = event => $(event.target.parentNode.nextElementSibling).slideToggle();

const content = createContainerWithClasses("div", "converter-more-content");
content.appendChild(toggleContent);
const wrapper = createContainerWithClasses("div", "converter-more-wrapper");
wrapper.appendChild(header);
wrapper.appendChild(content);
return wrapper;
};

const createConverterValueInput = type => {
const input = createContainerWithClasses("input", "converter-value", "converter-action-item");
input.dataset.type = type;
input.type = "number";
input.value = 1;
input.placeholder = `Enter ${type}...`;
return input;
}
};

function converterHandler(props){
try{
const value = parseFloat(props.valueInput.value);
if(!value){
throw new Error(`Invalid input! Type ${this.type} value again!`);
}
const from = getSelectedOption(props.fromSelect);
if(!from){
throw new Error("You forgot to choose <span class='primary-text'>from</span> unit of measurement!");
}

const to = getSelectedOption(props.toSelect);

const precision = parseInt(props.precisionRange.querySelector("input").value);

//list of objects, that contain converted values
const converted = this.convert({value, from, to});
//get short name of selected "from" value
const fromShortName = this.getById(from, this.dataByType.list).names.short;

const resultHeader = `<div class="result-header">${value} ${fromShortName} is:</div>`;
const resultContent = this.toHtmlList({converted, precision});
props.result.innerHTML = resultHeader + resultContent;
}
catch(error){
props.result.innerHTML = error.message;
}
};

document.querySelector("#mobile-menu").addEventListener("click", () => {
const menu = $("#menu");
const mobileMenuArrow = $("#mobile-menu > .fas");
menu.slideToggle(500);
$("#mobile-menu > .fas").toggleClass("fa-angle-down");
$("#mobile-menu > .fas").toggleClass("fa-angle-up");
mobileMenuArrow.toggleClass("fa-angle-down");
mobileMenuArrow.toggleClass("fa-angle-up");
});
4 changes: 2 additions & 2 deletions js/precisionRange.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
function moveConverterRangeCaption(rangeWrapper){
const moveConverterRangeCaption = rangeWrapper => {
const range = rangeWrapper.querySelector("input");
const {min, max, value} = range;
const percentPad = Number((value - min) * 100 / (max - min));
Expand All @@ -7,7 +7,7 @@ function moveConverterRangeCaption(rangeWrapper){
rangeCaption.querySelector("span").textContent = value;
rangeCaption.style.left = `calc(${percentPad}% + (${pixelPad}px))`;
};
function createConverterPrecisionRange(type){
const createConverterPrecisionRange = type => {
const wrapper = createContainerWithClasses("div", "range-wrapper");
const precisionTitle = createConverterTitle("precision:");
const rangeCaption = createContainerWithClasses("div", "range-value-caption");
Expand Down

0 comments on commit 98d040c

Please sign in to comment.