I have a UI with x input boxes. I want the values in the boxes to increase by 1 from left to right. The functionality like in this plunk: https://plnkr.co/edit/82sNDb?p=preview .
const inputValue = element =>
Rx.Observable.fromEvent(element, 'input').map(e =>
parseInt(e.target.value, 10));
const box1$ = inputValue(box1);
const box2$ = inputValue(box2);
const box3$ = inputValue(box3);
box1$.subscribe((val) => {
box2.value = val + 1;
box3.value = val + 2;
});
box2$.subscribe((val) => {
box1.value = val - 1;
box3.value = val + 1;
});
box3$.subscribe((val) => {
box1.value = val - 2;
box2.value = val - 1;
});
Trying to scale that for more boxes seems hard though so I tried to chain them together instead; where change in one box propagates to the 'next' box.
I cant seem to do that in a none complicated way though and resorted to use subjects to push values to the streams. Is there a cleaner way to do this?
My chained implementation: https://plnkr.co/edit/tbM5Gh?p=preview
window.addEventListener('DOMContentLoaded', () => {
const box1 = document.querySelector('#box1');
const box2 = document.querySelector('#box2');
const box3 = document.querySelector('#box3');
const box1Subject = new Rx.Subject();
const box2Subject = new Rx.Subject();
const box3Subject = new Rx.Subject();
// link box1 -> box2
const box1$ = createBoxStream(box1)(box1Subject)(val => val + 1)
.subscribe((newValue) => {
box2.value = newValue;
box2Subject.next(newValue);
});
// link box2 -> box3
const box2$ = createBoxStream(box2)(box2Subject)(val => val + 1)
.subscribe((newValue) => {
box3.value = newValue;
box3Subject.next(newValue);
});
// link box3 -> box1
const box3$ = createBoxStream(box3)(box3Subject)(val => val - 2)
.subscribe((newValue) => {
box1.value = newValue;
box1Subject.next(newValue);
});
});
const createBoxStream = element => subject => projection => Rx.Observable
.fromEvent(element, 'input')
.map(e => parseInt(e.target.value, 10))
.merge(subject)
.map(projection)
.distinctUntilChanged();
I would go with slightly different approach, we could merge streams from all inputs and update input boxes accordingly. Such solution will work for any number of input boxes:
window.addEventListener('DOMContentLoaded', () => {
const boxesCount = 3;
const boxes = [];
let $allBoxesInput = null;
const keyUp = el => Rx.Observable.fromEvent(el, 'keyup');
for (let i = 0; i < boxesCount; i++) {
const el = document.querySelector(`#box${i}`);
boxes.push(el);
$allBoxesInput = $allBoxesInput ?
Rx.Observable.merge($allBoxesInput, keyUp(el)) : keyUp(el);
}
$allBoxesInput.distinctUntilChanged()
.subscribe(event => {
// we get all events here
const id = event.target.id;
const index = parseInt(id.substr(id.length - 1, id.length));
const value = event.target.value;
// update all at the right
for (let i = index + 1; i < boxes.length; i++) {
boxes[i].value = parseInt(boxes[i - 1].value) + 1;
}
});
});
I started ids with 0 to be more array index friendly:
<body>
<input type="text" id="box0">
<input type="text" id="box1">
<input type="text" id="box2">
</body>
Related
I have a problem with adding a function which will create an amount of boxes with rising size and when i try to add more I will start from the size of the previous last box.
So far i have a code like this:
function getRandomHexColor() {
return `#${Math.floor(Math.random() * 16777215).toString(16)}`;
}
const input = document.querySelector("#controls>input");
const createBtn = document.querySelector("button[data-create]");
const destroyBtn = document.querySelector("button[data-destroy]");
const boxCollection = document.querySelector("#boxes");
const createBoxes = (amount) => {
amount = input.value;
for (let i = 0; i < amount; i += 1) {
let newBox = document.createElement("div");
newBox.style.height = `${30 + 10 * i}px`;
newBox.style.width = `${30 + 10 * i}px`;
newBox.style.background = getRandomHexColor();
boxCollection.insertAdjacentElement("beforeend", newBox);
}
};
createBtn.addEventListener("click", createBoxes);
const destroyBoxes = () => {
boxCollection.innerHTML = "";
};
destroyBtn.addEventListener("click", destroyBoxes);
I was thinking about adding a new var which will be the lastChild of const boxCollection and add it to height and width of newBox. Am I thinking right?
You can either create a variable with the current number of boxes or you can just count the current number each time you add more. In the example the current number (currentCount) is based on the length of a querySelectorAll().
function getRandomHexColor() {
return `#${Math.floor(Math.random() * 16777215).toString(16)}`;
}
const boxCollection = document.querySelector("#boxes");
const createBoxes = e => {
e.preventDefault();
let amount = parseInt(e.target.amount.value);
let currentCount = boxCollection.querySelectorAll('div').length;
Object.keys([...Array(amount)]).forEach(i => {
let incVal = parseInt(i) + currentCount;
let newBox = document.createElement("div");
newBox.style.height = `${30 + 10 * incVal}px`;
newBox.style.width = `${30 + 10 * incVal}px`;
newBox.style.background = getRandomHexColor();
boxCollection.insertAdjacentElement("beforeend", newBox);
});
};
const destroyBoxes = e => {
e.preventDefault();
boxCollection.innerHTML = "";
};
document.forms.create.addEventListener("submit", createBoxes);
document.forms.destroy.addEventListener("submit", destroyBoxes);
<form name="create">
<input type="number" name="amount" />
<button>Create</button>
</form>
<form name="destroy">
<button>Destroy</button>
</form>
<div id="boxes"></div>
You could hold the latest size of the box in a var.
function getRandomHexColor() {
return `#${Math.floor(Math.random() * 16777215).toString(16)}`;
}
const input = document.querySelector("#controls>input");
const createBtn = document.querySelector("button[data-create]");
const destroyBtn = document.querySelector("button[data-destroy]");
const boxCollection = document.querySelector("#boxes");
let currentBoxSize = 30 // starting box size
const BOX_SIZE_INCREMENT = 10 // increment size
const createBoxes = (amount) => {
amount = input.value;
for (let i = 0; i < amount; i += 1) {
let newBox = document.createElement("div");
newBox.style.height = `${currentBoxSize}px`;
newBox.style.width = `${currentBoxSize}px`;
newBox.style.background = getRandomHexColor();
boxCollection.insertAdjacentElement("beforeend", newBox);
currentBoxSize += BOX_SIZE_INCREMENT
}
};
createBtn.addEventListener("click", createBoxes);
const destroyBoxes = () => {
boxCollection.innerHTML = "";
};
destroyBtn.addEventListener("click", destroyBoxes);
Just store the last input value in a variable, or if you want to preserve the state, you can store it in locale storage, and when you click create boxes, sum it with the value with the new input value.
function getRandomHexColor() {
return `#${Math.floor(Math.random() * 16777215).toString(16)}`;
}
const input = document.querySelector("#controls>input");
const createBtn = document.querySelector("button[data-create]");
const destroyBtn = document.querySelector("button[data-destroy]");
const boxCollection = document.querySelector("#boxes");
let lastInputValue = 0;
const createBoxes = (amount) => {
amount = input.value + lastInputValue;
for (let i = 0; i < amount; i += 1) {
let newBox = document.createElement("div");
newBox.style.height = `${30 + 10 * i}px`;
newBox.style.width = `${30 + 10 * i}px`;
newBox.style.background = getRandomHexColor();
boxCollection.insertAdjacentElement("beforeend", newBox);
}
lastInputValue = amount;
};
createBtn.addEventListener("click", createBoxes);
const destroyBoxes = () => {
boxCollection.innerHTML = "";
};
destroyBtn.addEventListener("click", destroyBoxes);
I am having a hard time trying to figure out how to get the the value from every new Li and reduce it (add) to then output to my h2. Can't figure out what I am doing wrong. Any help would be greatly appreciated! Codepen: https://codepen.io/Chasehud26/pen/Poagjwy
I tried to console.log different variables to see if there were any hints of what is going wrong.
const form = document.querySelector("form")
const nameInput = document.querySelector("#name-input")
const priceInput = document.querySelector("#price-input")
const button = document.querySelector("button")
const nameUl = document.querySelector("#item-name")
const priceUl = document.querySelector("#item-price")
const h2 = document.querySelector("h2")
const nameLi = document.createElement("li")
const priceLi = document.createElement("li")
form.addEventListener("submit", function (e) {
e.preventDefault()
let nameVal = nameInput.value
let priceVal = priceInput.value
const nameLi = document.createElement("li")
const priceLi = document.createElement("li")
nameUl.appendChild(nameLi)
nameLi.innerHTML = nameInput.value
priceUl.appendChild(priceLi)
priceLi.textContent = `${priceInput.value}`
showTotals()
})
//TRYING TO ADD TOGETHER ALL THE PRICE VALUES AND THEN PUT IT TO MY H2//
function showTotals() {
const priceList = document.querySelectorAll("li")
for (let priceLists of priceList) {
const total = []
total.push(parseFloat(priceLists.textContent));
const totalMoney = total.reduce(function (total, item) {
total += item;
return total;
}, 0);
const finalMoney = totalMoney.toFixed(2);
h2.textContent = finalMoney;
}
}
You need to have your const total [] array initialized outside of the for loop. also, when you setup your <li> decorators, you need to differentiate between the number and non-number fields, since the way you had it, it was trying to add the text 'li' fields also:
/// truncated for clarity
const nameLi = document.createElement("li")
const priceLi = document.createElement("li")
priceLi.classList.add('num') // <== this line added
//// =================
function showTotals() {
const priceList = document.querySelectorAll("li.num") // added class
const total = [] // <== move this to here
for (let priceLists of priceList) {
total.push(parseFloat(priceLists.textContent));
const totalMoney = total.reduce(function (total, item) {
total += item;
return total;
}, 0);
const finalMoney = totalMoney.toFixed(2);
h2.textContent = finalMoney;
}
I'm having trouble implementing the logic that will limit me from adding the same items to my shopping list. When the item is the same, I just want to display the quantity with the existing item.
<div class="pizzas">
</div>
<div class="shoppingCart">
<p class="totalPrice">Hungry? order our pizzas</p>
</div>
// js
fetch("https://raw.githubusercontent.com/alexsimkovich/patronage/main/api/data.json")
.then(data => data.json())
.then(data => {
let valueCurrency = 0;
data.forEach(element => {
const shoppingCart = document.querySelector(".shoppingCart");
const pizzas = document.querySelector(".pizzas");
const box = document.createElement("div");
const img = document.createElement("img");
const title = document.createElement("h3");
const ingredients = document.createElement("p");
const price = document.createElement("h4");
const btn = document.createElement("button");
const totalPrice = document.querySelector(".totalPrice");
box.className = "box";
ingredients.className = "ingredients"
btn.className = "btn";
img.src = element.image;
img.className = "img";
title.innerHTML = element.title;
ingredients.innerHTML = element.ingredients;
price.innerHTML = element.price.toFixed(2) + " zł";
btn.innerHTML = "Dodaj do koszyka";
box.appendChild(img);
box.appendChild(title);
box.appendChild(ingredients);
box.appendChild(price);
box.appendChild(btn);
pizzas.appendChild(box);
btn.addEventListener("click", (e) => {
valueCurrency = valueCurrency + element.price;
const pizza = document.createElement("div");
pizza.className = "pizzaList";
const pizzasList = document.createElement("li");
const pizzaPrice = document.createElement("p");
const btnRemove = document.createElement("button");
btnRemove.innerText = "X";
pizzasList.innerText = title.textContent;
pizzaPrice.innerText = price.textContent;
pizza.appendChild(pizzasList);
pizza.appendChild(pizzaPrice);
pizza.appendChild(btnRemove);
totalPrice.innerText = "Całkowita cena: " + valueCurrency.toFixed(2);
if(pizzasList.innerText === pizzasList.innerText)
{
// don't add another item to the list
// just add +1 to existing element
}
else
{
// add an item to the list
shoppingCart.prepend(pizza);
}
btnRemove.addEventListener("click", (e) => {
pizza.remove();
valueCurrency = valueCurrency - element.price;
totalPrice.innerText = "Całkowita cena: " + valueCurrency.toFixed(2);
})
})
});
})
.catch(err => console.log(err));
My problem is exactly in the conditional statement, I don't know exactly how to implement the counting of the same pizzas option.
Thank you in advance for your help.
Since you are using html elements for this, what you can do is to use a data-attribute in your pizza element and increment it each time you need.
Something like:
if(pizzasList === pizzasList)
{
pizza.dataset.total = Number(pizza.dataset.total) + 1;
}
else
{
pizza.dataset.total = 1;
shoppingCart.prepend(pizza);
}
Then just use pizza.dataset.total to retieve the total number of repetitions.
The logic seems sound but my ul is not displaying what I am asking it to. I have used console.logs and I am for sure getting poem in the function displayPoem(poem) but it isn't showing up when I button click. Any help would be greatly appreciated!
const inputsList = document.querySelector('ol');
const poemsList = document.getElementById('savedThoughts');
const form = document.getElementById('')
const submitButton = document.getElementById('submitThoughts');
const startButton = document.querySelector('#startButton')
startButton.onclick = () => {
const ranNum = generateRanNum();
generateInputs(ranNum)
changeToRestartText()
}
submitButton.onclick = () => {
const poem = savePoem();
console.log(poem)
displayPoem(poem);
clearForm()
}
const generateRanNum = () => {
let randomNumber = Math.floor(Math.random() * 20);
return randomNumber
}
const changeToRestartText = () => {
startButton.textContent = 'Restart Game'
}
const generateInputs = (ranNum) => {
const listItem = document.createElement('li');
for(let i = 1; i <= ranNum; i++){
const input = document.createElement('input');
listItem.appendChild(input).setAttribute('type', 'text');
console.log(ranNum)
}
inputsList.appendChild(listItem);
}
const savePoem = () => {
let poemArr = [];
const input = document.querySelectorAll('input');
input.forEach(element => {
poemArr.push(element.value);
})
// console.log(poemArr)
return poemArr;
}
const displayPoem = (poem) => {
const savedPoem = document.createElement('li')
const savedText = document.createElement('span')
const deletePoem = document.createElement('button')
console.log(poem)
savedPoem.appendChild(savedText);
savedText.textContent = poem.toString();
savedPoem.appendChild(deletePoem);
deletePoem.textContent = 'Delete';
poemsList.appendChild(savedPoem)
deletePoem.onclick = e => {
poemsList.removeChild(savedPoem);
}
}
const clearForm = () => {
const inputLi = document.querySelectorAll('li');
inputLi.forEach(element => {
element.remove()
})
}
small html segment
<div >
<ul id="savedThoughts">
</ul>
</div>
Your saved list items aren't showing up because your submit onclick calls displayPoem which creates list items and then calls clearForm which removes all list items on the page. Try inputLi = document.querySelectorAll('ol > li').
I set up two events, one in feet and one in inches. I'm trying to grab the value of each event which would be feet and inches but I can't because of each event's scope. Is there a way to pass both values into my totalHeight function so that I can add the two values together?
const justFeet = totalFeet.addEventListener('input', (e) => {
const heightInFeet = e.target.value;
let displayFeet = document.createElement('h3')
displayFeet.textContent = heightInFeet * 12
// totalInches.appendChild(displayFeet)
})
const justInches = inches.addEventListener('input', (e) => {
const addOnInches = e.target.value;
let displayInches = document.createElement('h3')
displayInches.textContent = addOnInches
// totalInches.appendChild(displayInches)
})
function totalHeight (feet, inches) {
const finalTotal = feet + inches;
let finalHeight = document.createElement('h3')
finalHeight.textContent = finalTotal
totalInches.appendChild(finalHeight)
}
totalHeight(displayFeet, displayInches)
An example of what it looks like you are trying to do. There's more you need to do, for example I used integers below but you could use floating numbers and perhaps add better handling for isNaN().
<html>
<style>
</style>
Feet:<input id="feet" type="number"></input>
Inches:<input id="inches" type="number"></input>
<h3>Feet converted to inches: <span id="displayFeetToInches"></span></h3>
<h3>Inches: <span id="displayInches"></span></h3>
<h3>Total inches:<span id="finalHeight"></span></h3>
<script>
const feet = document.getElementById("feet");
const inches = document.getElementById("inches");
const total = document.getElementById("total"); //in inches
const displayFeetToInches = document.getElementById("displayFeetToInches"); //in inches
const displayInches = document.getElementById("displayInches"); //in inches
const justFeet = feet.addEventListener('input', (e) => {
console.log('justFeet');
const heightInFeet = e.target.value;
displayFeetToInches.textContent = heightInFeet * 12;
totalHeight();
})
const justInches = inches.addEventListener('input', (e) => {
console.log('justInches');
const addOnInches = e.target.value;
displayInches.textContent = addOnInches
totalHeight();
})
function totalHeight (feet, inches) {
console.log('totalinches');
const ftToInches = displayFeetToInches.textContent;
const addOn = displayInches.textContent;
const finalTotal = parseInt(ftToInches) + parseInt(addOn);
finalHeight.textContent = finalTotal
}
</script>
</html>