Как избавиться нескольких срабатываний скрипта при залипании клавиши Enter?
Заметил в логах и перепроверил на своей клавиатуре, что при вводе в текстовое поле искомого значения и нажатия клавиши Enter происходит как-бы залипание клавиши клавиатуры и PHP-скрипт отрабатывает 5-20 раз. При нажатии кнопки Поиск мышкой такого нет.
Как такого избежать? Можно ли средствами JS это реализовать?
На текстовое поле наложен jQuery обработчик:
var myobj = {
txtSearch: $('#txtSearch'),
btnSubmit: $('#btnSubmit'),
init: function () {
this.btnSubmit.on('click', this.action.bind(this));
this.txtSearch.on('keyup', this.action.bind(this));
},
action: function (e) {
//проверяем клик по кнопке или энтер
if (e.keyCode !== 13 && e.type !== 'click') {
return;
}
this.disableForm();
//getAjax()
},
getAjax: function (code) {
$.ajax({...})
.done(function (r) {
this.enableForm();
}.bind(this));
},
disableForm: function () {
this.btnSubmit.attr('disabled', 'disabled');
this.txtSearch.off('keyup', this.action.bind(this));
},
enableForm: function () {
this.btnSubmit.removeAttr('disabled');
this.txtSearch.on('keyup', this.action.bind(this));
},
};
Скриншот одного нажатия клавиши Enter, то есть, проблему видно налицо:
Update: нашел проблемку, при нажатии enter отключал форму this.txtSearch.off('keyup', this.action.bind(this));, а затем при загрузке результата включал её обратно this.txtSearch.on('keyup', this.action.bind(this));
Я думал, что обработчик удаляется, а затем заново навешивается. На самом деле, обработчик не удаляется, а наоборот плодится в геометрической прогрессии.
Ответы (1 шт):
Начну объяснение с небольшого эксперимента, проверим, являются ли два значения - одинаковыми значениями (т.е. ссылаются ли они на одно и то же). Для сравнения воспользуемся методом Object.is():
const q = {a: true};
const z = q;
z.b = false;
console.log( Object.is(q, q) );
console.log( Object.is(q, z) );
Видим, что Object.is возвращает true, т.е. по ссылке один и тот же объект.
Теперь поставим эксперимент с сущностями, которые ближе к вопросу:
const fn = () => {};
const bindedFn1 = fn.bind(window);
const bindedFn2 = fn.bind(window);
console.log( Object.is(bindedFn1, bindedFn2) );
Видим, что Object.is возвращает false, т.е. при каждом вызове bind создаётся новый объект.
Что происходит в твоём коде
$().ready(() => {
class Search {
constructor() {
this.count = 0;
this.txtSearch = $('#search');
this.txtSearch.on('keyup', this.action.bind(this));
}
action(ev) {
console.log(`call action ${++this.count}`);
this.txtSearch.off('keyup', this.action.bind(this));
this.txtSearch.on('keyup', this.action.bind(this));
}
}
const example = new Search();
$(document).on('keyup', () => console.log('------------------'));
});
<html>
<body >
<input id="search" type="text"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
</body>
</html>
В логе видно, что при текстовом вводе в input, с каждым новым событием количество вызовов функции action увеличивается, видна "серия" вызовов.
Так происходит из-за того, что при отписке от события, this.txtSearch.off('keyup', this.action.bind(this)), ты передал не тот же обработчик, который был передан при подписке. Такой же, но не тот же. Ты передал другую функцию. При отписке нужно было передавать именно ту функцию, которую ты передавал при подписке, именно её следует удалить.
А "серия" вызовов образуется потому, что при первом вызове action добавляется +1 обработчик. При втором вызове 2 обработчика и добавляется ещё +2. На третьем 4 обработчика и добавляется ещё +4. Лавина.
Как нужно исправить твой код
Не зная что за сущность твой this, я покажу как решить проблему в лоб - просто сохраню в this экземпляр функции, которая станет обработчиком события keyup, вот так: this.handlerKeyUp = this.action.bind(this)
хотя ещё можно отписаться вообще от всего this.txtSearch.off('keyup'), но обычно так делать не рекомендуют
$().ready(() => {
class Search {
constructor() {
this.count = 0;
this.txtSearch = $('#search');
this.handlerKeyUp = this.action.bind(this);
this.txtSearch.on('keyup', this.handlerKeyUp);
}
action(ev) {
console.log(`call action ${++this.count}`);
this.txtSearch.off('keyup', this.handlerKeyUp);
this.txtSearch.on('keyup', this.handlerKeyUp);
}
}
const example = new Search();
$(document).on('keyup', () => console.log('------------------'));
});
<html>
<body >
<input id="search" type="text"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
</body>
</html>
в typescript можно задекорировать метод так, чтоб он был autobind, но я не помню как это сделать в js. можешь поискать, для расширения кругозора :)
UDP by Qwertiy
В функцию .on передаётся строка со списком отслеживаемых событий в которых, опционально, можно указать namespace. Таким образом можно кастомизировать отписку от обработчика:
$().ready(() => {
class Search {
constructor() {
this.count = 0;
this.txtSearch = $('#search');
this.txtSearch.on('keyup.ns', this.action.bind(this));
}
action(ev) {
console.log(`call action ${++this.count}`);
this.txtSearch.off('keyup.ns');
this.txtSearch.on('keyup.ns', this.action.bind(this));
}
}
const example = new Search();
$(document).on('keyup', () => console.log('------------------'));
});
<html>
<body >
<input id="search" type="text"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
</body>
</html>
