Ajax call on textarea change, but not on every change - javascript

I'm making a kind of note system for my users in Laravel application.
I want this textarea that the user writes their notes on to be saved after the user is done typing, instead of running the ajax call on every change done because that would be a lot of requests.
What is the best way to achieve this?
Should I use some sort of timer here to determine if the user is done writing?
Currently I'm just using the following:
notesTextarea.on('change', function(e) {
$.ajax({ ... });
});

While the other proposed solutions (on blur or on keydown if it's the enter key), would work, I think they're not what you're looking for.
What if the user neither clicks outside the textarea, nor uses enter? I would use a technique called debounce instead. You can read about it for example here: https://davidwalsh.name/javascript-debounce-function or see an example here: https://www.geeksforgeeks.org/debouncing-in-javascript/
The short version is, it only calls a function after an event has stopped firing for a given length of time.
Add the debounce function to your code by either using an NPM package (ex. https://www.npmjs.com/package/debounce) or directly adding the necessary code:
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
Source: https://davidwalsh.name/javascript-debounce-function which has taken it from Underscore.js
Then have a function doing your ajax calls like:
var makeAjaxCall = function(e) {
$.ajax({ ... });
};
Now you can simply add a listener like:
var minTimeoutBetweenCallsInMilliSeconds = 500;
notesTextarea.on('change', debounce(makeAjaxCall, minTimeoutBetweenCallsInMilliSeconds));
Now the makeAjaxCall function would be called only 500ms after the last change event occurred, a.k.a. the user stopped typing.

You can use the onlbur event instead. It happens when the element lost the focus, that could means you are no longer changing its content:
$("#notesTextarea").on('blur', function(e) {
// $.ajax({ ... });
console.log("has finished, content to be saved:" + $(this).val());
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<p>
Notes: <textarea id="notesTextarea"></textarea>
</p>
<p>
Other something: <input type="text">
</p>

One way, you can make the call on pressing the enter key on keydown event:
notesTextarea.on('keydown', function(e) {
if(e.keyCode == 13){
$.ajax({ ... });
}
});
You can also think of blur which will occur when the element loses focus
notesTextarea.on('blur', function(e) {
$.ajax({ ... });
});

If you want to automate this saving you can call to ajax, after N number of characters typed by user each time.
Otherwise, you can call to ajax function on CTRL+S key combination
document.addEventListener('keydown', function(event) {
if (event.ctrlKey && event.key == 's') {
event.preventDefault(); //prevent default file save box open by browser
alert('ajax call goes here!');
}
});

Similar to #Mamun's answer.
Call Ajax on keypress event:
$("textarea").on('keypress', function() {
//$.ajax({ ... });
console.log('request sent!');
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<p>
<textarea id="textarea"></textarea>
</p>

Related

Handling multithreadiing issue in javascript/jquery?

$(".getDetails").click(function() {
// some stuff like fetching response from server
})
when user clicks getDetails button on UI multiple times within fraction of second , jquery generates two calls for click function and my logic fails.
I think solution to this will be to disable the button on first click itself(so that use can't click multiple times). Once i get the response or just before returning
from click method i make it enable. Is there any better solution ?
If no, how can i make button disable as soon as user click button first time. I think it needs to be done before calling click method or some where in html element ?
Java provides synchronized keyword so that only one thread enters at time inside method , i am not sure is similar thing exist in javascript or not ?
Assuming the click handler executes an AJAX request you can set the button as disabled before making the request, then enable it again once the request completes. Try this:
$(".getDetails").click(function(){}
var $btn = $(this).prop('disabled', true);
$.ajax({
url: '/foo'
success: function() {
console.log('It worked!');
},
error: function() {
console.log('It failed!');
},
complete: function() {
$btn.prop('disabled', false);
}
});
});
you can try unbinding click event and after ajax call again bind click to that class
$(".getDetails").click(function(){}
$(".getDetails").unbind('click');
// some stuff like fetching response from server
)
You can use simple flag to prevent firing your logic multiple times:
var flag = true
$(".getDetails").click(function() {
if (flag) {
flag = false;
//your logic...
//when your code ends (in after-AJAX callback for example)
flag = true;
}
});
$(".getDetails").click(function(e){
var $target = $(e.currentTarget);
// assuming the click listener is on the button
$target.prop('disabled',true);
// request, stuff...and when done:
$target.prop('disabled',false);
})
try Prevent Default and return false to avoid any other event propagation
This is solution is like semaphore or monitor
var progress = false;
$(".getDetails").on('click', function(e) {
if(!progress){
progress = true;
// some stuff like fetching response from server
//also after sucessfull fetch make true to false again
}else{
console.log('something in progress');
}
e.preventDefault();
return false;
})
This should make sure that your button will not fire the async request twice, until you have a response.
function doAjaxReq() {
/*
Add your ajax operation here
as a return value of doAjaxReq
like so:
return $.ajax({
url: '/foo',
type: 'POST',
data: data
})
Since i can't use ajax here let's smilulate
it useing a promise.
*/
promise = new Promise(function(res, rej) {
setTimeout(function(){
res({foo: "bar"});
}, 1000)
})
return promise;
}
/*
Inside here you add the click handlder
only once use `elem.one('click'...`
*/
function addClickHandler(elem) {
elem.one('click', function() {
// do your ajax request and when its
// done run `addClickHanlder` again
// i'm using `.then` because of the promise,
// you should be using `.done`.
doAjaxReq().then(function(data) {
console.log(data);
addClickHandler(elem);
});
})
}
addClickHandler($(".getDetails"));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button class="getDetails">Get Details</button>

Do I need to remove and replace event handlers with JQuery to swap them?

I want the events click and touchstart to trigger a function.
Of course this is simple with JQuery. $('#id').on('click touchstart', function{...});
But then once that event is triggered, I want that same handler to do something else when the events are triggered,
and then later, I want to go back to the original handling function.
It seems like there must be a cleaner way to do this than using $('#id').off('click touchstart'); and then re-applying the handler.
How should I be doing this?
You can create a counter variable in some construct in your javascript code that allows you to decide how you want to handle your event.
$(function() {
var trackClicks = (function() {
var clicks = true;
var getClicks = function() {
return clicks;
};
var eventClick = function() {
clicks = !clicks;
};
return {
getClicks: getClicks,
eventClicks: eventClicks
}
})();
$('#id').on('click touchstart', function {
if (trackClicks.getClicks()) {
handler1();
} else {
handler2();
}
trackClicks.eventClick();
});
function handler1() { //firsthandler};
function handler2() { //secondhandler};
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
The way I would do this is by creating a couple of functions for the handler function to call based on certain flags. Sudo code would be something like this:
function beginning_action() {
...
}
function middle() {
...
}
var beginning_state = true;
$('#id').on('click touchstart', function{
if(beginning_state) {
beginning_action();
} else {
middle();
}
});
Then all you need to do is change the variable beginning_state to change which function is called. Of course you would give them better names that describe what they do and not when they do it.
Additionally, if you want the handler to call more than two functions you can change the beginning_state variable from a boolean to an int and check it's value to determine which function to call.
Good luck!

differences between onload, onunload, onbeforeunload

I might be creepy but I am testing something and I kind of don't understand the purpose of there three events...
so here is my testcode:
<div id="timeBU"></div>
<div id="timeL"></div>
<div id="timeUL"></div>
<div id="text"></div>
<script type="text/javascript">
function loading(message, timeId) {
var secs=0;
var interval = setInterval(function() {
document.getElementById(timeId).innerHTML = secs;
secs++;
},1);
document.getElementById("text").innerHTML += "<br>"+message+"<br>";
}
// first way
window.addEventListener("beforeunload", loading("beforeunload", "timeBU"));
window.addEventListener("load", loading("load", "timeL"));
window.addEventListener("unload", loading("unload", "timeUL"));
// second way
// window.onunload = loading("beforeunload", "timeBU");
// window.onload = loading("load", "timeL");
// window.onbeforeunload = loading("unload", "timeUL");
</script>
it desn't matters I use first way or second way, but anyways I posted both...
What I am saying, that output depend on the order of //first way code or //second way code, which listener is added first, that function's message is displayed first... you can try swapping places... but time is all the same (you can increase interval or alert something for better view), that makes me think that all three events behave same...
Note: I've tested this only for chrome
my extra question is:How can I differ whether user just opened my site or reloaded?
if you have some kind of advices about my extra question, I would be grateful...
Because you're calling loading() function, you're not using as a callback:
window.addEventListener("beforeunload", loading("beforeunload", "timeBU"))
That line calls loading() (so your message is printed) and passes its return value (nothing) to addEventListener() as callback. Nothing will be performed when event will be fired.
It's not how I'd suggest to do it but try instead this:
window.addEventListener("beforeunload", function () {
loading("beforeunload", "timeBU")
});
This code worked for me. (tested in firefox & chrome).
The only difference now is that loading(message, timeId) now returns a function which gets called as a callback. Events always require you to pass a Callback (which refers to a function) which gets called as soon as the event is called.
<div id="timeBU"></div>
<div id="timeL"></div>
<div id="timeUL"></div>
<div id="text"></div>
<script type="text/javascript">
function loading(message, timeId) {
return function(){
var secs=0;
var interval = setInterval(function() {
document.getElementById(timeId).innerHTML = secs;
secs++;
},1);
document.getElementById("text").innerHTML += "<br>"+message+"<br>";
}
}
window.addEventListener("beforeunload", loading("beforeunload", "timeBU"));
window.addEventListener("load", loading("load", "timeL"));
window.addEventListener("unload", loading("unload", "timeUL"));
</script>

Event listener DOMNodeInserted being run multiple times, why?

I'm trying to listen for a node with a certain class being added to the DOM dynamically. Once this Node has been added i want to then add an instance of of a plugin to this Node. The problem I'm having is DOMNodeInserted is running multiple times which is then running my plugin multiple on this one Node which is causing problems.
There is only ever one occurrence of this class on page.
Why is this and how can I stop this from happening?
$(document).ready(function(){
$('#editArea').live('DOMNodeInserted', '.class', function(e){
$('.class').plugin({
source: 'libs/ajax/somescript.php',
});
})
});
I ran into the same problem awhile back. What you need to do is debounce the function so it fires after the last DOMNodeInserted call.
Try this (adapted from John Hann's smartresize--comment/link left in):
(function ($, sr) {
// debouncing function from John Hann
// http://unscriptable.com/index.php/2009/03/20/debouncing-javascript-methods/
var debounce = function (func, threshold, execAsap) {
var timeout;
return function debounced() {
var obj = this, args = arguments;
function delayed() {
if (!execAsap)
func.apply(obj, args);
timeout = null;
};
if (timeout) {clearTimeout(timeout);
} else if (execAsap) {func.apply(obj, args);}
timeout = setTimeout(delayed, threshold || 100);
};
}
jQuery.fn[sr] = function (fn) { return fn ? this.on('DOMNodeInserted', debounce(fn)) : this.trigger(sr); };
})(jQuery, 'debouncedDNI');
$(document).ready(function () {
$('#editArea').debouncedDNI(function () {
$('.class').plugin({
source: 'libs/ajax/somescript.php',
});
});
});
Probably because in your DOM whatever the element you're watching for has children elements. The event is fired once for the matching element (.class) and once for each descendant.
If your element you need to watch is something like a select with a bunch of option elements under it, a quick and dirty solution might be to watch for another "buddy" element you can put along with it in the DOM. For instance:
$(document).ready(function(){
$('#editArea').on('DOMNodeInserted', '#classbuddy', function(e){
$('.class').plugin({
source: 'libs/ajax/somescript.php',
});
})
});
Then in your markup you would just need to add something like an empty span element with id="classbuddy". Because that span would not have children elements, your code would fire only once and so .plugin() would be applied one time only.

Get text from field on keyup, but with delay for further typing

I have a form which is submitted remotely when the various elements change. On a search field in particular I'm using a keyup to detect when the text in the field changes. The problem with this is that when someone types "chicken" then the form is submitted seven times, with only the last one counting.
What would be better is something like this
keyup detected - start waiting (for one second)
another keyup detected - restart waiting time
waiting finishes - get value and submit form
before I go off and code my own version of this (I'm really a backend guy with only a little js, I use jQuery for everything), is there already an existing solution to this? It seems like it would be a common requirement. A jQuery plugin maybe? If not, what's the simplest and best way to code this?
UPDATE - current code added for Dan (below)
Dan - this may be relevant. One of the jQuery plugins I'm using on the page (tablesorter) requires this file - "tablesorter/jquery-latest.js", which, if included, leads to the same error with your code as before:
jQuery("input#search").data("timeout", null) is undefined
http‍://192.168.0.234/javascripts/main.js?1264084467
Line 11
Maybe there's some sort of conflict between different jQuery definitions? (or something)
$(document).ready(function() {
//initiate the shadowbox player
// Shadowbox.init({
// players: ['html', 'iframe']
// });
});
jQuery(function(){
jQuery('input#search')
.data('timeout', null)
.keyup(function(){
jQuery(this).data('timeout', setTimeout(function(){
var mytext = jQuery('input#search').val();
submitQuizForm();
jQuery('input#search').next().html(mytext);
}, 2000)
)
.keydown(function(){
clearTimeout(jQuery(this).data('timeout'));
});
});
});
function submitQuizForm(){
form = jQuery("#searchQuizzes");
jQuery.ajax({
async:true,
data:jQuery.param(form.serializeArray()),
dataType:'script',
type:'get',
url:'/millionaire/millionaire_quizzes',
success: function(msg){
// $("#chooseQuizMainTable").trigger("update");
}
});
return true;
}
Sorry i haven't tested this and it's a bit off the top of my head, but something along these lines should hopefully do the trick. Change the 2000 to however many milliseconds you need between server posts
<input type="text" id="mytextbox" style="border: 1px solid" />
<span></span>
<script language="javascript" type="text/javascript">
jQuery(function(){
jQuery('#mytextbox')
.data('timeout', null)
.keyup(function(){
clearTimeout(jQuery(this).data('timeout'));
jQuery(this).data('timeout', setTimeout(submitQuizForm, 2000));
});
});
</script>
Here's your fancy jquery extension:
(function($){
$.widget("ui.onDelayedKeyup", {
_init : function() {
var self = this;
$(this.element).keyup(function() {
if(typeof(window['inputTimeout']) != "undefined"){
window.clearTimeout(inputTimeout);
}
var handler = self.options.handler;
window['inputTimeout'] = window.setTimeout(function() {
handler.call(self.element) }, self.options.delay);
});
},
options: {
handler: $.noop(),
delay: 500
}
});
})(jQuery);
Use it like so:
$("input.filterField").onDelayedKeyup({
handler: function() {
if ($.trim($(this).val()).length > 0) {
//reload my data store using the filter string.
}
}
});
Does a half-second delay by default.
As an update, i ended up with this which seems to work well:
function afterDelayedKeyup(selector, action, delay){
jQuery(selector).keyup(function(){
if(typeof(window['inputTimeout']) != "undefined"){
clearTimeout(inputTimeout);
}
inputTimeout = setTimeout(action, delay);
});
}
I then call this from the page in question's document.ready block with
afterDelayedKeyup('input#search',"submitQuizForm()",500)
What would be nice would be to make a new jquery event which uses this logic, eg .delayedKeyup to go alongside .keyup, so i could just say something like this for an individual page's document.ready block.
jQuery('input#search').delayedKeyup(function(){
submitQuizForm();
});
But, i don't know how to customise jquery in this way. That's a nice homework task though.
Nice job, Max, that was very helpful to me! I've made a slight improvement to your function by making it more general:
function afterDelayedEvent(eventtype, selector, action, delay) {
$(selector).bind(eventtype, function() {
if (typeof(window['inputTimeout']) != "undefined") {
clearTimeout(inputTimeout);
}
inputTimeout = setTimeout(action, delay);
});
}
This way you can use it for any type of event, although keyup is probably the most useful here.
I know this is old, but it was one of the first results when I was searching for how to do something like this so I though I would share my solution. I used a combination of the provided answers to get what I needed out of it.
I wanted a custom event that worked just like the existing jQuery events, and it needed to work with keypress + delete, backspace and enter.
Here's my jQuery plugin:
$.fn.typePause = function (dataObject, eventFunc)
{
if(typeof dataObject === 'function')
{
eventFunc = dataObject;
dataObject = {};
}
if(typeof dataObject.milliseconds === 'undefined')
dataObject.milliseconds = 500;
$(this).data('timeout', null)
.keypress(dataObject, function(e)
{
clearTimeout($(this).data('timeout'));
$(this).data('timeout', setTimeout($.proxy(eventFunc, this, e), dataObject.milliseconds));
})
.keyup(dataObject, function(e)
{
var code = (e.keyCode ? e.keyCode : e.which);
if(code == 8 || code == 46 || code == 13)
$(this).triggerHandler('keypress',dataObject);
});
}
I used $.proxy() to preserve the context in the event, though there could be a better way to do this, performance-wise.
To use this plugin, just do:
$('#myElement').typePause(function(e){ /* do stuff */ });
or
$('#myElement').typePause({milliseconds: 500, [other data to pass to event]},function(e){ /* do stuff */ });

Categories

Resources