I have an HTML document (here), which creates an iframe-based media player for a collection of songs within albums (I just used letters to define these albums and songs in the mymusic array, for simplicity).
Focusing on the top 3 iframes, the way I have set out the user interaction is to generate the HTML for forms of available albums and songs using Javascript, and write them to the iframes in the body. If you run it and make a selection in the Albums menu, you will see that the options in the Songs menu correspond with the mymusic array, so this works.
However, when I choose a song, the function nowplaying(trackindex,albumindex) should be called using an onchange event in the Songs form, the same way as in the form generated using showinitial() ... but the function does not get called.
I have ruled out the coding of nowplaying itself as a cause, because even when I change nowplaying to alert("hello"), it does not get called. So this leads me to think the problem is with the onchange attribute in "anything", but I can't see the problem. The way I coded it is no different to before, and that worked fine, so why won't this work?
Any help would be much appreciated!
Firebug is your friend....
i is not defined
function
onchange(event) {
parent.nowplaying(this.SelectedIndex,
i); }(change )
onchange is getting called, but i is not defined when calling nowplaying.
This is the result of this line:
p+="<html><head></head><body><form><select onchange='parent.nowplaying(this.SelectedIndex,i);' size='";
which is using "i" in the string, when it should append it as a variable:
p+="<html><head></head><body><form><select onchange='parent.nowplaying(this.SelectedIndex," + i + ");' size='";
To clarify, i is defined when anything(i) is called, but you aren't writing i into the code, just the letter "i". When nowplaying(this.SelectedIndex,i) is called, i is no longer defined, because you aren't inside of the anything() function anymore. You need to expand i when you append the html to p, so that the value is there and not the variable i.
function anything(i){
p+="...<select onchange='parent.nowplaying(this.SelectedIndex,i);'...";
Your onchange event handler is set from a string. When run, it will not have access to i, which is a local variable from the anything function that has long since gone away.
The simple fix would be:
p+="...<select onchange='parent.nowplaying(this.SelectedIndex,'+i+');'...";
which turns the current value of i at string-making time into an integer literal inside the string.
However, it's not generally a good idea to be creating code from strings. It's normally better to write the event handler as a normal function object:
// You will need the below workaround to get the iframe document in IE too
//
var iframe= document.getElementById('songs');
var idoc= 'contentDocument' in iframe? iframe.contentDocument : iframe.contentWindow.document;
idoc.open();
idoc.write(s);
idoc.close();
idoc.getElementsByTagName('select')[0].onchange= function() {
// This is a closure. The 'i' variable from the parent 'anything' function is
// still visible in here
//
parent.nowplaying(this.selectedIndex, i);
};
However you would generally want to avoid setting handlers from one frame on a different one. I'm not really sure what the iframes are gaining you here other than headaches. Why not just simply use positioned divs with overflow? You can still rewrite their content through innerHTML if you need to... though I would prefer to populate them using DOM methods, to avoid all the HTML-injection problems your current script has.
Related
I have a web page which has four tabs at the top. Clicking one of the tabs displays the appropriate page beneath. The tab selection and display is controlled by a js/jQuery function I've called 'changeTab'. Nothing uusual there.
I want to set up a (different) JS function for each tab, to run when that tab is displayed, similar to the way jQuery 'document.ready' works when the main page itself is loaded. I can put a function call at the bottom of my 'changeTab' function, such as 'tabLoaded()'. But that obviously only calls the same one function each time.
I can name the functions 'tab_1_Loaded', 'tab_2_Loaded' etc. ,but then I need some way of dynamically modifying the function call so that the number of the tab is included (I already have the tab number, I just need to work out how to insert it into the function call).
What I am hoping for is a function call like:
tab_[insert tabNum dynamiclly here]_Loaded();
Is that possible in a few lines of code?
I have read articles on Stackoverflow, but they seem do address a different problem of creating (new?) functions with a dynamically derived name. I can be quite clear what my functions are called. I need a dynamically derived call. I suspect it may be possible with 'eval' but my reading also suggests eval is to be avoided, so I've not pursued it.
My fall-back is a series of conditionals:
if(tabNum == 1) tab_1_Loaded();
if(tabNum == 2) tab_2_Loaded();
etc.
but it seems inelegant (though simple) and it certainly works in this case where the number of possibilities is small. Is there a better way that's also simple?
LATER: I've subsequently realised there's an additional complication for the particular page/tabs I'm working on right now, (though it won't apply to the entire site). This page is for on-line booking. The first tab is the booking form (visitor enters dates, number of people). The second and subsequent tabs aren't populated until the visitor clicks 'Next' and moves on to the next stage. Consequently any function call in the 'changeTabs' function is made before the contents of the tab have actually loaded, so it does't work.
To deal with that I'm going to put the call into a script at the bottom of each tab contents. I expect there are more elegant ways of doing it, but it's only one line of code, whereas all the offered solutions are actually more verbose (and harder for me to understand). I will probably still need the call from 'changeTab' to cope with the visitor flicking through the tabs before finalising the booking.
When press the tab, the call back function will always be invoked, no matter what how many call back functions all will be invoked. You cannot conditionally invoke a callback function from a key press. Ideal way to implement this would be
i) Have a single call back function for the tab event
ii) Identify the id of the element, that is currently on focus when tab is pressed inside that call back function
iii) Add conditions based on that element on focus to have your logic of functions for respective elements
Yes it's possible to do what you're asking. All functions and variables declared with global scope are methods and properties of the window object, so you can build the name of the your function as a string and reference it via bracket notation.
So assuming you have tabNumber already stored in a variable:
var functionName = "tab_"+tabNumber+"_Loaded";
window[functionName]();
(See https://codepen.io/slynagh/pen/MMVEoE)
But a better approach would be to use callback functions or else use one tab_Loaded() function which accepts the tabNumber as a parameter, eg:
function tab_Loaded(tabNumber){
if(tabNumber === 1) { do something }
else if(tabNumber === 2 ) {do something else}
//etc
}
At the top of my JavaScript document, which is linked to an HTML page, I declared:
var pizzaVar;
Then, I have a function later in the code that sets the variable's value and then calls another function:
function makePizza()
{
pizzaVar = document.getElementById("pizzaDiv").innerHTML;
givePizzaToppings();
}
But when I tried to use pizzaVar in the next function to set the innerHTML of pizzaDiv, nothing happened:
function givePizzaToppings()
{
var toppings = "<p>Onions and Bacon</p>";
pizzaVar = toppings;
}
However, if I changed the last line to
document.getElementById("pizzaDiv").innerHTML = toppings;
it worked. I don't want to have to put that in everytime I want to change pizzaDiv though, is there a way to fix this.
PS: I tried saying
var pizzaVar= document.getElementById("pizzaDiv").innerHTML;
outside of the functions but I got an error message saying:
TypeError: null is not an object(evaluating 'document.getElementById("pizzaDiv").innerHTML')
Your pizzaVar will point to a string value, which will be the HTML at the time of the assignment. It doesn't magically setup a link to change it.
If you wanted to use this global variable, you'd be better off pointing it to the div element and then using innerHTML every time you wanted to change its value.
Then you should research to understand why innerHTML is rarely the best tool to modify the DOM.
Unfortunately, whenever you want to change the .innerHtml of your pizza div, you're going to have to write document.getElementById("pizzaDiv").innerHTML on the left side of an equals sign. pizzaVar is not a pointer, and doesn't point to the .innerHtml of the div.
The error you got is telling you that document.getElementById("pizzaDiv") returned null and hence has no properties at all, much less an .innerHtml property. This is because it did not exist at the time of the .getElementById call.
function initnav(){
var cbox=document.getElementsByClassName('box');
for(var i=0;i<cbox.length;i++){
cbox[i].innerHTML=cbox[i].id;
<!--so far I have set their captions after setting their ids same as how I want them to appear.-->
<!--what comes next is what doesn't really work.-->
getElementById(cbox[i].id).onclick=Mclick(cbox[i].id);
}
};
function Mclick(id){alert(id);}
The whole thing is in a js file, and promptly linked from my html file.
As planned, all the buttons should appear and be clickable, but what is happening instead is only one of them is visible and that one is not working when I click on it.
When I create a lot of div-oriented buttons, I wish I could run for loop and be able to assign each of them as clickable instead of writing lines as many as they are.
How do you assign onclick within for loop in javascript?
You're calling the function instead of assigning it.
getElementById(cbox[i].id).onclick = Mclick;
Of course, now your function will receive an event argument instead of an id. (Passing the id inside the loop is a huge pain; easiest fix is to not bother trying.) But it also gets the attached element as this, which is convenient:
function Mclick() {
alert(this.id);
}
Other comments:
You should try not to be in the habit of using innerHTML if you're not assigning a string that contains known HTML. Saves you from having to care about escaping. Use textContent instead.
Assigning to onclick is a bit inflexible; you can only ever assign one click handler this way, and it's hard to notice if you accidentally overwrote an existing handler. Use addEventListener.
getElementById(element.id) should surely be equivalent to element.
Don't use HTML comments within JavaScript! :) They only work for... weird backwards-compatibility reasons. JavaScript comments are either // ... or /* ... */.
Best not to capitalize a function name unless it's supposed to be a constructor; you may notice that SO's highlighting made Mclick green, because it thinks it's a class name.
So I'd end up with:
function initnav() {
var cbox = document.getElementsByClassName('box');
for(var i = 0; i < cbox.length; i++) {
cbox[i].textContent = cbox[i].id;
cbox[i].addEventListener('click', alert_id);
}
}
function alert_id(event) {
alert(this.id);
}
So basically you don't call the for loop since the for loop is in the function. If you want to call all your variables and the statements in the for loop you have put the statements in the function and call the function outside of the function but inside of the script.
I am trying to add a character counter to a page, on this page i enter in three values and it returns a large string in the innerHTML of a div with the ID of 'AnswerBoxID', now i want my script to obviously count the number of characters in it to do this i have written
var submit=document.getElementsByClassName('Row3');
function countChars(){
count = document.getElementById('AnswerBoxID').innerHTML.length;
document.title ="Chars "+count+"/160";
}
Which returns a ROUGH approximate of the chars, when i then paste it into an editor or something else that counts chars i get a different result again, counting with this method gets within 5 chars of what other things are reporting (specifically notepad++).
BUT my biggest problem is I have been unable to get countChars() to update
when the value of document.getElementById('AnswerBoxID').innerHTML updates, in javascript I overcame that using the following code
var submit=document.getElementsByClassName('Row3');
for (i=0; i<submit.length; i++){
submit[i].firstChild.setAttribute('onclick','countChars()');
}
After reading GM Pitfalls 2 i then modified my approach to the following
for (i=0; i<submit.length; i++){
submit[i].firstChild.addEventListener('click',countChars(),true);
}
But it still doesnt work!
And before anyone asks yes I do define the count variable before the function. I don't really mind the mostly accurate length thing I would prefer it to be more precise but I do really want to add onclick elements that run countChars() to the submit buttons.
You seem to add the event handler wrong.
elm.setAttribute('onclick','countChars()');
would set an attribute, and eval 'countChars()' in the global scope when the element is clicked. Yet, Greasemonkey scripts run sandboxed to their own global object, and your declared function "countChars" is not available to the eval.
elm.addEventListener('click',countChars(),true);
executes the function immediately and adds the return value as a handler. To make it work, just remove the brackets and pass the function itself. See also element.addEventListener
Greasemonkey scripts run sandboxed. That means, the script runs in the page's onload event then is removed. Calling your Greasemonkey script's countChars() function when the form is submitted will return an undefined error, as your function object is no longer present in the DOM.
Here's my simplified contentEval function (based on GM wiki's Content Script Injection's function):
function contentEval(source) {
var script = document.createElement('script');
script.setAttribute("type", "text/javascript");
script.textContent = source;
document.head.appendChild(script);
}
You can also set the text property instead of textContent for compatibility with older IE versions, thanks RogG!
Put that function in your GM script, it will serve to append any function you need to user later to the DOM's head.
Pass your countChars function as a parameter to the contentEval function:
contentEval(function countChars() {
var count = document.getElementById('AnswerBoxID').textContent.length;
document.title ='Chars '+count+'/160';
});
This way, your countChars function will be placed inside a script element appended to the document's head, which will be accessible after the GM script's execution time ends.
If you want to check a demo of the code above, use a DOM Inspector in this fiddle. It creates a new script element which is appended to the (in JSfiddle's case, the iframe's) document's head, meaning it will be accessible whenever you need it.
You could also simply attach the function to the unsafeWindow, but you should avoid using unsafeWindow unless strictly necessary, and note that unsafeWindow is not supported in Google Chrome.
Use this page for reference: http://wiki.greasespot.net/Content_Script_Injection
Also use the .textContent method to get the text content of an element (and its descendants) as noted by RobG.
In GreaseMonkey, you should be able to use:
var count = document.getElementById('AnswerBoxID').textContent.length;
This is my first post.
I'm trying to do some basic meta-programming with javascript, and I was wondering if there is a way of get the id of a particular object and with that id, access to the variable name, or get simply the variable name of a particular object. I wanna recreate a situation in which you first create every single html in a web page, and append to some of the html tags events associated to a particular class -example class Person-. for example: Supposed the next code:
var someFunction = function(someText){alert(someText);}
function SomeClassFunction(){
this.aClassFunction = someFunction;
}
var aVariableName = new SomeClassFunction();
and in the HTML code suppose I have the next piece of code.
<html>
<head>
<title></title>
</head>
<body>
<div onclick="aVariableName.aClassFunction('Some text to show in alert');">
</div>
</body>
</html>
Then, as you may notice the onclick event uses the aVariableName I created before, but because I first create the name of the variable and then append the name in the code cause I knew aVariableName was the name of that object. What I wanna do or implement is to create the text above in html without know the variable name of an specific object. I have surfed on the net but, unfortunately I haven't found anything about it.
i dont know how to get the name of a variable from the code its self without doing a whole load of work parsing stuff, which will get messy, and i'd shoot someone for this.
var someValue;
var foo = function() { someValue; }
alert((foo + '')); // this is now a string, use substr to extract variable name
You know you can set events like this in javascript someElement.onclick = someFunction so you dont really need to know the name of the variable if all you're doing is setting an event handler.
In general, no — you can't get the name of a particular variable or its "id" either.
If you really want to, it may be possible to do Bad Things with exceptions and stack traces to get that information… But I don't know off the top of my head how to do that.
Edit: I assume that, by “variable ID”, you mean “a unique identifier for the object referenced by that variable”, like Python's id builtin:
>>> x = {}
>>> y = z = {}
>>> (id(x), id(y), id(z))
(123, 567, 567)
I'm not 100% sure that I understand your question correctly in regards to meta-programming, but typically you would attach an event handler to the click event of the DOM element, then you can examine properties of the element within the handler.
There are a couple things in JavaScript that facilitate meta-programming: eval will let you interpret arbitrary code, so you can build a string of code and eval it. The security concerns are numerous. You can also access properties of an object by index or by name, e.g.
aVariableName.aClassFunction
is the same as
aVariableName["aClassFunction"]
Hope that helps.