using setTimeout in javascript to do simple animation - javascript

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.

Related

How to check if an api call has completed

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);
}
}
}

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');
}

Why object property became undefined when using setInterval

As below code, I make an object named "test", and give it properties and method.
The property came from its argument.
And I try to call the method every 2 sec after onload, and the result shows undefined.
But if I only call the method not using setInterval(), like this
window.onload = function() {
giveword.showWord();
}
I'll be able to show the text "Hi".. Why is that?
var giveword = new test("Hi");
function test(word) {
this.word = word;
}
test.prototype.showWord = function() {
document.getElementById("msg_box").innerHTML = this.word;
}
window.onload = function() {
setInterval(giveword.showWord, 2000);
}
Thanks for help...
The reason is because in your test.prototype.showWord function your this object is referring to the context in which the function is called, which is the window object when called from setInterval.
I think what you want to do is use a closure to make the context of showWord() be the giveword instance like this:
var giveword = new test("Hi");
function test(word) {
this.word = word;
}
test.prototype.showWord = function() {
document.getElementById("msg_box").innerHTML = this.word;
}
window.onload = function(){
setInterval(function(){giveword.showWord();}, 2000); // <<-- here's the closure
}
The difference is that with the closure you're telling the setInterval function to call a function within the context as it was when the setInterval was declared. When setInterval was declared there was a variable in scope called giveword that had a method showWord() that returns the value of your initial input. (Closures are hard to explain, and I'm afraid you'd be best served by someone else explaining them if you need more info.)
This solution this now so easy, use an arrow function in setInterval. Here is an example using setInterval inside of an object method.
const mobile = {
make: 'iPhone',
model: 'X',
battery: 10,
charging: false,
charge: function() {
if(this.battery < 100) {
this.charging = true;
console.info('Battery is charging...');
let interval = setInterval(() => {
this.battery = this.battery + 10;
console.info(mobile.battery);
if( this.battery === 100){
this.charging = false;
clearInterval(interval);
console.info('Battery has finished charging.');
}
}, 100);
}
else {
console.info('Battery does not need charging.');
}
}
}

Javascript and Callbacks

I wrote a JavaScript function, and it was working perfectly, but it was pretty long, so I wanted to break it up into smaller functions. I thought this would be easy (and maybe it is) but I'm running into issues!
So the structure of my code is as follows:
getPosition: function(a) {
if (true) {
position = this.getPoint(a);
}
},
getPoint: function(a) {
var position;
var options = a.target.parentElement.children;
[].forEach.call(options, function(option){
if (option.type == "point") {
position = this.getNewPoint(a, option);
} else if (option.type == "line") {
position = this.getNewLine(a, option);
}
}
return position;
},
getNewPoint: function(a, option){
...
return point;
},
getNewLine: function(a, option){
...
return line;
}
Trying this gave me the error that this.getNewPoint and this.getNewLine were not defined. That makes sense because of scope, so I decided to try using a callback:
getPosition: function(a) {
if (true) {
position = this.getPoint(a, this.getNewPoint, this.getNewLine);
}
},
getPoint: function(a, pointCallback, lineCallback) {
var position;
var options = a.target.parentElement.children;
[].forEach.call(options, function(option){
if (option.type == "point") {
position = pointCallback(a, option);
} else if (option.type == "line") {
position = lineCallback(a, option);
}
}
return position;
},
getNewPoint: function(a, option){
...
return point;
},
getNewLine: function(a, option){
...
return line;
}
This get's the functions to be called as wanted, however, (I'm guessing that) because of the asynchronous nature of Javascript, the code is continuing without the callbacks completing, so the return value is never being returned. It looks like when I put in some console.log() to test it it started working. I'm guessing this is because it's forcing the code to slow down. Any ideas?
UPDATE:
Because of the awesome help I've received I've got it working to a point. So right now it works as long as the getNewLine function is never called. So I'm thinking there's something wrong in the way my getNewLine function is returning, since it's breaking everything! So here's a but more detail on that function:
getNewLine: function(a, option){
var line;
var endPoints = option.points;
for (var i = 0; i < (endPoints.length - 1); i++) {
... math here
if (distance <= linePadding) {
if (true) {
line = option;
return line; //Want to break the loop and for the function to return
}
}
}
return line;
}
You haven't introduced anything truly asynchronous, just a different method of applying a function. There's a much easier fix to your problem than using callback; save a reference to the correct this:
getPoint: function(a) {
var self = this; // <--
var options = a.target.parentElement.children;
[].forEach.call(options, function(option){
if (option.type == "point") {
newPoint = self.getNewPoint(a, option); // <--
} else if (option.type == "line") {
newLine = self.getNewLine(a, option); // <--
}
});
},
As for why your new solution doesn't work, it looks like you aren't passing the callbacks with the right context. First off, I believe you meant to type
position = this.getPoint(a, this.getNewPoint, this.getNewLine);
But the problem with this is that you, again, lose the correct this context. You could fix this by explicitly setting it using .bind
position = this.getPoint(a, this.getNewPoint.bind(this), this.getNewLine.bind(this));
Bind creates a copy of the given function where the this context is explicitly set.
I actually wrote an answer explaining how this is determined here. And as Felix Kling pointed out, .forEach accepts another argument which sets the context of this:
[].forEach.call(options, function(option) {
// Your same code as before
}, this); // <-- Set the context
There is no asynchronous code here. Just because you are passing a function as a parameter doesn't mean it's asynchronous.
The issue you are having is that getPoint isn't returning anything! You need a return statement for it to return anything.
As for the first example, the value of this changes every time you enter a new function(){}. this is based on how the function is called. Inside the forEach, this is the element in the "array" the global window object, not your object.
You can "backup" this to a variable and then use that inside the forEach. You can set the value of this in the forEach by passing it after the callback.
getPoint: function(a) {
var options = a.target.parentElement.children,
position;
[].forEach.call(options, function(option){
if (option.type == "point") {
position = this.getNewPoint(a, option);
} else if (option.type == "line") {
position = this.getNewLine(a, option);
}
}, this);
return position;
},
What you think this is is changing scopes. this always points to the most recent parent function. So if you separate the code out into multiple functions the this variable changes depending on the function.
One way to work around this is, when calling one function from another you can set the this within the function using the .call or .apply methods.
.call takes the this scope as the first parameter, and all following parameters are the actual parameters passed into the called function. (argument = parameter -1).
.apply takes the this scope as the first parameter, and its second parameter is an array that will be passed into the called function as it's parameter.
So in this case I would suggest
getPosition: function(a) {
if (true) {
position = this.getPoint.call(this, a);
}
},
getPoint: function(a) {
var position;
var options = a.target.parentElement.children;
[].forEach.call(options, function(option){
if (option.type == "point") {
position = this.getNewPoint.call(this, a, option);
} else if (option.type == "line") {
position = this.getNewLine.call(this, a, option);
}
}
return position;
},
getNewPoint: function(a, option){
...
return point;
},
getNewLine: function(a, option){
...
return line;
}

Passing JavaScript to Closure in JavaScript

I'm learning how to actually use JavaScript. I've run into a situation where I'm getting an error. The error is: TypeError: 'undefined' is not an object (evaluating 'this.flagged'). I've narrowed down my code to where its happening. My code looks like this:
var flagged = false;
var intervals = [];
return {
flagged: flagged,
intervals: intervals,
createInterval : function (options) {
var defer = $q.defer();
if (this.throwsError) {
defer.reject('There was an error creating the interval.');
} else {
this.intervals.push(
$interval(function() {
console.log('here 1');
console.log(this.flagged);
},
1000
));
}
}
};
The error gets thrown at the: console.log(this.flagged); I'm guessing it has to do with the fact that "this" isn't visible. Yet, if "this" isn't visible, I'm not sure how to get the value for flagged. Can someone please explain to me what I need to do to get the value for flagged?
Thank you!
When you are using this inside $interval it won't be pointing to your original object, however, you can do this:
var flagged = false;
var intervals = [];
return {
flagged: flagged,
intervals: intervals,
createInterval : function (options) {
var defer = $q.defer(),
self = this;
if (this.throwsError) {
defer.reject('There was an error creating the interval.');
} else {
this.intervals.push(
$interval(function() {
console.log('here 1');
console.log(self.flagged);
},
1000
));
}
}
};
notice var self = this;
In JavaScript,
var flagged
will be a scoped variable, i think what you need here is a global scope variable for that, simply remove var from behind it.
flagged = false;
that should do the trick.

Categories

Resources