Strange behaviour when calling an object more than once - javascript

I have some code here that basically fades in and out some text from an array.
If I call it once, it works as intended. IE, it displays some text for 3 seconds and the fades in the next text from array.
If however I try to restart the animation again, it seems to run twice as fast. Can anyone help me? I think I can cancelling the previous animation correctly
In the code below, I call rotator.start(); just to demo the problem I am having. Just call it once to see how it should behave.
http://jsfiddle.net/zmTAC/3/
<div id="foobar"></div>
<script>
var rotator = {
quoteIndex: -1,
duration: 500,
delay: 3000,
play: false,
quotes: [],
theElement: null,
start: function (quotes, theElement) {
this.quoteIndex = -1;
this.quotes = quotes;
this.theElement = theElement;
this.stop();
this.play = true;
this.showNextQuote();
return this;
},
showNextQuote: function () {
this.quoteIndex = (this.quoteIndex + 1) % this.quotes.length;
if (this.play) {
$(this.theElement).html(this.quotes[this.quoteIndex])
.fadeIn(this.duration)
.delay(this.delay)
.fadeOut(this.duration, this.showNextQuote.bind(this));
}
return this;
},
stop: function () {
this.play = false;
$(this.theElement).stop(true, true).show();
return this;
}
};
rotator.start(["foo1", "bar1"], "#foobar");
rotator.start(["foo2", "bar2"], "#foobar");
rotator.start(["foo3", "bar3"], "#foobar");
rotator.start(["foo4", "bar4"], "#foobar");
rotator.start(["foo5", "bar5"], "#foobar");
rotator.start(["foo6", "bar6"], "#foobar");
rotator.start(["foo7", "bar7"], "#foobar");
rotator.start(["foo8", "bar9"], "#foobar");
rotator.start(["foo9", "bar9"], "#foobar");
rotator.start(["foo0", "bar0"], "#foobar");
</script>

It looks like $.stop() is not cancelling the completed callback to $.fadeOut(). I added a third element to the quotes array in the final call and every other quote was shorter (i.e. the second, then the first(in the second loop), then the third, etc.)
I'm not certain that is the cause (and if it is it is a bug in $.stop()) but a hack (but it works) is to create a container element which you compeltely remove in rotator.stop() and (re)create in rotator.start(). Like this:
var rotator = {
quoteIndex: -1,
duration: 500,
delay: 3000,
play: false,
quotes: [],
theElement: null,
start: function (quotes, theElement) {
this.quoteIndex = -1;
this.quotes = quotes;
this.stop();
this.theElement = theElement;
$(this.theElement).html('<span class="rotator"/>');
this.play = true;
this.showNextQuote();
return this;
},
showNextQuote: function () {
if (this.play) {
this.quoteIndex = (this.quoteIndex + 1) % this.quotes.length;
$('.rotator', this.theElement).html(this.quotes[this.quoteIndex])
.fadeIn(this.duration)
.delay(this.delay)
.fadeOut(this.duration, this.showNextQuote.bind(this));
}
return this;
},
stop: function () {
this.play = false;
$('.rotator', this.theElement).stop(true, true).remove();
$(this.theElement).show();
return this;
}
};
See it working in this fiddle

By calling start method you are overriding the quotes. It is playing between foo0 and bar0. What you want to do is pass a single array of quotes like so:
rotator.start(["foo1", "bar1", "foo2", "bar2", "foo3", "bar3"], "#foobar");
Works fine on Firefox and IE for me. JSFiddle I also changed to jQuery 1.11.0 as 1.10 didn't work at all on IE.
EDIT
I am not sure what went wrong on jQuery side or on browser side. What I know you were doing a known mistake with the this.play variable. You set it to false, then to true, and then checked if it was true in the same function. If you run this function twice at the same time what you might get is:
this.stop(); // second execution is on this line
this.play = true; // first execution is on this line
this.showNextQuote();
What might happen is the second execution sets this.play to false and then the first execution sets it to true. What happens next in showNextQuote method is run by both executions as this.play is true for both (showNextQuote function checks if this.play is true).
I am not sure if it was the problem and I am pretty sure it was not the problem but what I showed you is what happens when you try to run multiple asynchronous operations. I am pretty sure it is buggy jQuery. Maybe newer version fixed this?
Maybe some other community member has more to say on this?
Solution
EDIT2
Thinking of it, JS would not allow it to happen as it would not execute this code in a separate threads. I wont delete it though as it explains you how easy it is to make this mistake with asynchronous jQuery that might be causing your problem.

can you try this instead of the commented line in your stop method
//$(this.theElement).stop(true, true).show();
$(this.theElement).finish().show();
working example here

Related

JavaScript methods: Can't stop propagation

I have an object literal which represents a page structure of an app. If a certain condition is met, I want to skip a page and go to the next one.
Please take a look at the following code:
var someObj = {
"page-1": {
before: function(x){
switch(x){
case true:
break;
case false:
someObj['page-2'].init(); // HERE, I want to skip to the page-2 init() method, **and not go back!**
return false;
break;
}
},
init: function(){
this.before(false);
alert("This is page 1!"); // I don't want to see this!
},
},
"page-2": {
init: function(){
alert("this is page 2!"); // After this alert, I want to STOP, and not go back to the page-1 init() method!
return false;
},
},
}
someObj['page-1'].init();
I can't work out how to stop after the "This is page 2!" alert - I always get 2 alerts. None of the return false work? How can I do this?
Change before() to always return a boolean and make the remainder of the page-1 init() method conditional:
before: function(x){
if (!x) {
someObj['page-2'].init();
}
return x;
},
init: function(){
if (this.before(false)) {
alert("This is page 1!");
}
},
You can either have all your functions pay attention to return values from other functions they call, implementing an "abort" protocol of some kind, or you can throw an exception.
"page-2": {
init: function(){
alert("this is page 2!");
throw new Error("Cease and desist");
},
},
The choice between the two approaches depends on your needs.
What I would do is actually check to see what returns from your before() function and based on that value execute the rest of the init() method, like this:
if(this.before(false)){
do stuff
}else{
do something else
}

Javascript: uncaught typeerror undefined is not a function for object methods

I make a simple quiz game. Here is some relevan methods that i have inside one object.
But doesn't work. I always get an error within 'rightAnswerGot' function. Console drops
"uncaught typeerror undefined is not a function for object methods" for this.addVariantsHtml(this.updateCharacter());
BasicGame.Game.prototype = {
actionOnClick: function (button) {
var log;
if(button.value==this.char_bubble.text) {
setTimeout(this.rightAnswerGot,1000);
} else {
// wrong
swoshsound.play();
}
console.log(log);
},
rightAnswerGot: function (){
this.addVariantsHtml(this.updateCharacter());
},
addVariantsHtml: function(id) {
this.answer = this.getAnswersVariants(id);
for (var i = 0; i < 6; i++) {
this.button[i].value = this.answer[i]['trans'];
this.button[i].char_id = this.answer[i]['id'];
this.ans_text[i].setText(this.answer[i]['trans']);
}
},
updateCharacter: function() {
var i = this.getRandomCharacter();
console.log("updateCharacter: "+i + " " +this.chars[i]);
this.char_bubble.setText(this.chars[i].getPath());
return i;
}
}
The aim is to froze the game for a second, when user choose the right answer, and than go to next question. Any ideas why does it happens?
Thanks
Looks like a classic JavaScript scope issue to me. However as you've tagged this question as using Phaser, I would suggest you use a Phaser Timer event to avoid scope problems. Specifically:
setTimeout(this.rightAnswerGot,1000);
replace it with:
this.game.time.events.add(Phaser.Timer.SECOND, this.rightAnswerGot, this);
Which will create a single 1 second timer that fires only once, calling your function at the end of it. You can use 1000 instead of Phaser.Timer.SECOND of course.
I would image that whats happening is that its trying to call the this.addVariantsHtml method, before its calling this.updateCharacter and getting the ID.
So your probably expecting that when it runs, for it to be something like:
this.addVariantsHtml(1);
But its actually trying to run
this.addVariantsHtml(this.updateCharacter());
So just do this:
var id = this.updateCharacter();
this.addVariantsHtml(id);
Either that or you need to look into method chaining/piping, which is just complicated and doesnt need to be used for this situation, but is interesting :)
Ok I found something that made it work!!
Here is a solution:
actionOnClick: function (button) {
var log;
if(button.value==this.char_bubble.text) {
var context=this;
setTimeout(function() {
context.addVariantsHtml(context.updateCharacter());
},1000);
} else {
// wrong
swoshsound.play();
}
console.log(log);
},

Calling functions in two different ways - JavaScript

I'm very new to JavaScript, so my apologies if this answer is glaringly obvious or I'm barking up the wrong tree!
What's the difference in the following code snippets:
function primeAddNum(innerHTML) {
return function() {
addNum(innerHTML);
return false;
};
}
var func = primeAddNum(innerHTML);
The second one:
var func = function() {
return function() {
addNum(innerHTML);
return false;
};
}();
The top one works the way I'd like it to, but not the bottom, but that's not overly important to me. What I want to know is the logic behind each block, because I just can't see the difference!
The problem with the second block is that innerHTML is undefined there, since you're not passing it. They will become equivalent if you change it to:
var func = function(innerHTML) {
return function() {
addNum(innerHTML);
return false;
};
}(innerHTML);
Well with the second one you can only create a func once. But with first one, you can create many:
var func1 = primeAddNum(innerHTML);
var func2 = primeAddNum(someOtherInnerHTML);
there is no difference, you can use both without any problems

Is it possible to pause/resume/manipulate a swiffyobject from JS?

There seems to be little support or discussion around regarding Google Swiffy (http://swiffy.googlelabs.com/).
Is it possible to effectively pause/resume/manipulate a swiffyobject from JS?
Using standard Google output, I noticed the swiffyobject could be found in console with a few properties; notably frameRate. Could this property be manipulated for example?
For the latest Swiffy release (Swiffy runtime version 5.2 https://www.gstatic.com/swiffy/v5.2/runtime.js) I did this.
1.Use jsbeautifier.org as mentioned in samb's post.
2.Find the function containing .start(). In my case...
db(N, function () {
var a = this.Dg;
this.ck(function () {
a.start()
})
});
db(Yj[I], Yj[I].start);
3.Duplicate this function with a different name, and replace start() with stop()
myNewFunction(N, function () {
var a = this.Dg;
this.ck(function () {
a.stop()
})
});
myNewFunction(Yj[I], Yj[I].stop);
4.Find the declaration of the function containing .start(). In my case db.
function db(a, b) {
return a.start = b
}
5.Duplicate this function and call it the same as the new function you created with stop() in and replace start with stop. In my case myNewFunction.
function myNewFunction(a, b) {
return a.stop = b
}
That's it.
Now you can call my anim.stop();
e.g.
//create anim
var anim = {swiffy code};
var myAnim = new swiffy.Stage(document.getElementById('animContainer'), anim);
myAnim.start();
//some button click
myButton.on('click',function(){
myAnim.stop();
});
Sorry for my english I'm french;)
I was looking for a solution to be able to properly handle animation Swiffy.
Since the new version (5.0) google code has changed and I can no longer maniupler animation with small hacks found on the net ...
For cons, I coded force to find a solution .. which seems to me very simple and clean .. (without touching the source Swiffy!)
In fact any part of this post : swiffy / javascript
Can be recovered with flashvars Swiffy (in as2 and as3 it should work too ..)
the side javascript can do this kind of things:
function playMovie(){
stage.setFlashVars('myresponse=play');
return false;
}
function stopMovie(){
stage.setFlashVars('myresponse=pause');
return false;
}
and the side of the flash in a function enterFrame ... :
_root.onEnterFrame = function(){
switch(_level0.myresponse){
case 'play':
_root.play();
break;
case 'pause':
_root.stop();
break;
default :
break;
}
_level0.myresponse = undefined;
}
and that's it!
To you organize the methods you want but .. it works;)
Must retake the undefined variable if you want to reuse it later ;)
Having un-minified the runtime.js - it was possible to achieve the behaviour I wanted.
On line 3312 (unminified - jsbeautifier.org)
M.start = function (arg) {
this.T[Qa]();
if(arg){
this.cb.start(arg)
}else{
this.cb.start()
}
};
And on line 3823:
M.start = function(arg) {
if(arg){
console.log(arg);
window.clearInterval(window.pauseAnimation)
}else{
window.pauseAnimation = window.setInterval(Ob(this.ne, this), 40 );
if (!this.ie) this.ie = !0, this.ne(), window.pauseAnimation
}
};
Then using console, it is possible to pause/resume your animation using:
stage.start(true) // PAUSE the animation.
stage.start() // RESUME the animation.

Clear javascript on dynamic load

I have a javascript plugin for a special image scroller. The scroller contains a bunch of timeout methods and a lot of variables with values set from those timeouts.
Everything works perfectly, but for the site I am working on it is required that the pages are loaded dynamically. The problem with this is when i for instance change the language on the site this is made by jquery load function meaning the content is dynamically loaded onto the site - AND the image slider aswell.
NOW here is the big problem! When I load the image slider for the second time dynamically all my previous values remains as well as the timers and everything else. Is there any way to clear everything in the javascript plugin as if it where like a page reload?
I have tried a lot of stuff so far so a little help would be much appreciated!
Thanks a lot!
You might want something like that to reload scripts:
<script class="persistent" type="text/javascript">
function reloadScripts()
{ [].forEach.call(document.querySelectorAll('script:not(.persistent)'), function(oldScript)
{
var newScript = document.createElement('script');
newScript.text = oldScript.text;
for(var i=0; i<oldScript.attributes.length; i++)
newScript.setAttribute(oldScript.attributes[i].name, oldScript.attributes[i].value);
oldScript.parentElement.replaceChild(newScript, oldScript);
});
}
// test
setInterval(reloadScripts, 5000);
</script>
As far as I know, there's no other way to reset a script than completely remove the old one and create another one with the same attributes and content. Not even clone the node would reset the script, at least in Firefox.
You said you want to reset timers. Do you mean clearTimeout() and clearInterval()? The methods Window.prototype.setTimeout() and Window.prototype.setInterval() both return an ID wich is to pass to a subsequent call of clearTimeout(). Unfortunately there is no builtin to clear any active timer.
I've wrote some code to register all timer IDs. The simple TODO-task to implement a wrapper callback for setTimeout is open yet. The functionality isn't faulty, but excessive calls to setTimeout could mess up the array.
Be aware that extending prototypes of host objects can cause undefined behavior since exposing host prototypes and internal behavior is not part of specification of W3C. Browsers could change this future. The alternative is to put the code directly into window object, however, then it's not absolutely sure that other scripts will call this modified methods. Both decisions are not an optimal choice.
(function()
{ // missing in older browsers, e.g. IE<9
if(!Array.prototype.indexOf)
Object.defineProperty(Array.prototype, 'indexOf', {value: function(needle, fromIndex)
{ // TODO: assert fromIndex undefined or integer >-1
for(var i=fromIndex || 0; i < this.length && id !== window.setTimeout.allIds[i];) i++;
return i < this.length ? i : -1;
}});
if(!Array.prototype.remove)
Object.defineProperty(Array.prototype, 'remove', { value: function(needle)
{ var i = this.indexOf(needle);
return -1 === i ? void(0) : this.splice(i, 1)[0];
}});
// Warning: Extensions to prototypes of host objects like Window can cause errors
// since the expose and behavior of host prototypes are not obligatory in
// W3C specs.
// You can extend a specific window/frame itself, however, other scripts
// could get around when they call window.prototype's methods directly.
try
{
var
oldST = setTimeout,
oldSI = setInterval,
oldCT = clearTimeout,
oldCI = clearInterval
;
Object.defineProperties(Window.prototype,
{
// TODO: write a wrapper that removes the ID from the list when callback is executed
'setTimeout':
{ value: function(callback, delay)
{
return window.setTimeout.allIds[window.setTimeout.allIds.length]
= window.setTimeout.oldFunction.call(this, callback, delay);
}
},
'setInterval':
{ value: function(callback, interval)
{
return window.setInterval.allIds[this.setInterval.allIds.length]
= window.setInterval.oldFunction.call(this, callback, interval);
}
},
'clearTimeout':
{ value: function(id)
{ debugger;
window.clearTimeout.oldFunction.call(this, id);
window.setTimeout.allIds.remove(id);
}
},
'clearInterval':
{ value: function(id)
{
window.clearInterval.oldFunction.call(this, id);
window.setInterval.allIds.remove(id);
}
},
'clearTimeoutAll' : { value: function() { while(this.setTimeout .allIds.length) this.clearTimeout (this.setTimeout .allIds[0]); } },
'clearIntervalAll': { value: function() { while(this.setInterval.allIds.length) this.clearInterval(this.setInterval.allIds[0]); } },
'clearAllTimers' : { value: function() { this.clearIntervalAll(); this.clearTimeoutAll(); } }
});
window.setTimeout .allIds = [];
window.setInterval .allIds = [];
window.setTimeout .oldFunction = oldST;
window.setInterval .oldFunction = oldSI;
window.clearTimeout .oldFunction = oldCT;
window.clearInterval.oldFunction = oldCI;
}
catch(e){ console.log('Something went wrong while extending host object Window.prototype.\n', e); }
})();
This puts a wrapper method around each of the native methods. It will call the native functions and track the returned IDs in an array in the Function objects of the methods. Remember to implement the TODOs.

Categories

Resources