How to check if an api call has completed - javascript

I have a script in my code
<script src="https://geodata.solutions/includes/statecity.js"></script>
which is making an ajax call. This script is used to fetch states and cities and loads the value in select. How do I check whether that particular call is complete as it is in this external script and I want to set value of select using javascript/jquery
I am currently using setTimeout for setting select value and delaying it randomly for 6 seconds however it's not the right approach. Also, I have tried putting the code to set value in $(document).ready() but the api call returns the values later
setTimeout(function(){
jQuery("#stateId").val('<?php echo $rowaddress['state']; ?>').change();
setTimeout(function(){
jQuery("#cityId").val('<?php echo $rowaddress['city']; ?>').change();
}, 3000);
}, 6000);

spy on jQuery.ajax:
jQuery.ajax = new Proxy(jQuery.ajax, {
apply: function(target, thisArg, argumentsList) {
const req = target.apply(thisArg, argumentsList);
const rootUrl = '//geodata.solutions/api/api.php';
if (argumentsList[0].url.indexOf(rootUrl) !== -1) {
req.done(() => console.log(`request to ${argumentsList[0].url} completed`))
}
return req;
}
});

Having a look through the code for statecity.js, I've just seen that:
jQuery(".states").prop("disabled",false);
is executed upon completion of initial loading. This is on line 150 of the source code.
You could monitor the disabled attribute of the .states selector to be informed when the activity is completed using the handy JQuery extension watch.
To detect the completion of the event, just watch the disabled property of the .states item:
$('.states').watch('disabled', function() {
console.log('disabled state changed');
// Add your post-loading code here (or call the function that contains it)
});
Note that this is extremely hacky. If the author of statecity.js changes their code, this could stop working immediately or could behave unexpectedly.
It is always very risky to rely on tinkering in someone else's code when you have no control over changes to it. Use this solution with caution.
Unfortunately, the original link to the watch extension code seems to have expired, but here it is (not my code but reproduced from author):
// Function to watch for attribute changes
// http://darcyclarke.me/development/detect-attribute-changes-with-jquery
$.fn.watch = function(props, callback, timeout){
if(!timeout)
timeout = 10;
return this.each(function(){
var el = $(this),
func = function(){ __check.call(this, el) },
data = { props: props.split(","),
func: callback,
vals: [] };
$.each(data.props, function(i) { data.vals[i] = el.attr(data.props[i]); });
el.data(data);
if (typeof (this.onpropertychange) == "object"){
el.bind("propertychange", callback);
} else {
setInterval(func, timeout);
}
});
function __check(el) {
var data = el.data(),
changed = false,
temp = "";
for(var i=0;i < data.props.length; i++) {
temp = el.attr(data.props[i]);
if(data.vals[i] != temp){
data.vals[i] = temp;
changed = true;
break;
}
}
if(changed && data.func) {
data.func.call(el, data);
}
}
}

Related

Run Function on Variable Change

Let's assume I have a variable:
var x = 0;
Each time this variable gets modified I want to run a function:
function(){
console.log('x has been changed');
}
Would RxJs be appropiate for this task? If not, what other approach would work better?
You set value to property of an object, use set, get.
const x = {};
let value = 0;
function fn(oldValue, newValue) {
console.log(`${oldValue} has been changed to ${newValue}`);
}
Object.defineProperty(x, "prop", {
get() {
return value
},
set(val) {
fn(value, val);
value = val;
}
});
x.prop = 1;
x.prop = 10;
Douglas Tyler finished his answer before I had the chance to but yes, proxy is definitely something that you might use and here's an example :
const obj = {
_id: null,
set id(str) {
console.log('setting the value !');
this._id = str;
},
get id() {
return this._id;
}
}
I think a good bet would be to use Proxy, although this only works for objects, arrays and functions. Another option would be checking the value of on an interval and comparing it to the old value of x which you've stored in another variable, though this may not work for your purposes. I think your best option would be to always set x with a function that does whatever other functionality you want it to.
You can use interval. Although I use Angular to $watch, you can see its implementation. There is also object.watch functionality but last i checked was not working in other browsers. So below is code using intervals (not a fun of intervals though)
var x=0, xWatcher = 0;
setInterval(function() {
if ( x !== xWatcher ) {
xWatcher = x;
//Your code here
xChanged()
}
}, 50); // any delay you want
function xChanged(){
console.log('x has been changed');
}

How do I know when a DOM-element is created?

I'm trying to acquire a reference for a specific QUnit DOM-element, as soon as this element is created. I can get it by using window.setTimeout, but is there an event-driven way to do it?
I have tried various approaches, but only the least satisfying (window.setTimeout) actually works:
window.onload = function() {
var qunitTestrunnerToolbar_element = document.getElementById("qunit-testrunner-toolbar");
console.log("from window.onload: qunitTestrunnerToolbar_element: ", qunitTestrunnerToolbar_element);
};
returns null
document.addEventListener("DOMContentLoaded", function(event) {
var qunitTestrunnerToolbar_element = document.getElementById("qunit-testrunner-toolbar");
console.log("from document.addEventListener(DOMContentLoaded): qunitTestrunnerToolbar_element: ", qunitTestrunnerToolbar_element);
});
returns null
document.addEventListener("load", function(event) {
var qunitTestrunnerToolbar_element = document.getElementById("qunit-testrunner-toolbar");
console.log("from document.addEventListener(load): qunitTestrunnerToolbar_element: ", qunitTestrunnerToolbar_element);
});
is never executed
window.setTimeout(function() {
var qunitTestrunnerToolbar_element = document.getElementById("qunit-testrunner-toolbar");
console.log("from window.setTimeout: qunitTestrunnerToolbar_element: ", qunitTestrunnerToolbar_element);
}, 2000);
returns the DOM-reference
The code can be befiddled at this jsfiddle.
Note that the fiddle only logs the one successful DOM-reference. The others are somehow silenced.
This is how it looks when executed locally:
There is the DOMMutationObserver, which allows you to subscribe to changes to the DOM.
var mutationObserver = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
var id = 'qunit-testrunner-toolbar',
added = mutation.addedNodes,
removed = mutation.removedNodes,
i, length;
for (i = 0, length = added.length; i < length; ++i) {
if (added[i].id === id) {
// qunit-testrunner-toolbar was added
}
}
for (i = 0, length = removed.length; i < length; ++i) {
if (removed[i].id === id) {
// qunit-testrunner-toolbar was removed
}
}
});
});
mutationObserver.observe(target, {childList: true});
If you need to stop listening for changes (as the amount of changes to the DOM can be huge ;) ), you can simply use mutationObserver.disconnect().
If the #qunit-testrunner-toolbar is not the node that is added (but instead is part of another structure), the checks above will not work. If that is the case you can replace the added[i].id === id with something like
if (added[i].querySelector('#qunit-testrunner-toolbar')) {
// it was added
}
I should at least mention the now deprecated Mutation Events, which seem more convenient to implement but were pretty slow and inaccurate.
document.addEventListener('DOMNodeInserted', function(e) {
if (e.target.id === 'qunit-testrunner-toolbar') {
}
});
Tempting to use, but don't as it is deprecated.
Everything you put in JSFiddle's javascript is already wrapped in onload event (so window.onload will never be fired again - it is already fired). Change it to "Javascript" -> "Load time" -> "No wrap - in <head>" and you will get all of your events fired.

using setTimeout in javascript to do simple animation

I am using setTimeout to create animation in Javascript, but it does not seem to work. Only the 1st move of the animation is executed, no subsequent moves.
I tried on two different laptops using Firefox, one doesn't throw any error, but the one says self.animateCallback is not a function. I also see other errors like saying my timeout function is useless or "compile-and-go" when I tried diff ways. Doesn't seem to get it working. I tried "function(self){self.animateCallback()}" and "self.animateCallback" (with and without quotes).
The code is below, it is part of a prototype method.
increment : function(incr, target, tick) {
var self = this;
self.animateCallback = function()
{
var done = Math.abs(self.currValue - target) < Math.abs(incr);
if(!self.animateCallback || done) {
if(done) {
self.updateAngle(self.currValue/self.maxValue);
self.stopAnimation(); //just setting animateCallback to null
}
}
else
{
self.updateAngle((self.currValue+incr)/self.maxValue);
setTimeout(self.animateCallback, tick);
}
}
self.animateCallback.call();
},
I've got a feeling the problem has something to do with the line setTimeout(self.animateCallback..., which is accessing the function through a closure and a property. It should be neater, at least, to do it like this:
increment : function(incr, target, tick) {
var self = this;
var animateCallback = function()
{
var done = Math.abs(self.currValue - target) < Math.abs(incr);
if(done) {
self.updateAngle(self.currValue/self.maxValue);
self.animateTimeout = null;
}
else
{
self.updateAngle((self.currValue+incr)/self.maxValue);
self.animateTimeout = setTimeout(animateCallback, tick);
}
}
animateCallback();
},
stopAnimation: function() {
if (this.animateTimeout) {
clearTimeout(this.animateTimeout);
this.animateTimeout = null;
}
},
I think the error is that some other code is changing the value of self.animateCallback to something else. The first time through, setTimeout has the correct value for self.animateCallback, but after the first time, the value of self.animateCallback has changed to something else, which isn't a function, but is still a non-falsy value so that !self.animateCallback returns false.
You can try changing the if statement to this:
if((typeof self.animateCallback !== "function") || done) {
if(done) {
self.updateAngle(self.currValue/self.maxValue);
self.stopAnimation(); //just setting animateCallback to null
}
}
try to pass an anonymous function to setTimeout, like
setTimeout(function(){ self.animateCallback(); }, tick);
hope it'll help.

Throttle event calls in jQuery

I have a keyup event bound to a function that takes about a quarter of a second to complete.
$("#search").keyup(function() {
//code that takes a little bit to complete
});
When a user types an entire word, or otherwise presses keys rapidly, the function will be called several times in succession and it will take a while for them all to complete.
Is there a way to throttle the event calls so that if there are several in rapid succession, it only triggers the one that was most recently called?
Take a look at jQuery Debounce.
$('#search').keyup($.debounce(function() {
// Will only execute 300ms after the last keypress.
}, 300));
Here is a potential solution that doesn't need a plugin. Use a boolean to decide whether to do the keyup callback, or skip over it.
var doingKeyup = false;
$('input').keyup(function(){
if(!doingKeyup){
doingKeyup=true;
// slow process happens here
doingKeyup=false;
}
});
You could also use the excellent Underscore/_ library.
Comments in Josh's answer, currently the most popular, debate whether you should really throttle the calls, or if a debouncer is what you want. The difference is a bit subtle, but Underscore has both: _.debounce(function, wait, [immediate]) and _.throttle(function, wait, [options]).
If you're not already using Underscore, check it out. It can make your JavaScript much cleaner, and is lightweight enough to give most library haters pause.
Here's a clean way of doing it with JQuery.
/* delayed onchange while typing jquery for text boxes widget
usage:
$("#SearchCriteria").delayedChange(function () {
DoMyAjaxSearch();
});
*/
(function ($) {
$.fn.delayedChange = function (options) {
var timer;
var o;
if (jQuery.isFunction(options)) {
o = { onChange: options };
}
else
o = options;
o = $.extend({}, $.fn.delayedChange.defaultOptions, o);
return this.each(function () {
var element = $(this);
element.keyup(function () {
clearTimeout(timer);
timer = setTimeout(function () {
var newVal = element.val();
newVal = $.trim(newVal);
if (element.delayedChange.oldVal != newVal) {
element.delayedChange.oldVal = newVal;
o.onChange.call(this);
}
}, o.delay);
});
});
};
$.fn.delayedChange.defaultOptions = {
delay: 1000,
onChange: function () { }
}
$.fn.delayedChange.oldVal = "";
})(jQuery);
Two small generic implementations of throttling approaches. (I prefer to do it through these simple functions rather than adding another jquery plugin)
Waits some time after last call
This one is useful when we don't want to call for example search function when user keeps typing the query
function throttle(time, func) {
if (!time || typeof time !== "number" || time < 0) {
return func;
}
var throttleTimer = 0;
return function() {
var args = arguments;
clearTimeout(throttleTimer);
throttleTimer = setTimeout(function() {
func.apply(null, args);
}, time);
}
}
Calls given function not more often than given amount of time
The following one is useful for flushing logs
function throttleInterval(time, func) {
if (!time || typeof time !== "number" || time < 0) {
return func;
}
var throttleTimer = null;
var lastState = null;
var eventCounter = 0;
var args = [];
return function() {
args = arguments;
eventCounter++;
if (!throttleTimer) {
throttleTimer = setInterval(function() {
if (eventCounter == lastState) {
clearInterval(throttleTimer);
throttleTimer = null;
return;
}
lastState = eventCounter;
func.apply(null, args);
}, time);
}
}
}
Usage is very simple:
The following one is waiting 2s after the last keystroke in the inputBox and then calls function which should be throttled.
$("#inputBox").on("input", throttle(2000, function(evt) {
myFunctionToThrottle(evt);
}));
Here is an example where you can test both: click (CodePen)
I came across this question reviewing changes to zurb-foundation. They've added their own method for debounce and throttling. It looks like it might be the same as the jquery-debounce #josh3736 mentioned in his answer.
From their website:
// Debounced button click handler
$('.button').on('click', Foundation.utils.debounce(function(e){
// Handle Click
}, 300, true));
// Throttled resize function
$(document).on('resize', Foundation.utils.throttle(function(e){
// Do responsive stuff
}, 300));
Something like this seems simplest (no external libraries) for a quick solution (note coffeescript):
running = false
$(document).on 'keyup', '.some-class', (e) ->
return if running
running = true
$.ajax
type: 'POST',
url: $(this).data('url'),
data: $(this).parents('form').serialize(),
dataType: 'script',
success: (data) ->
running = false

onchange javascript variable

Is there a way to call a JavaScript function if a javascript variable changes values using jQuery?
Something to the extend of -
var test = 1;
test = 2; // calls a javascript function
test = 3; // calls a javascript function
This way I wouldn't have to add an onchange event to so many different functions.
(Something seems a bit carelessly planned in your code if you need functionality like that)
The easiest way to add that feature is to create a function for updating your variable, that also calls whatever other function you want to.
Instead of:
var test = 1;
test = 2; // calls a javascript function
test = 3; // calls a javascript function
You do:
var test = 1;
function set_test(newval) {
test = newval;
my_callback(); // this is whatever you wanted to call onChange
}
set_test(2);
set_test(3);
try this, it's real variable change event:
var book = {
_year: 2004,
edition: 1
};
Object.defineProperty(book, "year", {
get: function(){
return this._year;
},
set: function(newValue){
this._year=newValue;
this.edition=newValue-2004;
alert(this._year);
alert(this.edition);
}
});
book.year=2017
// will alert 2017 and 13
No, there is not, just polling with setInterval or setTimeout or callbacks. Events only apply to DOM. I'd suggest that you try to go with callbacks and do things like this:
function foo(data, callback)
{
// do things with data
callback(data);
}
function bar(data)
{
console.log('callback can has', data);
}
foo('baz', bar);
It's a rough example, but should give you the idea.
One option is to wrap your data into a heavier object.
var Watching = function(){
var a;
this.getA(){
return a;
};
this.setA(value){
a = value;
this.trigger('watch');
};
his.watchA(callback){
this.bind('watch', callback);
};
};
var obj = new Watching();
obj.watchA(function(){ alert('changed'); });
obj.setA(2);
This doesn't answer your question exactly, but it may solve your problem:
make your variable as html content of an element, then use jQuery change() event
<script>
document.write("<div id='test'>"+test+"</div>";
$("#test").change(function(){//your script here});
</script>
You can create a class to be notified when your variable changed.
this is the class:
class ListeningVariable {
constructor(val, changeHandler) {
this.val = val;
this.changeHandler = changeHandler
}
set value(val) {
if (this.val !== val) {
this.changeHandler(val);
}
this.val = val;
}
changeHandler(val) {}
}
Then you can create an instance of this class instead of your variable:
let myVar = new ListeningVariable(25/*initialize*/, function(val) {
console.log("variable Changed to:", val);
}/*handler function*/);
And when you want to change your variable, just use this code:
myVar.value = 20; // calls the changeHandler function
myVar.value = 20; // does't call the changeHandler function
myVar.value = 40; // calls the changeHandler function
You can do something like this with setting intervals to keep track of change:
var dataToChange = 1;
var key = dataToChange;
var int = setInterval(() => {
if (dataToChange != key) {
console.log('changed'); /// if data changes
clearInterval(int);
} else {
console.log('nothing changed'); /// while nothing changes
}
}, 3000);
setTimeout(() => {
///// supposedly this is when the variable changes
dataToChange = 2;
}, 9000);
The below function will poll for changes in the test variable every 5 seconds:
// initialize test variable globally
var test = 1;
// global variable to store the previous value of test
// which is updated every 5 seconds
var tmp = test;
setInterval("pollForVariableChange()", 5000);
function pollForVariableChange() {
if (tmp != test) {
alert('Value of test has changed to ' + test);
}
tmp = test;
}

Categories

Resources