I have the following code:
function initializeUI(){
//More code here
models.player.addEventListener('change:context', contextChanged);
//More code here
}
var timerId = null;
function contextChanged() {
models.player.load('context').done(function (player) {
if (player.context.uri == tempPlaylist.uri) {
//User started playing this context. Start timer.
timerId = setInterval(updateTimerView, 1000);
}
else {
//User is playing a different context. Delete temp playlist and timer.
models.Playlist.removeTemporary(tempPlaylist);
clearInterval(timerId);
}
});
}
function updateTimerView(){
//UI related code goes here
}
I would assume that change:context would only be fired when the context (in my case, tempPlaylist) is changed, but it turns out that it's also being fired when the track is changed, played, or paused. This can't be the desired functionality... otherwise it would defeat the purpose of having the change:track and change:playing events.
This is causing a lot of trouble for me mainly because when the track is changed, played, or paused, contextChanged is called, but player.context.uri hasn't changed, so more and more timers keep getting added. What am I missing here?
I agree, looks like a bug. I'd probably change it to something like this
require(['$api/models'], function(models) {
models.player.addEventListener('change:context', contextChanged);
var last_context=null;
function contextChanged(e) {
if(last_context != e.oldValue.uri) {
last_context = e.oldValue.uri;
console.log('hola, new context uri - ' + last_context);
}
else {
console.log('faux context change');
}
}
});
Related
I'm writing a game using HTML5 canvas and Javascript. I'm using setInterval to animate the game and check for events at regular intervals. I've written multiple levels for the game, but I can't seem to escape the setInterval when a given level ends in order to start the next one. The code accurately detects when a level is won or lost and successfully clears the interval and renders the button, but the button does not fire.
Adding a button was my latest idea. I've also tried removing the canvas using jQuery and inserting a new one. I've also tried using clearRect on the canvas but it doesn't fire either.
Given that I can't return a value from setInterval, what are my options, if any? Is there another way to accomplish the same thing? Is there a separate error with my code that I've overlooked? Thanks!
Game.prototype.win = function(int) {
clearInterval(int);
var content = "<p style='color:white;'>You win</p><br><button id='next-level'>Next Level</button></menu>"
$('#controls').append(content)
};
Game.prototype.lose = function(int) {
clearInterval(int);
var content = "<p style='color:white;'>You Lose</p><br><button id='next-level'>Start Over?</button></menu>"
$('#controls').append(content)
};
Game.prototype.run = funtion () {
$('#start').click( function () {
$('#controls').empty();
var waveOne = new window.CrystalQuest.Wave(this.X_DIM, this.Y_DIM, this, Game.WAVE_ONE)
var game = this
var int = setInterval( function () {
if (waveOne.step() === "lost" ) {
game.lose(int);
} else if (waveOne.step() === "won") {
game.win(int);
}
waveOne.draw(this.ctx)
}, 20)
this.bindKeyHandlers(waveOne);
}.bind(this));
$('#next-level').click( function () {
$('#controls').empty();
...more code...
});
};
To stop a setInterval() you have to store the returned value from the original call to setInterval() in some persistent location and then call clearInterval() on that value.
Because you declared your interval with var as in var int, it was local only to that method and was not available anywhere else in the code.
There are a number of ways to do that in your code. I would probably suggest storing it as an instance variable like this:
Game.prototype.run = funtion () {
$('#start').click( function () {
$('#controls').empty();
var waveOne = new window.CrystalQuest.Wave(this.X_DIM, this.Y_DIM, this, Game.WAVE_ONE)
var game = this;
this.stop();
this.interval = setInterval( function () {
if (waveOne.step() === "lost" ) {
game.lose(int);
} else if (waveOne.step() === "won") {
game.win(int);
}
waveOne.draw(this.ctx)
}, 20)
this.bindKeyHandlers(waveOne);
}.bind(this));
$('#next-level').click( function () {
$('#controls').empty();
...more code...
});
};
Then, you can make a method that will stop the interval like this:
Game.prototype.stop = function() {
if (this.interval) {
clearInterval(this.interval);
this.interval = null;
}
}
And, change your other methods like this:
Game.prototype.win = function(int) {
this.stop();
var content = "<p style='color:white;'>You win</p><br><button id='next-level'>Next Level</button></menu>"
$('#controls').append(content)
};
Game.prototype.lose = function(int) {
this.stop();
var content = "<p style='color:white;'>You Lose</p><br><button id='next-level'>Start Over?</button></menu>"
$('#controls').append(content)
};
For your event handling issues, if you are destroying and recreating a button, then you will lose any event handlers that were attached to any DOM elements that got replaced.
You have two choices for how to fix it:
After you create the new DOM elements, you can again set the event handlers on the new DOM elements.
You can use "delegated event handling". This attaches the event handlers to a parent DOM object that is itself not replaced. The click event bubbles up to the parent and is handled there. The child can be replaced as many times as you want and the event handler will still work.
See these references for how to use delegated event handling with jQuery:
Does jQuery.on() work for elements that are added after the event handler is created?
jQuery .live() vs .on() method for adding a click event after loading dynamic html
JQuery Event Handlers - What's the "Best" method
I have seen similar questions - but not that fix my problem!
I have audio on my page and when one ends, I want the next to start, but I can't even get the ended to trigger...
I cut the code down to this:
function DaisyChainAudio() {
$().on('ended', 'audio','' ,function () {
alert('done');
});
}
This is called from my page/code (and is executed, setting a break point shows that).
As far as I understand this should set the handler at the document level, so any 'ended' events from any 'audio' tag (even if added dynamically) should be trapped and show me that alert...
But it never fires.
edit
With some borrowing from Çağatay Gürtürk's suggestion so far have this...
function DaisyChainAudio() {
$(function () {
$('audio').on('ended', function (e) {
$(e.target).load();
var next = $(e.target).nextAll('audio');
if (!next.length) next = $(e.target).parent().nextAll().find('audio');
if (!next.length) next = $(e.target).parent().parent().nextAll().find('audio');
if (next.length) $(next[0]).trigger('play');
});
});
}
I'd still like to set this at the document level so I don't need to worry about adding it when dynamic elements are added...
The reason it does not fire is, media events( those specifically belonging to audio or video like play, pause, timeupdate, etc) do not get bubbled. you can find the explanation for that in the answer to this question.
So using their solution, I captured the ended event, and this would allow setting triggers for dynamically added audio elements.
$.createEventCapturing(['ended']); // add all the triggers for which you like to catch.
$('body').on('ended', 'audio', onEnded); // now this would work.
JSFiddle demo
the code for event capturing( taken from the other SO answer):
$.createEventCapturing = (function () {
var special = $.event.special;
return function (names) {
if (!document.addEventListener) {
return;
}
if (typeof names == 'string') {
names = [names];
}
$.each(names, function (i, name) {
var handler = function (e) {
e = $.event.fix(e);
return $.event.dispatch.call(this, e);
};
special[name] = special[name] || {};
if (special[name].setup || special[name].teardown) {
return;
}
$.extend(special[name], {
setup: function () {
this.addEventListener(name, handler, true);
},
teardown: function () {
this.removeEventListener(name, handler, true);
}
});
});
};
})();
Try this:
$('audio').on('ended', function (e) {
alert('done');
var endedTag=e.target; //this gives the ended audio, so you can find the next one and play it.
});
Note that when you create a new audio dynamically, you should assign the events. A quick and dirty solution would be:
function bindEvents(){
$('audio').off('ended').on('ended', function (e) {
alert('done');
var endedTag=e.target; //this gives the ended audio, so you can find the next one and play it.
});
}
and run bindEvents whenever you create/delete an audio element.
I've created a Backbone, Marionette and Require.js application and am now trying to add smooth transitioning between regions.
To do this easily* ive decided to extend the marionette code so it works across all my pages (theres a lot of pages so doing it manually would be too much)
Im extending the marionette.region open and close function. Problem is that it now doesnt call the onClose function inside each of my views.
If I add the code directly to the marionette file it works fine. So I'm probably merging the functions incorrectly, right?
Here is my code:
extendMarrionette: function () {
_.extend(Marionette.Region.prototype, {
open : function (view) {
var that = this;
// if this is the main content and should transition
if (this.$el.attr("id") === "wrapper" && document.wrapperIsHidden === true) {
this.$el.empty().append(view.el);
$(document).trigger("WrapperContentChanged")
} else if (this.$el.attr("id") === "wrapper" && document.wrapperIsHidden === false) {
$(document).on("WrapperIsHidden:open", function () {
//swap content
that.$el.empty().append(view.el);
//tell router to transition in
$(document).trigger("WrapperContentChanged");
//remove this event listener
$(document).off("WrapperIsHidden:open", that);
});
} else {
this.$el.empty().append(view.el);
}
},
//A new function Ive added - was originally inside the close function below. Now the close function calls this function.
kill : function (that) {
var view = this.currentView;
$(document).off("WrapperIsHidden:close", that)
if (!view || view.isClosed) {
return;
}
// call 'close' or 'remove', depending on which is found
if (view.close) {
view.close();
}
else if (view.remove) {
view.remove();
}
Marionette.triggerMethod.call(that, "close", view);
delete this.currentView;
},
// Close the current view, if there is one. If there is no
// current view, it does nothing and returns immediately.
close : function () {
var view = this.currentView;
var that = this;
if (!view || view.isClosed) {
return;
}
if (this.$el.attr("id") === "wrapper" && document.wrapperIsHidden === true) {
this.kill(this);
} else if (this.$el.attr("id") === "wrapper" && document.wrapperIsHidden === false) {
//Browser bug fix - needs set time out
setTimeout(function () {
$(document).on("WrapperIsHidden:close", that.kill(that));
}, 10)
} else {
this.kill(this);
}
}
});
}
Why don't you extend the Marionette.Region? That way you can choose between using your custom Region class, or the original one if you don't need the smooth transition in all cases. (And you can always extend it again if you need some specific behavior for some specific case).
https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.region.md#region-class
var MyRegion = Marionette.Region.extend({
open: function() {
//Your open function
}
kill: function() {
//Your kill function
}
close: function() {
//Your close function
}
});
App.addRegions({
navigationRegion: MyRegion
});
Perhaps your issue is that you are not passing a function to your event listener, but instead calling the code directly in the code below.
setTimeout(function(){
$(document).on("WrapperIsHidden:close", that.kill(that));
}, 10)
It is likely that you want something like this:
setTimeout(function(){
$(document).on("WrapperIsHidden:close", function (){ that.kill(that); });
}, 10)
Another possible problem is that you are mixing up your references to this/that in your kill function. It seems like you probably want var view to either be assigned to that.view or to use this rather than that throughout the method.
Answer to your additional problems:
You should try passing the view variable from the close function directly into your kill function because the reference to currentView is already changed to the new view object when you actually want to old view object. The reason this is happening is that you are setting a timeout before executing the kill function. You can see this if you look at the show source code. It expects close, open and then currentView assignment to happen synchronously in order.
I'm using Three.js with the WebGL renderer to make a game which fullscreens when a play link is clicked. For animation, I use requestAnimationFrame.
I initiate it like this:
self.animate = function()
{
self.camera.lookAt(self.scene.position);
self.renderer.render(self.scene, self.camera);
if (self.willAnimate)
window.requestAnimationFrame(self.animate, self.renderer.domElement);
}
self.startAnimating = function()
{
self.willAnimate = true;
self.animate();
}
self.stopAnimating = function()
{
self.willAnimate = false;
}
When I want to, I call the startAnimating method, and yes, it does work as intended. But, when I call the stopAnimating function, things break! There are no reported errors, though...
The setup is basically like this:
There is a play link on the page
Once the user clicks the link, a renderer's domElement should fullscreen, and it does
The startAnimating method is called and the renderer starts rendering stuff
Once escape is clicked, I register an fullscreenchange event and execute the stopAnimating method
The page tries to exit fullscreen, it does, but the entire document is completely blank
I'm pretty sure my other code is OK, and that I'm somehow stopping requestAnimationFrame in a wrong way. My explanation probably sucked, so I uploaded the code to my website, you can see it happening here: http://banehq.com/Placeholdername/main.html.
Here is the version where I don't try to call the animation methods, and fullscreening in and out works: http://banehq.com/Correct/Placeholdername/main.html.
Once play is clicked the first time, the game initializes and it's start method is executed. Once the fullscreen exits, the game's stop method is executed. Every other time that play has been clicked, the game only executes it's start method, because there is no need for it to be initialized again.
Here's how it looks:
var playLinkHasBeenClicked = function()
{
if (!started)
{
started = true;
game = new Game(container); //"container" is an empty div
}
game.start();
}
And here's how the start and stop methods look like:
self.start = function()
{
self.container.appendChild(game.renderer.domElement); //Add the renderer's domElement to an empty div
THREEx.FullScreen.request(self.container); //Request fullscreen on the div
self.renderer.setSize(screen.width, screen.height); //Adjust screensize
self.startAnimating();
}
self.stop = function()
{
self.container.removeChild(game.renderer.domElement); //Remove the renderer from the div
self.renderer.setSize(0, 0); //I guess this isn't needed, but welp
self.stopAnimating();
}
The only difference between this and the working version is that startAnimating and stopAnimating method calls in start and stop methods are commented out.
One way to start/stop is like this
var requestId;
function loop(time) {
requestId = undefined;
...
// do stuff
...
start();
}
function start() {
if (!requestId) {
requestId = window.requestAnimationFrame(loop);
}
}
function stop() {
if (requestId) {
window.cancelAnimationFrame(requestId);
requestId = undefined;
}
}
Working example:
const timeElem = document.querySelector("#time");
var requestId;
function loop(time) {
requestId = undefined;
doStuff(time)
start();
}
function start() {
if (!requestId) {
requestId = window.requestAnimationFrame(loop);
}
}
function stop() {
if (requestId) {
window.cancelAnimationFrame(requestId);
requestId = undefined;
}
}
function doStuff(time) {
timeElem.textContent = (time * 0.001).toFixed(2);
}
document.querySelector("#start").addEventListener('click', function() {
start();
});
document.querySelector("#stop").addEventListener('click', function() {
stop();
});
<button id="start">start</button>
<button id="stop">stop</button>
<div id="time"></div>
Stopping is as simple as not calling requestAnimationFrame anymore, and restarting is to call it it again.
ex)
var pause = false;
function loop(){
//... your stuff;
if(pause) return;
window.requestionAnimationFrame(loop);
}
loop(); //to start it off
pause = true; //to stop it
loop(); //to restart it
var myAnim //your requestId
function anim()
{
//bla bla bla
//it's important to update the requestId each time you're calling reuestAnimationFrame
myAnim=requestAnimationFrame(anim)
}
Let's start it
myAnim=requestAnimationFrame(anim)
Let's stop it
//the cancelation uses the last requestId
cancelAnimationFrame(myAnim)
Reference
I played around with the tutorial of a 2D Breakout Game where they also used requestAnimationFrame and I stopped it with a simple return. The return statement ends function execution if the value of return is omitted.
if(!lives) {
alert("GAME OVER");
return;
}
// looping the draw()
requestAnimationFrame(draw);
I would suggest having a look at the requestAnimationFrame polyfill gibhub page. There are discussions about how this is implemented.
So, after doing some more testing, I've found out that it was, indeed, my other code that posed a problem, not the animation stopping (it was a simple recursion after all). The problem was in dynamically adding and removing the renderer's domElement from the page. After I've stopped doing that, for there was really no reason to do so, and included it once where the initialization was happening, everything started working fine.
I have a function that updates a <div /> via AJAX:
function update() {
<!-- .ajax() -->
setTimeout(update(), 3000);}
}
What I need is that this is not executed when the user is not present on the website, so if there is no movement of the mouse (we will suppose that if move it is in the website) it will not update .mousemove(). By the way, there is any other thing that we can do to know is someone is active on the website?
How can this be done? Thank you in advance!
Edit: probably I explained bad. I need to know the way to only update when there is activity. Like Facebook does with his news feed, the front page. Thanks!
You could use a mousemove handler to track when the user last moved, and then have the process only happen if they last moved the mouse within X seconds. But of course, if the user is sitting there reading something, or if they're a keyboard-oriented kind of person, that will tend to miss that they are there... So you'd probably want to look at keydown as well.
Here's a mousemove example:
jQuery(function($) {
var count = 0, lastmove = new Date();
$(document).mousemove(function() {
++count;
lastmove = new Date();
$('#display').html("Moved " + count + " times");
});
});
Then your update code could do this:
function update() {
if (new Date() - lastmove < 60000) { // 60 seconds
// Really do the update
}
else {
// Check back in a few seconds
setTimeout(update, 3000);
}
}
Off-topic, but you have an error in your update code. You have:
setTimeout(update(), 3000);
...which will call update immediately and then try to use its return value to schedule something to happen in three seconds. If you want the call to update to be scheduled to happen in three seconds, leave off the () after it:
setTimeout(update, 3000);
I think I might have ended up with something such as this. Avoids date arithmetic. Only cares whether there's been some activity since the last update().
window.activeFlag = false;
window.updateDelay = 3000;
$(document).bind('mousemove scroll keydown', function(){ activeFlag = true; });
function update() {
if(activeFlag) {
doWork();
activeFlag = false;
}
}
window.setTimeout(update, updateDelay);
edit: I've discovered a flaw in the code. The following is more appropriate:
window.activeFlag = false;
window.updateDelay = 3000;
$(document).bind('mousemove scroll keydown', function(){ activeFlag = true; });
function update() {
if(activeFlag) {
doWork();
activeFlag = false;
}
window.setTimeout(update, updateDelay);
}
update();
I think there is no easy way to determine if the user is present
I would use a combination of mousemove, scroll, keypress.
var bUpdate = false;
function update() {
if(bUpdate){
///perform your ajax request
}
}
$(document).mousemove(function(){
bUpdate = true;
setTimeout(function(){bUpdate=false;}, 3000);}
});