PDA

View Full Version : adding nodes with events



Trinithis
06-02-2007, 03:06 AM
Is there a way to create elements with events and have them be kept when appended to the page?

var body = document.getElementsByTagName("body")[0];
var node1 = document.createElement("span");
node1.appendChild(document.createTextNode("666"));
node1.onclick = "alert(666)";
body.appendChild(node1.cloneNode(true));
body.appendChild(node1);
//Neither alert 666 when clicked

jscheuer1
06-02-2007, 06:34 AM
This:


node1.onclick = "alert(666)";

won't assign an onclick event even to an existing element. It is only assigning a string to the onclick attribute. Try:


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd">
<html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">

</head>
<body>
<script type="text/javascript">
var node1 = document.createElement("span"), func1 = function(){alert(666);};
node1.appendChild(document.createTextNode("666"));
node1.onclick = func1;
document.body.appendChild(node1);
</script>
</body>
</html>

Twey
06-02-2007, 07:51 AM
Or simply:
node1.onclick = function() { alert("666"); };instead of using a string. The key point is that it must be a function, not a string.

jscheuer1
06-02-2007, 03:31 PM
Or, even more simply (Opera Only, as far as I know):


node1.onclick = alert;

Opera alerts:

[object MouseEvent]

as alert, by itself there is a predefined function and the event is assumed as its parameter.

Trinithis
06-02-2007, 03:36 PM
Hmm, neither are working for me. I'm currently recoding my view object script and am removing innerHTML from my code at the same time.

Here's a piece of my code that I created for my static object ObjectWindow and whenever an instance is made, I clone nodes, in this case I clone ObjectWindow.optionsBox. (I am not using prototypes because the nodes have to be cloned anyway.)


ObjectWindow.optionsBox.closeAnchor = document.createElement("a");
ObjectWindow.optionsBox.closeAnchor.href = "javascript:void(0);";
ObjectWindow.optionsBox.closeAnchor.onclick = function() {ObjectWindow.close(this.parentNode.parentNode.id);}
ObjectWindow.optionsBox.closeAnchor.appendChild(document.createTextNode("close"));
ObjectWindow.optionsBox.appendChild(ObjectWindow.optionsBox.closeAnchor);

mburt
06-02-2007, 03:41 PM
Is ObjectWindow.close() a function?

Trinithis
06-02-2007, 03:43 PM
Is ObjectWindow.close() a function?
Yep. Just to make sure that was not the problem, I tried two things. First, I changed the onclick to a simple thing like alert(). Secondly, I tried making a non-cloned instance of the tag and added the event after it was appended, and that worked...

jscheuer1
06-02-2007, 03:47 PM
close() is a reserved function/method for closing windows and documents. That could be a part of the problem. Use a non-reserved name for your close function. Also, even with an href of javascript:void(0);, you still need the onclick event to return false.

Trinithis
06-02-2007, 03:58 PM
I think I know what is happening. I tried appending the original ObjectWindow.optionsBox to the document as is, and it worked fine. Then I added a cloned version of it to the document, and it failed to work. I believe cloned nodes do not keep their events. I also tried using addEventListener too. I think I will have to code a version of cloneNode that keeps events.

jscheuer1
06-02-2007, 04:41 PM
Why would you want to clone a just created node anyway? If you need more than one of the same type, use a loop to create them. You are right about cloning the created node not preserving the event. If it is a hard coded node with a hard coded event though, that works.

Trinithis
06-02-2007, 07:45 PM
I'm not batching out a series of clones all at once. Instead, the user can upon creating an ObjectWindow object after the page has loaded, and its constructor clones the node.

I don't know what a hard coded event/node is. Could you give an example of one that would work with cloneNode?

My idea for a workaround is


function cloneNode2(o, deepBool) {
function copyEvents(o, clone) {
for(var i=0; i<o.childNodes.length; i++) {
if(o.childNodes[i].onclick) clone.childNodes[i].onclick = o.childNodes[i].onclick;
//etc for other events
copyEvents(o.childNodes[i], clone.childNodes[i]);
}
}
var clone = o.cloneNode(deepBool);
copyEvents(o, clone);
return clone;
}

mburt
06-02-2007, 11:04 PM
Instead of putting copyEvents inside cloneNode2, it needs to be in a seperate scope. Pass the "o" argument when you use the function instead.

All cloneNode does is takes an object and literally clones it.

(object).cloneNode(object)

jscheuer1
06-03-2007, 02:32 AM
Well, a hard coded node, event, or whatever is one actually in the HTML markup of the page. The cloneNode works as expected with those. Like if I have:


<div id="art" onclick="alert('hi');">Hi</div>

and clone that node, the event is copied to the new node. I'm a little surprised that it doesn't clone an event that is assigned by javascript, but it doesn't appear to.

Your workaround looks OK (I haven't analyzed it in detail). The bottom line would be though, does it do what you want it to? If so, I don't see anything horrible about it.

Added:

One thing that bothers me, that I would want to test before signing off on is this:


function copyEvents(o, clone) {
for(var i=0; i<o.childNodes.length; i++) { . . .

What if there are no childNodes? It might work out but, if it doesn't:


function copyEvents(o, clone) {
if(o.childNodes&&o.childNodes.length>0)
for(var i=0; i<o.childNodes.length; i++) { . . .

Trinithis
06-03-2007, 07:16 AM
If the element has no child nodes, it still has a childNodes property . . . with nothing in it, hence it returns a length of zero. This seems to apply even with elements like the IMG tag.

Twey
06-03-2007, 12:38 PM
And thus the loop's condition fails immediately and the loop is never executed. What's got into you recently, John? :p

jscheuer1
06-03-2007, 02:36 PM
And thus the loop's condition fails immediately and the loop is never executed. What's got into you recently, John? :p

Well, I have been a bit too busy to test things out as much as I usually prefer. I'm just going on how the code looks. I did say (in this case) that if it works, fine.

In this and other recent threads, it has at times been fairly unclear whether the code worked or not, at least from the point of view of the way it was presented. I do better work when I know if there is a problem or not in first place.

jscheuer1
06-03-2007, 04:00 PM
I did play around with this whole idea a bit more and discovered that if a created node's event is added using the setAttribute() method, it will be cloned along with the node's other attributes, except in IE. Adding an event with setAttribute is non-standard (so, I really can only vouch for this in the FF and Opera versions I ran it on) though as far as I know and requires that the event be added as string data, ex:


node1.setAttribute('onclick', 'alert(666)');

The reason that this works is that the (what is known as, in some browsers) outerHTML of the node then includes:

onclick="alert(666)"

Except in IE where it is done like so:

onclick=alert(666)

and this, for some reason, still prevents the event from getting copied by cloneNode and (more importantly) even getting executed on node1.

mburt
06-03-2007, 07:05 PM
Maybe manually copying all of the attributes using the for in loop could work:

for (x in object)
newobject[x] = object[x];

jscheuer1
06-04-2007, 05:37 AM
OK, I found what appears to be a hack (but I'm willing to be proved wrong, that it isn't a hack) that will assign an event to a DOM created node and then allow said event to be cloned along with the created node:


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd">
<html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">

</head>
<body>
<script type="text/javascript">
function giveEvent(n, e, f){
n.setAttribute(e, f+'()')
if(n.attachEvent&&typeof n.outerHTML=='string'&&!/"/.test(n.outerHTML))
n.attachEvent(e, eval(f));
}
var node1 = document.createElement('div'), func1=function(){alert(666);};
node1.appendChild(document.createTextNode("666"));
giveEvent(node1, 'onclick', 'func1')
/*node1.setAttribute('onclick', 'func1()', 0);
if(node1.attachEvent&&typeof node1.outerHTML=='string'&&!/"/.test(node1.outerHTML)){
node1.attachEvent('onclick', func1);
}*/
document.body.appendChild(node1);
var bob=node1.cloneNode(true);
document.body.appendChild(bob);
</script>
</body>
</html>

Notes: The green commented out section demonstrates a little more clearly what is happening, it is the code I worked from to create the function. The advantage to this is that created nodes and their events may now be cloned without having to hunt through a node for its events, cloneNode will pick them up and copy them for you.

However, I only tested this in Opera 9.0, IE 7 and FF 1.5.0.12. And, it may have problems with certain events/functions whatever - I only tested it with the exact code shown. As a result, I'm not sure how universal it would prove to be. I'd be happy to find improvements.

Trinithis
06-04-2007, 06:29 AM
That's neat. Did you come up with that yourself? BTW how does that constitute as a hack? In that it takes advantage of potential bugs?

jscheuer1
06-04-2007, 06:46 AM
Yes, I came up with it by playing around. I figure it may be a hack, as setAttribute() isn't really (as far as I know) intended to be used for assigning events. As a result, it may not be portable to - say Safari, iCab, Konquerer, etc. I at first thought that it couldn't be used to set a function that would capture the event either, but I think I solved that problem, revised code follows:


function giveEvent(n, e, f){
n.setAttribute(e, f+'(event)')
if(n.attachEvent&&typeof n.outerHTML=='string'&&!/"/.test(n.outerHTML))
n.attachEvent(e, eval(f));
}
var node1 = document.createElement('div'), func1=function(e){alert(666+' '+e.type);};
node1.appendChild(document.createTextNode("666"));
giveEvent(node1, 'onclick', 'func1')
/*node1.setAttribute('onclick', 'func1(event)', 0);
if(node1.attachEvent&&typeof node1.outerHTML=='string'&&!/"/.test(node1.outerHTML)){
node1.attachEvent('onclick', func1);
}*/
document.body.appendChild(node1);
var bob=node1.cloneNode(true);
document.body.appendChild(bob);

Note: I have no idea why this captures the event in IE 7 and 6, but it does.

jscheuer1
06-06-2007, 05:10 PM
I've played around with this a bit more and discovered what may have been obvious, the assigned function(s) must be available to the global scope. This could be a problem if they are declared within a function. There are various ways of dealing with this, I've put forth one here. Also, there is the matter of multiple events, especially in IE, whose attachEvent method works oddly as noted in another thread. I've worked that out too, but testing with multiple event types (onclick, onmouseover, etc.) has yet to be done. I did notice, looking about the web, that the setAttribute method seems fairly acceptable for assigning events, when it works. I have yet to determine a really good way to test that within the script, but have developed an adequate screening process for the time being. I still cannot determine why the event gets captured in IE, and I'd love to know why - but, as mentioned before, it does:


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd">
<html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">

</head>
<body>
<script type="text/javascript">
function giveEvent(n, e, f){
if(n.addEventListener&&n.setAttribute){
if(!n[e])
n.setAttribute(e, f+'(event)');
else
n.setAttribute(e, n.getAttribute(e)+';'+f+'(event)');
}
else if(n.attachEvent){
if(n.holding){
for (var i = 0; i < n.holding.length; i++)
if(n.holding[i][0]==e)
n.detachEvent(e, n.holding[i][1]);
n.attachEvent(e, eval(f));
while(i--)
if(n.holding[i][0]==e)
n.attachEvent(e, n.holding[i][1]);
}
else {
n.holding=[];
n.attachEvent(e, eval(f));
}
n.holding[n.holding.length]=[e, eval(f)];
}
}
function loadem(){
var node1 = document.createElement('div');
loadem.func1=function(e){var t = e.target? e.target : e.srcElement; t.style.color=t.bool=!t.bool? 'red' : '';};
loadem.func2=function(e){var t = e.target? e.target : e.srcElement; alert(t.innerHTML);};
loadem.func3=function(e){var t = e.target? e.target : e.srcElement; alert(t.parentNode.innerHTML);};
loadem.func4=function(){alert('Done?');};
node1.appendChild(document.createTextNode("666"));
var node2 = document.createElement('span')
node2.appendChild(document.createTextNode(" 888"));
node1.appendChild(node2);
giveEvent(node2, 'onclick', 'loadem.func1')
giveEvent(node2, 'onclick', 'loadem.func2')
giveEvent(node2, 'onclick', 'loadem.func3')
giveEvent(node2, 'onclick', 'loadem.func4')
var bob=node1.cloneNode(true);
node1.appendChild(bob);
document.body.appendChild(node1);
}
loadem();
</script>
</body>
</html>