Добавить input'у атрибуты min/max; всем трём элементам добавить общую обёртку (уверен, на странице подобных блоков будет больше одного):
<div class="counter">
<button class="minus">-</button>
<input type="number" min="2" max="17" value="5">
<button class="plus">+</button>
</div>
При обновлении input'а ограничивать новое значение значениями атрибутов min/max (если где-то ограничений быть не должно, то вместо отсутствующих значений атрибутов подставлять +/- бесконечность). Кроме того, неплохо бы обрабатывать пользовательский ввод в input - а ну как кто захочет его напрямую отредактировать, мимо кнопок.
Как это может выглядеть:
const updateInput = change => e =>
$('input', e.delegateTarget).val(function(i, v) {
const min = this.min || -Infinity;
const max = this.max || Infinity;
return Math.max(min, Math.min(max, (v | 0) + change));
});
$('.counter')
.on('click', '.minus', updateInput(-1))
.on('click', '.plus', updateInput(1))
.on('input', updateInput(0));
Или, к чёрту jquery:
const updateInput = change => e => {
const input = e.target.closest('.counter').querySelector('input');
const min = input.min || -Infinity;
const max = input.max || Infinity;
input.value = Math.min(max, Math.max(min, (input.value | 0) + change));
};
document.querySelectorAll('.counter').forEach(function(n) {
n.querySelector('.minus').addEventListener('click', this[0]);
n.querySelector('input').addEventListener('input', this[1]);
n.querySelector('.plus').addEventListener('click', this[2]);
}, [ -1, 0, 1 ].map(updateInput));
Или, не обязательно назначать обработчики событий каждому элементу индивидуально, достаточно по одному делегированному на тип события:
document.addEventListener('click', ({ target: t }) => {
const change =
t.classList.contains('plus') ? +1 :
t.classList.contains('minus') ? -1 :
0;
if (change) {
const input = t.closest('.counter').querySelector('input');
input.value -= -change;
input.dispatchEvent(new Event('input'), { bubbles: true });
}
});
document.addEventListener('input', ({ target: t }) => {
if (t.closest('.counter')) {
t.value = Math.min(t.max || Infinity, Math.max(t.min || -Infinity, t.value | 0));
}
});
А ещё можно добавить кнопкам data-атрибуты, указывающие, на сколько должно быть изменено значение input'а, и тогда проверять у кнопок наличие классов будет не надо:
<button class="minus" data-change="-1">-</button>
<button class="plus" data-change="+1">+</button>
document.addEventListener('input', updateInput);
document.addEventListener('click', updateInput);
function updateInput({ target: t }) {
const counter = t.closest('.counter');
if (counter) {
const input = counter.querySelector('input');
const min = input.min || -Infinity;
const max = input.max || Infinity;
input.value = Math.min(max, Math.max(min, (input.value | 0) + (t.dataset.change | 0)));
}
}