Trouble with my javascript function, I think with document.getElementById('vodObj').innerHTML - javascript

I am trying to write a JavaScript function that will update the labels and attributes of my CSS menu. The CSS menu I create dynamically with PHP and a database, and I want to update the CSS menu so the top item is the currently selected one, and the currently selected one does not appear in the list below it. Now that you know what I am trying to accomplish, here is my code:
var vodName = Array();
var vodAddress = Array();
var vodDate = Array();
function switchVod(vodID) {
alert("switchVod ran");
var x = document.getElementById("vod1");
var y = x.getElementsByTagName("span");
y[0].innerHTML = vodName[vodID];
for (var i = 0; i < vodName.length; i++) {
if (i != vodID) {
var gameNum = i + 2;
var gameID = "vod" + gameNum;
var x = document.getElementByID(gameID);
var y = x.getElementsByTagName("span");
y[0].innerHTML = vodName[i]
x.onclick = function () {
switchVod(id);
}
}
}
alert("after for loop");
alert("1"); //works
document.getElementById('vodObj').innerHTML = 'some string';
alert("2"); //doesn't work
document.getElementById("vodDate").innerHTML = " some string ";
alert("finished"); //doesn't work
}
Deeper in the webpage, after getting my information from the database and storing the strings I need in the vodName, vodAddress, and vodDate arrays, and creating the CSS menu and <div id="vodObj"> and <div id="vodDate">, I initialize the page by calling
window.onload = switchVod(0);
It wasn't doing what I hoped, so I added some alert() calls to see how far into the function it was going before failing. alert("after for loop") worked, as did alert("1"). But, alert("2") does not pop up, and neither does alert("finished"), so I think the problem is with document.getElementById('vodObj').innerHTML = 'some string';.
Any ideas of what I could be doing wrong?

window.onload = switchVod(0);
executes switchVod and assigns the return value to window.onload. So it is very likely that the elements you are trying to access (#vodObj in particular) are not loaded yet.
You have to assign a function to window.onload:
window.onload = function() {
switchVod(0);
};
See also Why does jQuery or a DOM method such as getElementById not find the element?
There is an other problem which will encounter eventually:
x.onclick = function () {
switchVod(id);
}
You never defined id anywhere, and if you define it inside the loop, you will run into closure issues. See JavaScript closure inside loops – simple practical example for a solution.

y[0].innerHTML = vodName[vodID];
At this point vodName is an empty array. Actually throughout all of this, you never provide any values to vodName. Please provide complete document.

Related

when looping through dom elements, when to use "this" and when to use loop variable?

I'm a javascript/dom noob coming from mainly Python and Clojure, and the semantics of referring to things in the dom are really confusing me.
Take the following extract from some code which I just wrote into a chrome extension to identify a subset of pages matching criteria defined in testdownloadpage and then hang eventlisteners on pdf download links to intercept and scrape them.:
function hanglisteners() {
if (testdownloadpage(url)) {
var links = document.getElementsByTagName("a");
for (i = 0, len = links.length; i < len; i++) {
var l = links[i];
if (testpdf(l.href)) {
l.addEventListener("click", function(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
getlink(this.href);
}, false);
};
};
};
};
Originally, I had the call to getlink at the bottom reading getlink(l.href), and I thought (evidently naively) that what would happen with that code would be that it would loop through every matching link, and slap a listener on each that called getlink on the url for that link. But that didn't work at all. I tried replacing l.href with this.href just as a guess, and it started working.
I'm not sure why this.href worked when l.href did not. My best guess is that the javascript interpreter doesn't evaluate l.href in an addEventListener call until some point later, when l has changed to something else(??). But I'm not sure why that should be, or how to know when javascript does evaluate arguments to a function call and when it doesn't...
And now I'm worrying about the higher-up call to testpdf(l.href). That function is meant to check to make sure a link is a pdf download link before hanging the listener on it. But is that going to evaluate l.href within the loop, and hence correctly evaluate each link? Or is that also going to evaluate at some point after the loop, and should I be using this.href instead?
Can some kind soul please explain the underlying semantics to me, so that I don't have to guess whether referring to the loop variable or referring to this is correct? Thanks!
EDIT/ADDITION:
The consensus seems to be that my problem is a (clearly well-known) issue where inner functions in loops are victims of scope leaks s.t. when they're called, unless the inner function closes over all the variables it uses, they end up bound to the last element of the loop. But: why does this code then work?
var links = document.getElementsByTagName("a");
for (i = 0, len = links.length; i < len; i++) {
let a = links[i];
a.addEventListener("click", function(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
console.log(a.href);
});
};
<html>
<head>
<title>silly test</title>
</head>
<body>
<p>
Link 1
link2
link 3
</p>
</body>
</html>
Based on those answers, I'd expect clicking on every link to log "link 3," but they actually log the correct/expected naive results...
The problem arises because the loop variable has already changed by the time the listener actually fires. You should be able to see this with the example below.
function setup(){
var root = document.getElementById('root');
var info = document.createElement('div');
for (var i = 0; i < 5; i++){
// create a button
var btn = document.createElement('button');
// set up the parameters
btn.innerHTML = i
btn.addEventListener('click', function(event){
info.innerHTML = ('i = ' + i + ', but the button name is ' + event.target.innerHTML);
})
// add the button to the dom
root.appendChild(btn)
}
var info = document.createElement('div');
info.innerHTML = 'The Value of i is ' + i
root.appendChild(info)
}
// run our setup function
setup();
<div id="root">
</div>
so what you need to do is save off a copy of l for later use. addEventListener is automatically storing the element in this for exactly this reason. Another very good way to do this, however if you don't want to mess with this for some reason is to use a generator function (a function that creates a function).
for instance:
function clickListener(i, info){
return function(event){
info.innerHTML = ('i = ' + i + ', but the button name is ' + event.target.innerHTML);
}
}
This way we can put the variables i and info into scope for the listener function which is called later without the values changing.
putting this code into our example above, we get the following snippet
function clickListener(i, info){
return function(event){
info.innerHTML = ('i = ' + i + ', but the button name is ' + event.target.innerHTML);
}
}
function setup(){
var root = document.getElementById('root');
var info = document.createElement('div');
for (var i = 0; i < 5; i++){
// create a button
var btn = document.createElement('button');
// set up the parameters
btn.innerHTML = i
// use the generator to get a click handler
btn.addEventListener('click', clickListener(i, info))
// add the button to the dom
root.appendChild(btn)
}
info.innerHTML = 'The Value of i is ' + i
root.appendChild(info)
}
// run our setup function
setup();
<div id="root">
</div>
EDIT: Answering revised question
In your second iteration of code, you use the let keyword introduced with ES6. This keyword is specifically designed to give javascript variables more traditional block scope. In our case it makes the variable scoped to the specific iteration of the loop. A similar example can be seen on MDN. If you can support ES6 in your application/build system, using let is a great way to solve the problem.
When you're in the function of event listeners, this is bound to the DOM element for which the event listener is for. Imagine the following code:
var links = document.getElementsByTagName("a");
for (i = 0, len = links.length; i < len; i++) {
let a = links[i];
a.addEventListener("click", function(e) {
console.log(this === a); // => true
})
}
The two would be identical, so you can use either whenever

How to add array to parameter with function?

I am very new to programming and I am wondering if anyone can help me with this.
I am trying to make a pop up page.
I set variables for each click area which I set each area with div and placed with css.
Also for each pop up image which I put div id on each image on html and set display = "none" on css.
I want to make a function that shows one image on touchend and hide other images at the same time.
Could you help me with my code?
var pop = new Array("pop1","pop2","pop3","pop4","pop5","pop6");
var clickArea = new Array("click1","click2","click3","click4","click5","click6");
function diplay(click,show,hide){
click.addEventListner("touchend",function(){
show.style.display = "block";
hide.style.display = "none";
});
};
display("click[0]","pop[0]","pop[1,2,3,4,5]");
There are a few different issues with your code.
You used strings instead of the actual code structure references while calling display. I see that you mean for these to reference the element ids, but you must first get the element with document.getElementById(...) or jQuery's $("#...").
In the pop and clickArea arrays, you used strings, which do not have the .style object. You need to reference the elements themselves.
Your code structure is not designed to handle arrays.
You need to define the addEventListener before you need the function handler to be called. You do not want this every time.
The click argument in the display function is redundant, as it is never called.
You are using jQuery. You should have stated this! (but you're forgiven) :)
You can't reach into arrays with the syntax arrayName[#,#,#].
You misspelled "display". Whoops!
The arrays are redundant, since the code needed to be restructured.
First, in order to address Point #4, we need this code to run when the DOM has finished loading:
var clickArea = new Array("click1","click2","click3","click4","click5","click6");
clickArea.each(function(id){
$("#"+id)[0].addEventListener("touchend", display);
});
Next, we need to fix the issues with your code. They're explained above.
var pop = new Array("pop1","pop2","pop3","pop4","pop5","pop6");
function display(event){
var indx = Number(event.target.id.split(/\D/i).join(""));
$("#pop"+indx)[0].style.display = "block";
pop.each(function(ide) {
if (ide.split(/\D/i).join("") != indx-1) {
$("#"+ide)[0].style.display = "none";
}
});
};
Otherwise, great job! All of us started out like this, and believe in you! Keep it up!
P.S. You can set arrays like this [ ? , ? , ? , ? ] instead of this new Array( ? , ? , ? , ? ).
Here is an example using for loops instead of methods of Arrays etc
Start off by defining everything you can
var popup_id = ["pop1", "pop2", "pop3", "pop4", "pop5", "pop6"],
popup_elm = [], // for referencing the elements later
area_id = ["click1", "click2", "click3", "click4", "click5", "click6"],
area_elm = [], // for referencing the elements later
i; // for the for -- don't forget to var everything you use
// a function to hide all popups
function hideAll() {
var i; // it's own var means it doesn't change anything outside the function
for (i = 0; i < popup_elm.length; ++i) {
popup_elm.style.display = 'none';
}
}
// a function to attach listeners
function listenTouch(area, popup) {
area.addEventListener('touchend', function () {
hideAll();
popup.style.display = 'block';
});
// we did this in it's own function to give us a "closure"
}
Finally we are ready do begin linking it all to the DOM, I'm assuming the following code is executed after the elements exist in the browser
// setup - get Elements from ids, attach listeners
for (i = 0; i < popup_id.length; ++i) {
popup_elm[i] = document.getElementById(popup_id[i]);
area_elm[i] = document.getElementById(area_id[i]);
listenTouch(area_elm[i], popup_elm[i]);
}
You cannot treat strings as html elements.
Assuming there are elements with click area ids in the page, you may do something like (once the document is ready).
var popEls = pop.map(function (id) { return document.getElementById(id) });
clickArea.forEach(function (id) {
var clickAreaEl = document.getElementById(id);
clickAreaEl.addEventListener('click', onClickAreaClick);
});
function onClickAreaClick() {
var clickAreaNum = +this.id.match(/\d+$/)[0],
popIndex = clickAreaNum - 1;
popEls.forEach(function (popEl) {
popEl.style.display = 'none';
});
popEls[popIndex].style.display = 'block';
}

Create a string (text) that declares variable and its value, for use in Javascript

I am messing with Javascript code that needs to have variable dynamic part.
I am trying to substitute this piece of Javascript code:
var data = document.getElementById('IDofSomeHiddenField').value;
var print = document.getElementById('IDofOutputField');
print.value = data;
with something like:
var encapsulatedData = "var data = document.getElementById('IDofSomeHiddenField').value;";
var encapsulatedPrint = "var print = document.getElementById('IDofOutputField');";
so that when I use somewhere in Javascript code:
encapsulatedData;
encapsulatedPrint;
this will work:
print.value = data;
But it does not work.
Is there a way how to declare:
var encapsulatedData
var encapsulatedPrint
in similar manner like I wrote above, so that:
print.value = data;
works?
Do you mean magically create global variables?
function encapsulatedData() {
window.data = document.getElementById('IDofSomeHiddenField').value;
}
function encapsulatedPrint() {
window.print = document.getElementById('IDofOutputField');
}
encapsulatedData();
encapsulatedPrint();
print.value = data;
This is not very sanitary code, and what you want is probably not what you should be doing. Could you step back and say what your goal is, rather than the means to that goal? I suspect what you really want to be using are closures or returning first-class functions for delayed evaluation.
For example:
function makePrinter(id) {
var outputfield = document.getElementById(id);
return function(value) {
outputfield.value = value;
}
}
function getValue(id) {
return document.getElementById('IDofSomeHiddenField').value;
}
var data = getValue('IDofOutputField');
var print = makePrinter('IDofOutputField');
print(data);
You have a syntax error I think. You're not closing the parentheses on the first and second lines.
var data = document.getElementById('IDofSomeHiddenField').value;
var print = document.getElementById('IDofOutputField');
print.value = data;
It is also bad form to use JS evaluation like you're attempting to do. If anything you really want to create a function for each of the page elements that returns the page element. ECMAScript 5 has properties which I think is sort of what you're looking for with what you're trying to do but that isn't how ECMAScript 3 JS can work.

Jquery click bindings are not working correctly when binding multiple copies

I seem to have an issue when creating copies of a template and tying the .click() method to them properly. Take the following javascript for example:
function TestMethod() {
var test = Array();
test[0] = 0;
test[1] = 1;
test[2] = 2;
// Insert link into the page
$("#test_div").html("<br>");
var list;
for (x = 0; x < test.length; x++) {
var temp = $("#test_div").clone();
temp.find('a').html("Item #" + test[x]);
temp.click(function () { alert(x); });
if (list == undefined)
list = temp;
else
list = list.append(temp.contents());
}
$("#test_div2").append(list);
}
The problem I am seeing with this is that no matter which item the user clicks on, it always runs alert(2), even when you click on the first few items.
How can I get this to work?
Edit: I have made a very simple example that should show the problem much clearer. No matter what item you click on, it always shows an alert box with the number 2 on it.
Correct me if I'm wrong, .valueOf() in JS returns the primitive value of a Boolean object.....
this would not happen ShowObject(5,'T');... ShowObject(objectVal.valueOf(), 'T');
why not use objects[x].Value directly? ShowObject(objects[x].Value, 'T');
WOOOOOSSSHHHH!
after searching deeply... I found a solution...
because it's a closure, it won't really work that way...
here's a solution,
temp.find('a').bind('click', {testVal: x},function (e) {
alert(e.data.testVal);
return false;
});
for best explanation, please read this... in the middle part of the page where it says Passing Event Data a quick demo of above code
I think your issue arises from a misunderstanding of scopes in JavaScript. (My apologies if I'm wrong.)
function () {
for (...) {
var foo = ...;
$('<div>').click(function () { alert(foo); }).appendTo(...);
}
}
In JavaScript, only functions create a new scope (commonly referred to as a closure).
So, every round of the for loop will know the same foo, since its scope is the function, not the for. This also applies to the events being defined. By the end of looping, every click will know the same foo and know it to be the last value it was assigned.
To get around this, either create an inner closure with an immediately-executing, anonymous function:
function () {
for (...) {
(function (foo) {
$('<div>').click(function () { alert(foo); }).appendTo(...);
})(...);
}
}
Or, using a callback-based function, such as jQuery.each:
function () {
$.each(..., function (i, foo) {
$('<div>').click(function () { alert(foo); }).appendTo(...);
});
}
For your issue, I'd go with the latter (note the changes of objects[x] to just object):
var list;
jQuery.each(data.objects, function (x, object) {
// Clone the object list item template
var item = $("#object_item_list_template").clone();
// Setup the click action and inner text for the link tag in the template
var objectVal = object.Value;
item.find('a').click(function () { ShowObject(objectVal.valueOf(), 'T'); }).html(object.Text);
// add the html to the list
if (list == undefined)
list = item;
else
list.append(item.contents());
});

Javascript function objects

I edited the question so it would make more sense.
I have a function that needs a couple arguments - let's call it fc(). I am passing that function as an argument through other functions (lets call them fa() and fb()). Each of the functions that fc() passes through add an argument to fc(). How do I pass fc() to each function without having to pass fc()'s arguments separately? Below is how I want it to work.
function fa(fc){
fc.myvar=something
fb(fc)
}
function fb(fc){
fc.myothervar=something
fc()
}
function fc(){
doessomething with myvar and myothervar
}
Below is how I do it now. As I add arguments, it's getting confusing because I have to add them to preceding function(s) as well. fb() and fc() get used elsewhere and I am loosing some flexibility.
function fa(fc){
myvar=something
fb(fc,myvar)
}
function fb(fc,myvar){
myothervar=something
fc(myvar,myothervar)
}
function fc(myvar,myothervar){
doessomething with myvar and myothervar
}
Thanks for your help
Edit 3 - The code
I updated my code using JimmyP's solution. I'd be interested in Jason Bunting's non-hack solution. Remember that each of these functions are also called from other functions and events.
From the HTML page
<input type="text" class="right" dynamicSelect="../selectLists/otherchargetype.aspx,null,calcSalesTax"/>
Set event handlers when section is loaded
function setDynamicSelectElements(oSet) {
/**************************************************************************************
* Sets the event handlers for inputs with dynamic selects
**************************************************************************************/
if (oSet.dynamicSelect) {
var ySelectArgs = oSet.dynamicSelect.split(',');
with (oSet) {
onkeyup = function() { findListItem(this); };
onclick = function() { selectList(ySelectArgs[0], ySelectArgs[1], ySelectArgs[2]) }
}
}
}
onclick event builds list
function selectList(sListName, sQuery, fnFollowing) {
/**************************************************************************************
* Build a dynamic select list and set each of the events for the table elements
**************************************************************************************/
if (fnFollowing) {
fnFollowing = eval(fnFollowing)//sent text function name, eval to a function
configureSelectList.clickEvent = fnFollowing
}
var oDiv = setDiv(sListName, sQuery, 'dynamicSelect', configureSelectList); //create the div in the right place
var oSelected = event.srcElement;
if (oSelected.value) findListItem(oSelected)//highlight the selected item
}
Create the list
function setDiv(sPageName, sQuery, sClassName, fnBeforeAppend) {
/**************************************************************************************
* Creates a div and places a page in it.
**************************************************************************************/
var oSelected = event.srcElement;
var sCursor = oSelected.style.cursor; //remember this for later
var coords = getElementCoords(oSelected);
var iBorder = makeNumeric(getStyle(oSelected, 'border-width'))
var oParent = oSelected.parentNode
if (!oParent.id) oParent.id = sAutoGenIdPrefix + randomNumber()//create an ID
var oDiv = document.getElementById(oParent.id + sWindowIdSuffix)//see if the div already exists
if (!oDiv) {//if not create it and set an id we can use to find it later
oDiv = document.createElement('DIV')
oDiv.id = oParent.id + sWindowIdSuffix//give the child an id so we can reference it later
oSelected.style.cursor = 'wait'//until the thing is loaded
oDiv.className = sClassName
oDiv.style.pixelLeft = coords.x + (iBorder * 2)
oDiv.style.pixelTop = (coords.y + coords.h + (iBorder * 2))
XmlHttpPage(sPageName, oDiv, sQuery)
if (fnBeforeAppend) {
fnBeforeAppend(oDiv)
}
oParent.appendChild(oDiv)
oSelected.style.cursor = ''//until the thing is loaded//once it's loaded, set the cursor back
oDiv.style.cursor = ''
}
return oDiv;
}
Position and size the list
function configureSelectList(oDiv, fnOnClick) {
/**************************************************************************************
* Build a dynamic select list and set each of the events for the table elements
* Created in one place and moved to another so that sizing based on the cell width can
* occur without being affected by stylesheet cascades
**************************************************************************************/
if(!fnOnClick) fnOnClick=configureSelectList.clickEvent
if (!oDiv) oDiv = configureSelectList.Container;
var oTable = getDecendant('TABLE', oDiv)
document.getElementsByTagName('TABLE')[0].rows[0].cells[0].appendChild(oDiv)//append to the doc so we are style free, then move it later
if (oTable) {
for (iRow = 0; iRow < oTable.rows.length; iRow++) {
var oRow = oTable.rows[iRow]
oRow.onmouseover = function() { highlightSelection(this) };
oRow.onmouseout = function() { highlightSelection(this) };
oRow.style.cursor = 'hand';
oRow.onclick = function() { closeSelectList(0); fnOnClick ? fnOnClick() : null };
oRow.cells[0].style.whiteSpace = 'nowrap'
}
} else {
//show some kind of error
}
oDiv.style.width = (oTable.offsetWidth + 20) + "px"; //no horiz scroll bars please
oTable.mouseout = function() { closeSelectList(500) };
if (oDiv.firstChild.offsetHeight < oDiv.offsetHeight) oDiv.style.height = oDiv.firstChild.offsetHeight//make sure the list is not too big for a few of items
}
Okay, so - where to start? :) Here is the partial function to begin with, you will need this (now and in the future, if you spend a lot of time hacking JavaScript):
function partial(func /*, 0..n args */) {
var args = Array.prototype.slice.call(arguments, 1);
return function() {
var allArguments = args.concat(Array.prototype.slice.call(arguments));
return func.apply(this, allArguments);
};
}
I see a lot of things about your code that make me cringe, but since I don't have time to really critique it, and you didn't ask for it, I will suggest the following if you want to rid yourself of the hack you are currently using, and a few other things:
The setDynamicSelectElements() function
In this function, you can change this line:
onclick = function() { selectList(ySelectArgs[0], ySelectArgs[1], ySelectArgs[2]) }
To this:
onclick = function() { selectList.apply(null, ySelectArgs); }
The selectList() function
In this function, you can get rid of this code where you are using eval - don't ever use eval unless you have a good reason to do so, it is very risky (go read up on it):
if (fnFollowing) {
fnFollowing = eval(fnFollowing)
configureSelectList.clickEvent = fnFollowing
}
And use this instead:
if(fnFollowing) {
fnFollowing = window[fnFollowing]; //this will find the function in the global scope
}
Then, change this line:
var oDiv = setDiv(sListName, sQuery, 'dynamicSelect', configureSelectList);
To this:
var oDiv = setDiv(sListName, sQuery, 'dynamicSelect', partial(configureSelectListAlternate, fnFollowing));
Now, in that code I provided, I have "configureSelectListAlternate" - that is a function that is the same as "configureSelectList" but has the parameters in the reverse order - if you can reverse the order of the parameters to "configureSelectList" instead, do that, otherwise here is my version:
function configureSelectListAlternate(fnOnClick, oDiv) {
configureSelectList(oDiv, fnOnClick);
}
The configureSelectList() function
In this function, you can eliminate this line:
if(!fnOnClick) fnOnClick=configureSelectList.clickEvent
That isn't needed any longer. Now, I see something I don't understand:
if (!oDiv) oDiv = configureSelectList.Container;
I didn't see you hook that Container property on in any of the other code. Unless you need this line, you should be able to get rid of it.
The setDiv() function can stay the same.
Not too exciting, but you get the idea - your code really could use some cleanup - are you avoiding the use of a library like jQuery or MochiKit for a good reason? It would make your life a lot easier...
A function's properties are not available as variables in the local scope. You must access them as properties. So, within 'fc' you could access 'myvar' in one of two ways:
// #1
arguments.callee.myvar;
// #2
fc.myvar;
Either's fine...
Try inheritance - by passing your whatever object as an argument, you gain access to whatever variables inside, like:
function Obj (iString) { // Base object
this.string = iString;
}
var myObj = new Obj ("text");
function InheritedObj (objInstance) { // Object with Obj vars
this.subObj = objInstance;
}
var myInheritedObj = new InheritedObj (myObj);
var myVar = myInheritedObj.subObj.string;
document.write (myVar);
subObj will take the form of myObj, so you can access the variables inside.
Maybe you are looking for Partial Function Application, or possibly currying?
Here is a quote from a blog post on the difference:
Where partial application takes a function and from it builds a function which takes fewer arguments, currying builds functions which take multiple arguments by composition of functions which each take a single argument.
If possible, it would help us help you if you could simplify your example and/or provide actual JS code instead of pseudocode.

Categories