I have some code that fires a function on keyup inside an input field. To prevent this function from firing every literal keyup I set a timeout function of half a second so my code won't spam ajax requests.
But for some reason this timeout code isn't working.
This is what I had at first, which does work:
Form:
<form class="search-address">
<input id="account_id" type="hidden" name="account_id" value="<?PHP echo $_SESSION['user']['id']; ?>">
<input class="search-word" type="text" placeholder="Zoeken in adresboek...">
</form>
My trigger code:
$('.search-word').keyup(function(e) {
clearTimeout($.data(this, 'timer'));
if (e.keyCode == 13){
search(true);
}else{
$(this).data('timer', setTimeout(search, 500));
}
});
My function:
function search(force) {
var zoekwaarde = $(".search-word").val();
var account_id = $("#account_id").val();
$.ajax({
type:'post',
url:"includes/searchaddress.php",
data:({zoekwaarde: zoekwaarde, account_id: account_id}),
success:function(data){
$( "#adressenlist" ).show().empty().append( data );
$( "#deleteresult" ).hide();
$( "#adresresult" ).hide();
}
});
}
This works, but the problem is I have 2 forms with the same class of .search-word
. So I tweaked my code a little like this:
$('.search-word').keyup(function(e) {
searchword = $(this).val();
clearTimeout($.data(this, 'timer'));
if (e.keyCode == 13){
search(true, searchword);
}else{
$(this).data('timer', setTimeout(search(true, searchword), 500));
}
});
function search(force, searchword) {
var zoekwaarde = searchword;
var account_id = $("#account_id").val();
$.ajax({
type:'post',
url:"includes/searchaddress.php",
data:({zoekwaarde: zoekwaarde, account_id: account_id}),
success:function(data){
$( "#adressenlist" ).show().empty().append( data );
$( "#deleteresult" ).hide();
$( "#adresresult" ).hide();
}
});
}
I use searchword = $(this).val();
so I only post the value of $(this)
input field, and not the last one in the dom tree. I thought this would work but this breaks my timeout function, it now triggers the search
function instantly on every key press. What can I do to fix that?
CodePudding user response:
Try wrapping your search function in an anonymous function (callback) like:
setTimeout(() => { search(param1, param2) }, 1500)
CodePudding user response:
The OP needs to create a throttled version of the event handler.
E.g. underscore and lodash both provide a throttle
method.
function handleSearch(/*evt*/) {
console.log('handleSearch :: this.value ...', this.value);
}
document
.forms[0]
.querySelector('[type="search"]')
.addEventListener('input', handleSearch);
const elmSearch = document
.forms[1]
.querySelector('[type="search"]');
// - with underscore's `throttle` the handler's `event` argument
// unfortunately gets lost/ommitted.
// - a viable workaround is to firstly bind the to be handled control
// to `handleSearch` and then to create a throttled handler from it.
elmSearch.addEventListener('input', _.throttle(handleSearch.bind(elmSearch), 500));
[type=search] { width: 17em; margin: 10px 0; }
<script src="https://cdn.jsdelivr.net/npm/[email protected]/underscore-umd-min.js"></script>
<form class="search-address">
<input type="search" placeholder="immediate addressbook search ...">
</form>
<form class="search-address">
<input type="search" placeholder="throttled addressbook search ...">
</form>
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
Edit
"So... they should completely rewrite their code, removing jQuery, but adding underscore? Seems a bit much for such a simple problem. – Heretic Monkey"
"@HereticMonkey ... no one prevents them from writing their own
throttle
abstraction which I would advise anyhow. And the example above is a use case boiled down to the most necessary, in order to demonstrate the differences. The OP has a 2k reputation. I presume the OP can cope with that. – Peter Seliger"
Due to the arguments of Heretic Monkey there is a custom throttle
implementation which takes complexity off the OP's event handling scripts. The OP's code then can be refactored to something like the following example ...
function search(force, searchword) {
var zoekwaarde = searchword;
var account_id = $("#account_id").val();
$.ajax({
type: 'post',
url: "includes/searchaddress.php",
data: ({zoekwaarde: zoekwaarde, account_id: account_id}),
success: function(data){
$( "#adressenlist" ).show().empty().append( data );
$( "#deleteresult" ).hide();
$( "#adresresult" ).hide();
}
});
}
function handleSearch(evt) {
const searchword = evt.currentTarget.value;
// search(true, searchword)
console.log(`search(true, ${ searchword })`);
}
$('.search-word').each((_, elmNode) => {
// each element of cause needs its own
// (unique) throttled handler version.
$(elmNode).on('input', throttle(handleSearch, 500));
});
body { margin : 0; }
[type=search] { width: 17em; margin-bottom: 3px; }
.as-console-wrapper { min-height: 70%!important; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<form class="search-address">
<input type="hidden" name="account_id" id="account_id" value="sessionUserId" />
<input type="search" placeholder="Zoeken in adresboek ..." class="search-word" />
</form>
<form class="search-another-address">
<input type="hidden" name="account_id" id="account_id" value="sessionUserId" />
<input type="search" placeholder="Zoeken in adresboek ..." class="search-word" />
</form>
<script>
function isFunction(type) {
return (
(typeof type === 'function')
&& (typeof type.call === 'function')
&& (typeof type.apply === 'function')
);
}
function getSanitizedTarget(target) {
return target ?? null;
}
function getSanitizedInteger(value) {
return (Number.isSafeInteger(value = parseInt(value, 10)) && value) || 0;
}
function getSanitizedPositiveInteger(value) {
return Math.max(getSanitizedInteger(value), 0);
}
function throttle(proceed, threshold, isSuppressTrailingCall, target) {
if (!isFunction(proceed)) {
return proceed;
}
target = getSanitizedTarget(target);
threshold = getSanitizedPositiveInteger(threshold) || 200;
isSuppressTrailingCall = !!isSuppressTrailingCall;
let timeoutId, timeGap;
let timestampRecent, timestampCurrent;
let context;
function trigger(...argsArray) {
timestampRecent = timestampCurrent;
proceed.apply(context, argsArray);
}
function throttled(...argsArray) {
clearTimeout(timeoutId);
context = target ?? getSanitizedTarget(this);
timestampCurrent = Date.now();
if (timestampRecent) {
timeGap = (timestampCurrent - timestampRecent);
if (isSuppressTrailingCall && (timeGap >= threshold)) {
// trailing call will be suppressed.
trigger(...argsArray);
} else {
timeoutId = setTimeout(trigger, Math.max((threshold - timeGap), 0), ...argsArray);
}
} else {
// initial call.
trigger(...argsArray);
}
}
return throttled;
}
</script>
<iframe name="sif2" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>