Как лучше реализовать на JS кнопку заказа, по которой щелкаешь, она исчезает и появляется блок с кнопками плюс, минус и инпут с числом(как в Ozon)
Это все что я пока придумала. Еще нужно, чтобы при нажатии на минус, если число меньше одного, блок quantity исчезал и вновь появлялась кнопка order.
Изначально я просто делала все скрытием блока (display='none')
Но в Ozone я так поняла кнопки исчезают из DOM, а не просто скрываются
const btns = document.querySelectorAll(".f1");
btns.forEach((el) => {
el.addEventListener("click", (e) => {
let target = e.currentTarget;
let btnOrder = target.querySelector('button');
if (btnOrder) {
btnOrder.remove();
const blockQuantity = document.createElement("div");
const content = `<div class="quantity">
<button class="minus">-</button>
<input type="text" class="input" value="1">
<button class="plus">+</button></div>`;
blockQuantity.innerHTML = content;
target.append(blockQuantity);
let plus = target.getElementsByClassName("plus");
let minus = target.getElementsByClassName("minus");
let result = target.getElementsByClassName("input");
Array.prototype.forEach.call(plus, function(element) {
let plus = element;
plus.addEventListener("click", (e) => {
let span = plus.previousElementSibling;
console.log(span);
let oldValue = Number(span.value);
let newVal = oldValue + 1;
span.setAttribute("value", newVal);
});
});
Array.prototype.forEach.call(minus, function(element) {
let minus = element;
minus.addEventListener("click", (e) => {
let span = minus.nextElementSibling;
let oldValue = Number(span.value);
let newVal = oldValue - 1;
span.setAttribute("value", newVal);
if (newVal === 0) {
blockQuantity.remove();
}
});
});
}
});
});
<div class="f1">
<button class="btn btn-order">order</button>
</div>
<div class="f1">
<button class="btn btn-order">order</button>
</div>
<div class="f1">
<button class="btn btn-order">order</button>
</div>
Ответы (3 шт):
Я бы это реализовал следующим образом:
const defaultOrderBtn = () => { // Разметка кнопки "order", возвращается через функцию
const btn = document.createElement('button') // создаём кнопку
btn.className = ['btn', 'btn-order'] // добавляем ей стили
btn.textContent = 'order' // добавляем ей текст
btn.addEventListener('click', () => orderHandler(btn)) // добавляем ей обработчик
return btn // возвращаем кнопку
}
const quantityButtons = () => { // Разметка кнопок с изменением кол-ва продуктов
const parent = document.createElement('div') // создаём обёртку
parent.className = ['quantity'] // добавляем класс
const counter = document.createElement('input') // создаём "счётчик" товара
counter.type = 'number' // назначаем тип
counter.readOnly = true // запрещаем редактирование (если разрешить, нужно будет доработать)
counter.value = 1 // Устанавливаем значение по умолчанию
const minus = document.createElement('input') // создаём инпут "-"
minus.type = 'button' // он будет кнопкой
minus.value = '-'
minus.addEventListener('click', () => minusHandler(parent, counter)) // вешаем обработчик, в него передаём обёртку и наш "счётчик"
const plus = document.createElement('input') // Тут аналогично, но с "+"
plus.type = 'button'
plus.value = '+'
plus.addEventListener('click', () => plusHandler(parent, counter))
// Добавляем все кнопки в обёртку
parent.append(minus)
parent.append(counter)
parent.append(plus)
return parent // возвращаем обёртку
}
// Обработчик кнопки "order"
const orderHandler = (button) => {
button.parentNode.replaceChild(quantityButtons(), button) // при нажатие, заменяем кнопку "order" кнопками изменения кол-ва товаров
}
// Обработчик кнопки "-"
const minusHandler = (parent, counter) => {
if(+counter.value - 1 !== 0) { // Если при уменьшение кол-ва товаров в "счётчике" не 0
counter.value = +counter.value - 1 // сохраняем изменение
} else { // Если же 0
parent.parentNode.replaceChild(defaultOrderBtn(), parent) // То меняем кнопки на "order"
}
}
// Обработчик "+"
const plusHandler = (parent, counter) => {
counter.value = +counter.value + 1 // тут просто прибавляем +1
}
// Сразу всем существующим кнопкам повешаем обработчик, чтобы менять "order" на кнопки с изменением кол-ва
document.querySelectorAll('.btn-order').forEach(elem => {
elem.addEventListener('click', () => orderHandler(elem))
})
<div class="f1">
<button class="btn btn-order">order</button>
</div>
<div class="f1">
<button class="btn btn-order">order</button>
</div>
<div class="f1">
<button class="btn btn-order">order</button>
</div>
Комментарии в коде.
P.s.
Встаёт только вопрос, а как быть, если кол-во товаров из базы пришло и там не 0, следовательно, надо отобразить кнопки с изменением кол-ва и отобразить кол-во товаров в "счётчике".
Но это уже другая история.
Я попытался сохранить Ваш код в изначальном виде насколько это возможно, скажу сразу у Вас было много лишних моментов (я их просто заккоментировал чтобы у Вас была возможность оценить количество лишних строк) В целом еще нужно немного поизучать основы. Даже этот код можно сделать компактнее и красивее (см. Дополнение к ответу).
const btns = document.querySelectorAll(".f1");
btns.forEach((el) => {
el.addEventListener("click", (e) => {
let target = e.target;
//let btnOrder = target.querySelector('button');
let btnOrder = el.querySelector('.btn-order'); // + добавлено
if (target === btnOrder) {
btnOrder.remove();
//document.createElement("div");
const blockQuantity = el // +
const content = `<div class="quantity">
<button class="minus">-</button>
<input type="text" class="input" value="1">
<button class="plus">+</button></div>`;
blockQuantity.innerHTML = content;
//target.append(blockQuantity);
let plus = el.querySelector(".plus");
let minus = el.querySelector(".minus");
let result = el.querySelector("input");
//Array.prototype.forEach.call(plus, function(element) {
// let plus = element;
plus.addEventListener("click", (e) => {
let span = plus.previousElementSibling;
console.log(span);
let oldValue = Number(span.value);
let newVal = oldValue + 1;
span.setAttribute("value", newVal);
});
//});
//Array.prototype.forEach.call(minus, function(element) {
// let minus = element;
minus.addEventListener("click", (e) => {
let span = minus.nextElementSibling;
let oldValue = Number(span.value);
let newVal = oldValue - 1;
span.setAttribute("value", newVal);
if (newVal === 0) {
//blockQuantity.remove();
blockQuantity.innerHTML = `<button class="btn btn-order">order</button>` //+
}
});
//});
}
});
});
<div class="f1">
<button class="btn btn-order">order</button>
</div>
<div class="f1">
<button class="btn btn-order">order</button>
</div>
<div class="f1">
<button class="btn btn-order">order</button>
</div>
Дополнение к ответу:
В качестве дополнения приведу код полученный из исходного путем применения некоторых техник (метод bind, принцип разработки DRY, именование переменных), но даже этот код можно улучшать.
const blocks = document.querySelectorAll(".f1");
// храним шаблоны в удобном для редактирования месте
const orderHTML = '<button class="btn btn-order">order</button>'
const quantityHTML = `<div class="quantity">
<button class="minus">-</button>
<input type="text" class="input" value="1">
<button class="plus">+</button>
</div>`;
// общая функция установки значения инпуту используется
// через bind с заданием предопределенных аргументов
function setInputElement(block, inputElement, delta){
const oldValue = Number(inputElement.value);
const newVal = oldValue + delta;
inputElement.setAttribute("value", newVal);
if (newVal === 0) block.innerHTML = orderHTML
}
blocks.forEach((block) => {
block.addEventListener("click", (event) => {
if (event.target.classList.contains('btn-order')) {
block.innerHTML = quantityHTML;
const plus = block.querySelector(".plus");
const minus = block.querySelector(".minus");
// создаем разные обработчики через bind
const plusEventHandler = setInputElement.bind(this, block, plus.previousElementSibling, +1)
const minusEventHandler = setInputElement.bind(this, block, minus.nextElementSibling, -1)
plus.addEventListener("click", plusEventHandler)
minus.addEventListener("click", minusEventHandler)
}
});
});
<div class="f1">
<button class="btn btn-order">order</button>
</div>
<div class="f1">
<button class="btn btn-order">order</button>
</div>
<div class="f1">
<button class="btn btn-order">order</button>
</div>
как лучше
как у них и как лучше две разные вещи.
Изначально я просто делала все скрытием блока (display='none')
что насколько помню, может приводить к переотрисовке всего потока(ради показа\скрытия пары кнопок, так себе затея. особенно если страница не маленькая, но для новичка вполне норм)
Но в Ozone я так поняла кнопки исчезают из DOM, а не просто скрываются
а это, наверняка не только переотрисует поток, но перед этим еще и дом перестроит. что скорей камень в их огород, чем плюс.
перерисовывать поток и перестраивать дерево на каждый чих... осталось только начать всегда перезагружать html с бэка и встанет вопрос, а зачем вообще нам эти js/ajax?! )))
.flip {
background: skyblue;
margin: 1em;
padding: .3em;
}
/*.flip:hover #i, - для edge, он с within не дружит вроде */
.flip:focus-within #i
{transform: rotateX(0deg);}
#i {
transform: rotatex(-90deg);
transition: transform .5s;
position: absolute;
z-index: 7;
left: 2em
}
<form
class="flip"
oninput='if(this.i.value < 0){
this.i.blur(); this.i.value++
}'>
<label for='i'>я бы сделал так</label>
<input id='i' type='number' value='0' min='-1'>
</form>
но если уж так сильно хочется. ознакомьтесь с appendChhild, remuveChild
да и про innerHTML, outerHTML знания не помешают(в примерах выше продемонстрировали вроде оба подхода)
для полноты картины, можно еще document.write() вспомнить, но не уверен может ли он что то кроме текста вывести...
и кстати, можно еще с shadow DOM познакомиться. оно прикольное
поскольку приличных примеров(без тонны воды) и при том хоть как то работающих кот наплакал. вот вам головоломка:
customElements.define('y-c-t', class extends HTMLElement {
connectedCallback() {
this.attachShadow({
mode: 'open'
});
this.shadowRoot.append(document.getElementById('tpl').content.cloneNode(true));
}
});
b{display: list-item}
b{border: 2px solid pink; margin: 3px;}
ul{border: 2px solid lime !important; /*он спрятался*/}
<y-c-t data-comment='You Custom Tag'>
<b slot='l-items'>a</b>
<b slot='l-items'>b</b>
<b slot='footer'>c</b>
<b slot='l-items'>d</b>
<b slot='l-items'>e</b>
<b slot='l-items'>f</b>
<hr>
<hr slot='footer'>
<hr>
</y-c-t>
<template id='tpl' style='display: none; /*для ясности*/'>
<slot data-comment='все что без атрибута "SLOT" будет тут.
но это "тут", будет не тут'></slot>
<slot name='header'>
header placeholder
<br>
этот текст выводится, потому что данный слот ничем не занят
</slot>
<ul data-comment='йа в DOM`ике!!1'>
<slot name='l-items'>list items placeholder</slot>
</ul>
<slot name='footer'>footer placeholder</slot>
</template>