Illegal Invocation jQuery HTML5 Audio Play - javascript

Background
Basically, this code takes all of the audio tags on the page, and when one finishes it starts the next one in the DOM.
The Issue
When fnPlay is called I receive an Illegal Invocation error.
//THIS CODE FAILS
var lastAudio = null;
$('audio').each(function(index) {
var fnPlay = $(this)[0].play;
if (lastAudio != null) {
lastAudio.bind("ended", function() {
fnPlay();
});
}
lastAudio = $(this);
});
Now I am sure that the rest of the code is fine, because the following worked.
//WORKS GREAT!
var lastAudio = null;
$('audio').each(function(index) {
var lastAudioObj = $(this)[0];
if (lastAudio != null) {
lastAudio.bind("ended", function() {
lastAudioObj.play();
});
}
lastAudio = $(this);
});
Question
Can anybody explain why I couldn't store the play() function inside my variable fnPlay and call fnPlay(), but I could store the object and call the play() function on the object?

This is because of how the context of JavaScript functions work. The context (or this) inside a function is set when it's ran, not when it's set.
When you call lastAudioObj.play();, the play() function is called in the context of lastAudioObj. Inside play(), this is lastAudioObj, so everything works.
When you do fnPlay() however, it has no context. this inside the function will be null (or window). play() doesn't like that, so it throws an exception.
There are a few ways to fix this.
One is to call the function with .call() to manually set the context.
Set the variables like:
var lastAudioObj = $(this)[0];
var fnPlay = lastAudioObj.play;
Then call:
fnPlay.call(lastAudioObj);
You can also use .bind() to set the context when setting the variable.
var lastAudioObj = $(this)[0];
var fnPlay = lastAudioObj.play.bind(lastAudioObj);
Then you can just call it like:
fnPlay();

Related

Accessing a JavaScript / jQuery Instance only once

I have a jQuery function which is called on several events (button click, change etc.)
This function is called in the documentReadyFunction and is feeded with start values..
everytime I call this function, parameters will be passed to the function.
My problem is: I don't want to create a new Object each time I call the function, because if I set a variable which decides if a part of the function is beeing executed or not, will be always overwritten..
What do I have to do, to access the first created instance instead of creating always a new one with every function call..
Down below is a simplyfied version of my function.. Maybe you understand my problem better then.
$.fn.doSomething = function(param1) {
var localParam = param1;
var amIcalledMoreThanOnce = parseInt(0, 10);
if (param1 == 1) {
amIcalledMoreThanOnce = amIcalledMoreThanOnce + 1;
if (amIcalledMoreThanOnce == 1) {
$('#run').val(amIcalledMoreThanOnce);
// fill form fields with URL parameters
// This shall be executed only once after getting the URL vals
} else {
// set the localParam to 0 to exit this loop and reach the outter else..
localParam = 0;
$.fn.doSomething(localParam);
}
} else {
$('#run').val(amIcalledMoreThanOnce);
// use the User Input Data not the URL Params
}
};
$.fn.doSomething(1);
$.fn.doSomething(1);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
<input type="number" id="run">
you can use this pattern:
var nameOfYourFunction = (function() {
var initializedOnlyOnce = {};
return function() {
//use initializedOnlyOnce here.
}
})();
here what you see is; you create and run a function immediately when the code is run. The outer function immediately returns the inner function and it's assigned to nameOfYourFunction. Then you can use the nameOfYourFunction(); just as any other function. However any varible declared in the outer function will be available to the nameOfYourFunction() and initializedOnlyOnce will never be initialized again.

probably moronic js syntax error. Object is null

var fbToggle = document.getElementById("fbToggle");
and later in the script
fbToggle.addEventListener("click", toggle("fbContainer"));
Console tells me that fbToggle is NULL
This is in the document though.
<input type="checkbox" id="fbToggle">
I wasnt using eventListener before, so maybe there is a special order of declaration i'm missing ?
EDIT :
entire js :
function toggle(target) {
var obj = document.getElementById(target);
display = obj.style.display;
if (display == "none") {display = "block"}
else {display = "none"}
}
function init() {
var fbToggle = document.getElementById("fbToggle");
var twitToggle = document.getElementById("twitToggle");
var pinToggle = document.getElementById("pinToggle");
console.log(fbToggle); // NULL
fbToggle.addEventListener("click", toggle("fbContainer"));
twitToggle.addEventListener("click", toggle("twitContainer"));
pinToggle.addEventListener("click", toggle("pinContainer"));
}
window.onload = init();
HTML is way too long.but JS is in head, called from external file. Also i'm not in quirk mode.
It is not clear where "later in the script" is. If it is in different scope definitely it is not going to work. Suggesting you to keep everything in a global object if possible so that you can access from different places in the script.
window.globals = {};
window.globals.fbToggle = document.getElementById("fbToggle");
window.globals.fbToggle.addEventListener("click", function () {
toggle("fbContainer")
});
function toggle(container) {
alert(container);
}
http://jsfiddle.net/ST938/
Another point is addEventListener expects a function or function idenitifier, NOT a function call.
addEventListener("click", toggle("fbContainer")); // wrong
addEventListener("click", toggle); // correct
So if you want to pass a parameter
window.globals.fbToggle.addEventListener("click", function () {
toggle("fbContainer")
});
function toggle(container) {
alert(container);
}
In JavaScript, putting brackets after a function name causes it to be called. If you want to reference a function without calling it you must not put brackets after the name:
window.onload = init(); // this calls init() immediately
window.onload = init; // this correctly stores init in window.onload
The same applies to toggle(). If you need to pre-specify some of the arguments you can wrap it in an anonymous function:
fbToggle.addEventListener("click", function() { toggle("fbContainer"); });
or you can use bind:
fbToggle.addEventListener("click", toggle.bind(null, "fbContainer"));

Confused by this - getting error "this.myfuntion() is not a function"

Background: I am trying to edit a zen cart horizontal pop out menu to make the popout open inline within the menu. The problem I am having is that I am struggling to get my head around the javascript/jquery that came with it.
Without posting the whole thing the structure of the code is something like this:
(declare some vars)
//some functions like this:
function funcname(obj) {
//do something
}
//then one big master function like this:
function bigfunc(arg1, arg2, arg3, arg4, arg5) {
//declare some vars based on this
this.varname1=varname1;
this.varname2=varname2;
//declare some functions inside the big function
this.innerfunc1= function() {
//do stuff
}
this.innerfunc2= function() {
//do stuff
}
}//end of big function
//then goes on to declare init function
function initfunc(){
//this creates new bigfunc(arg1 arg2 arg3...) for each main menu item
}
//finally calls init function with
window.onload = initfunc();
Now on to my confusion -
1) firstly for clarification, am I correct in thinking based on all the this's floating about in bigfunc() and the fact that it is called with new bigfunc() that this is creating an object?
2)My current problem is with one of the functions inside bigfunc() which looks like this:
this.slideChildMenu = function() {
var divref = this.children[0].div;
var ulref = this.children[0].ul;
var maxwidth = this.children[0].width;
var nextWidth;
if (this.isMouseOnMe || this.isMouseOnChild()) {
nextWidth = divref.offsetWidth + slideSpeed_out;
if (nextWidth >= maxwidth) {
this.finishOpeningChild(divref, ulref, maxwidth);
} else {
ulref.style.left = nextWidth - maxwidth + "px";
divref.style.width = nextWidth + "px";
setTimeout("slideChildMenu('" + this.getId() + "')", slideTimeout_out);
}
}
Now my plan is to alter this to use jquery show to open the element so I tried this:
this.slideChildMenu = function() {
var divref = this.children[0].div;
var ulref = this.children[0].ul;
if (this.isMouseOnMe || this.isMouseOnChild()) {
$(divref).show(function(){
this.finishOpeningChild(divref, ulref);
});
}
}
But I am getting this-> TypeError: this.finishOpeningChild is not a function
Now, there is a lot of other stuff going on in this js so I wouldnt dream of asking someone on here to do my work for me, but I am hoping that if someone can explain to me why this function is not a function I may be able to work the rest out.
NOTE: I thought this was to do with the scope of "this" but the value of this appears to be exactly the same in both versions of the code.
I know this is a long one but your help is greatly appreciated.
The value of this in a function is called the "context" in which the function runs. In general, whenever you pass a callback function as an argument (as you do with $(divref).show(function() {...})), the function can run the callback in whatever context it wants. In this case, the jQuery show function chooses to run its callback in the context of the element being animated.
However, you want access to the value of this at the time the anonymous callback function is defined, rather than when it is run. The solution here is to store the outer value of this in a variable (traditionally called self) which is included in the scope of the newly-defined function:
this.slideChildMenu = function() {
//...
var self = this;
$(divref).show(function(){
self.finishOpeningChild(divref, ulref);
});
}
I am thinking that the jQuery selector has changed the scope of this.
In your example $(this); would refer to object being animated per jQuery api docs:
If supplied, the callback is fired once the animation is complete. This can be useful for stringing different animations together in sequence. The callback is not sent any arguments, but this is set to the DOM element being animated. If multiple elements are animated, it is important to note that the callback is executed once per matched element, not once for the animation as a whole.
If the object in question is instantiated you can call it with dot notation without using this like bigFunc.finishOpeningChild(divref, ulref);
You're probably a little confused about scope, it's not always easy keeping track, but doing something more like this:
var site = {
init: function(elm) {
self=site;
self.master.funcname2(self.varname1, elm); //call function in master
},
funcname: function(obj) {
//do something
},
varname1: 'some string',
varname2: 3+4,
master: function() {
this.varname3 = sin(30);
this.funcname2 = function(stuff, element) {
site.funcname(element); //call function in 'site'
var sinus = site.master.varname3; //get variable
}
}
}
window.onload = function() {
var elm = document.getElementById('elementID');
site.init(elm); //call init function
}
usually makes it a little easier to keep track.

How to pass parameters to a function declared like left = function()

How can I pass parameters to a function declared like something = function(){};
window.prototype.initInterface = function(){
this.mainPane = document.createElement('div');
this.mainPane.style.border="5px solid grey";
this.mainPane.style.margin="0px";
this.mainPane.style.width="420px";
this.mainPane.style.height="600px";
this.exitButton = document.createElement('input');
this.exitButton.setAttribute("type", "button");
this.exitButton.setAttribute("value", "exit");
this.exitButton.onclick = function(){
document.body.removeChild(this.mainPane);
};
this.mainPane.appendChild(this.exitButton);
document.body.appendChild(this.mainPane);
}
When the user presses the exit button I want to remove the mainPane from the body of the html page.
this.exitButton.onclick = function(this.mainPage){
document.body.removeChild(this.mainPane);
};
Does not work
How can I do this?
For your exitButton.onclick function to have access to variables you create in the enveloping initInterface function you want a to create a closure in the exitButton.onclick function by returning a function that performs the action you want and passing that the variable.
exitButton.onclick = function () {
return (function() {
document.body.removeChild(mainPane);
})(mainPane);
};
Read more on how closures work here and here and see a working example fiddle.
Alternatively, you forget about closures and walk up the DOM from the button which triggers the event to your mainPane
exitButton.onclick = function() {
// in here "this" is the object that triggered the event, exitButton
document.body.removeChild(this.parentNode);
}
As an aside, window.prototype does not exist if you are doing this in a browser; window is the object at the top of prototype chain in browser scripting. You want just window.initInterface = function () {} which is the exact same thing as function initInterface() {} because everything you do in javascript in the browser becomes a property of window.
This function is the function w/o function name. It could only be used once and you may not easy to find out what parameters should be passed.
You can create another function like :
function go(a1){}
And call it like window.prototype.initInterface = go(a1);
Or you can get some DOM parameters in this unnamed function by using functions like getDocumentById("DOM ID") etc.

Variable scope in Javascript Object

I'm discovering the concept of "objects" in JavaScript. I'm making an RSS Parser, and I have an error (commented).
function MyParser (feed_url) { // Construct
"use strict";
this.feedUrl = feed_url;
this.pubArray = [];
if (typeof (this.init_ok) == 'undefined') {
MyParser.prototype.parse = function () {
"use strict";
var thisObj = this;
$.get(this.feedUrl, function (data, textStatus, jqXHR) {
if (textStatus == 'success') {
var xml = jqXHR.responseXML,
//lastBuildDate = new Date($(xml).find('lastBuildDate').text());
items = $(xml).find('item');
items.each(function () {
var pubSingle = thisObj.makeObj($(this).find('pubDate').text(),
$(this).find('link').text(),
$(this).find('title').text(),
$(this).find('description').text(),
$(this).find('encoded').text(),
$(this).find('commentRss').text(),
$(this).find('comments').last().text());
thisObj.pubArray.push(pubSingle);
});
console.log(thisObj.pubArray); // OK
}
}, 'xml');
console.log(this.pubArray); // Empty
return (this.pubArray);
};
MyParser.prototype.makeObj = function (pubDate, pubLink, pubTitle, pubDesc, pubContent, pubComCount, pubComLink) {
"use strict";
var pubSingle = {};
pubSingle.pubDate = new Date(pubDate);
pubSingle.pubLink = pubLink;
pubSingle.pubTitle = pubTitle;
pubSingle.pubDesc = pubDesc;
pubSingle.pubContent = pubContent;
pubSingle.pubComCount = pubComCount;
pubSingle.pubComLink = pubComLink;
return (pubSingle);
};
}
this.init_ok = true;
}
If you look at the console.log(), you'll see that the line // OK is outputting my array correctly.
But later, when returning from $.get, my array is empty.
Does anybody have an idea why, and how to correct that please?
This is not a problem with variable-scope. The problem here is that you're working with asynchronous flow and you're not thinking correctly the flow.
Let me explain:
When you do your .get, you fire a parallel asynchronous process that will request information from the browser, but your main program's flow keeps going, so when you get to your "return" statement, your array has not been filled yet with the response from your get method.
You should use your array from inside the get callback and not outside of it, since you can't guarantee that the array will have the information you need.
Does it make any sense?
Let me know!
Further explanation
According to your comments, you're still doing something like this:
var results = MyParser(feed_url);
//code that uses results.pubArray
And you cannot do that. Even though you're setting your "pubArray" inside your .get callback, you're trying to use pubArray right after you called MyParser and that's before the .get callback is called.
What you have to do, is call your next step on your program's logic from within the .get callback... that's the only way you can be sure that the pubArray is filled with proper data.
I hope that makes it clearer.
This is because your line
console.log(this.pubArray); // Empty
is being called directly after you issue your Ajax request; it hasn't had time to fetch the data yet. The line
console.log(thisObj.pubArray); // OK
is being called inside the Ajax callback, by which time the data has been fetched.
Thank you all, and particulary #Deleteman .
Here is what I did:
$.get(this.feedUrl, 'xml').success(function () {
thisObj.handleAjax(arguments[0], arguments[1], arguments[2]);
$(document).trigger('MyParserDone');
}).error(function () {
$(document).trigger('MyParserFailed');
});
Then, when i enter "HandleAjax", i'm back in my object context, so "this" refers to my object and the right properties. The only "problem" is that I have to set a listener (MyParserDone) to make sure the parsing is finished.

Categories

Resources