Как лучше реализовать на 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 шт):

Автор решения: De.Minov

Я бы это реализовал следующим образом:

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, следовательно, надо отобразить кнопки с изменением кол-ва и отобразить кол-во товаров в "счётчике".

Но это уже другая история.

→ Ссылка
Автор решения: Daniil Loban

Я попытался сохранить Ваш код в изначальном виде насколько это возможно, скажу сразу у Вас было много лишних моментов (я их просто заккоментировал чтобы у Вас была возможность оценить количество лишних строк) В целом еще нужно немного поизучать основы. Даже этот код можно сделать компактнее и красивее (см. Дополнение к ответу).

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>

→ Ссылка
Автор решения: puffleeck

как лучше

как у них и как лучше две разные вещи.

Изначально я просто делала все скрытием блока (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>

→ Ссылка