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.
Related
I have an external JS file that adds a window.onload function to the page.
The basic premise is that it loads up a popup window on your website whenever the user clicks on certain link class. It's written in PHP / JS so assume that the function works by itself.
Inside this JS file has the following code.
window.onload = function() {
var anchors = document.getElementsByClassName("vyper-triggers");
for (var i = 0; i < anchors.length; i++) {
var anchor = anchors[i];
anchor.onclick = function() {
if (isMobile.any()) {
window.open("$url");
} else {
document.getElementById("clickonthis").click();
}
}
}
}
Now my problem is when my user wants to add 2 different popup windows, the window.onload function doesn't stack. Also because this is an embedded javascript that my user adds himself, there is no way for me to put both functions inside one big window.onload function.
My user might put one JS file in one area of their site, and another JS file in another area, if that makes sense.
So how do I make it so that the window.onload function will stack no matter the placing of these external JS files on the page and considering that each function must be kept separate?
Rather than setting window.onload, you should use addEventListener. Listeners added this way will stack automatically.
window.addEventListener('load', function() {
console.log('First listener');
});
window.addEventListener('load', function() {
console.log('Second listener');
});
window.addEventListener('load', function() {
console.log('Third listener');
});
If you have to support versions of IE before IE9, there's a polyfill which will make this work correctly.
Probably you have multiple files and u want to check something onload.
Let's implement a basic function to add other functions and run all of them when the event onload is triggered.
So, first we check if windows.onload has a function object if not add our function. If is contains a function object merge it with our function like this:
function addLoadEvent(callback) {
const previous = window.onload
if (typeof previous === 'function') {
window.onload = (e) => {
if (previous) previous(e)
callback(e)
}
}
...
}
This is an example how to use it:
function addLoadEvent(callback) {
const previous = window.onload
if (typeof previous === 'function') {
window.onload = (e) => {
if (previous) previous(e)
callback(e)
}
} else {
window.onload = callback
}
}
function func1() {
console.log('This is the first.')
}
function func2() {
console.log('This is the second.')
}
addLoadEvent(func1);
addLoadEvent(func2);
addLoadEvent(() => {
console.log('This is the third.')
document.body.style.backgroundColor = '#EFDF95'
})
I have a sample module in js that is suppose to manage Drag and Drop file upload.
The code seems to work for 'dragenter' event function but when I drop the file, and 'drop' event should call the dropped function, the code always forwards to the file path.
Here is the code sample
var testModule = (function testBuilder(){
function call(evt) {
evt.preventDefault();
console.log('works');
}
function dropped(evt) {
evt.preventDefault();
console.log('file dropped');
}
var element = document.getElementById('testBlock');
function init() {
element.addEventListener('dragenter', call, false);
element.addEventListener('drop', dropped, false);
}
publicAPI = {
init: init
};
return publicAPI;
})();
window.onload = function() {
testModule.init();
};
and a jsbin here https://jsbin.com/redixucate/edit?js,console,output
If anyone can figure out why it keeps redirecting the file path, I would rly appreciate it.
Add 'dragover' event with preventDefault and it should work.
Inside your init():
element.addEventListener('dragover', over, false);
and over function:
function over(e) {
e = e || window.event;
if(e.preventDefault) {
e.preventDefault();
}
}
Also add the same prevention to your other two functions ..
See https://jsbin.com/xemovariwu/1/edit?js,console,output
Also see this question/answer.
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've been working on webpages for a range of touch screen devices, and one of the most consistent problems is how touch events are handled.
Is there a nice way to only call a function once even when multiple (roughly) simultaneous events call it?
e.g.
$("body").on("mousedown touchstart MSPointerDown", function () {
alert("This message will appear multiple times on some devices.");
})
I've thought about using a timeout so the function can only be called once every 200 milliseconds or something similar (off the top of my head and untested):
var allowed = true;
$("body").on("mousedown touchstart MSPointerDown", function () {
if(allowed){
allowed = false;
alert("This message will hopefully only appear once!");
setTimeout(function () { allowed = true }, 200);
}
})
(For this question, I am NOT looking for plugin suggestions, I am aware there are lots of touch event plugins)
Is there a proper/nicer way to use multiple events as possible triggers for a single function? Could I alias the events in some way without breaking their other uses?
In effect, you're looking to take only the first event type that comes through and ignore all the others. This will still fire for future clicks/touches. Enter closures.
$(document).ready(function() {
function alertClosure() {
var eventType = null;
function doAlert(e) {
if (!eventType) {
eventType = e.type; // only the first eventType we get will be registered
}
if (e.type == eventType) {
alert("This message will hopefully only appear once!: " + e.type);
}
}
return doAlert;
}
$("body").on( "mousedown touchstart MSPointerDown", alertClosure() );
});
http://plnkr.co/edit/oz48d3
You could use $.one (rather than $.on)
Here : $.one documentation on jquery.com
If you want it to be subsequently called then you could rebind the handler on a timeout, something like this:
function handler(){
var called = false;
return function(ev){
if(!called){
called = true;
$("ul#messages").append($("<li>").text("event"));
setTimeout(bind, 1000); // rebind after a suitable pause
}
}
}
function bind(){
$("ul#messages").one("click", new handler())
};
$(function(){
bind();
});
https://jsfiddle.net/p3t6xo48/5/
This allows each bound handler to be run once, and once only, for multiple events, then it's rebound after a suitable pause.
In my page there is a frame that belongs to the same domain. The content of this frame is varied and relatively unpredictable. Whenever a user clicks a button (inside the frame) that performs a post, I need to execute a function that performs some UI tasks. The problem is that I cannot edit the source of these frames for reasons beyond my control. Some of these buttons are simple form submit buttons, but others do not directly submit the form, but instead have an onclick handler that performs some checks and might submit.
Here is the problem: How do I detect if one of these onclick handlers called form.submit()? If there's no handler, then obviously I can set up a handler for onsubmit(), but is not the case for all of these buttons.
This is my code so far:
function addEventBefore(element, type, before, after) {
var old = element['on' + type] || function() {};
before = before || function() {};
after = after || function() {};
element['on' + type] = function () {
before();
old();//I can't modify this old onclick handler
after();
};
}
function setup() {
console.log('setup');
}
function takedown() {
// In this method, I want to know if old() caused a form submit
console.log('takedown');
}
function $includeFrames(jQuery, selector) {
return jQuery(selector).add(jQuery('iframe').contents().find(selector));
}
var a = $includeFrames($, 'input[type="submit"], input[type="button"]').each(function() {
var elem = $(this)[0];
addEventBefore(elem, 'click', setup, takedown);
});
In the onload event of the iframe you'll need to hook up an event listener to each form in the iframed page. You need to do this on every load, as each fresh page needs new listeners.
$("#someIframe").on('load',function() {
$(this).contents().find("form").each(function() {
$(this).on('submit',function() {... your code...})
})
}
The solution that worked for me came from a friend of mine. The solution is to shim the form.submit() function.
$(function() {
var el = document.getElementById('myform');
el.submit = function(fn) {
return function() {
myFunctionGoesHere();
fn.apply(this, arguments);
};
}(el.submit);
});
Here is a working example: http://jsfiddle.net/hW6Z4/9/