How to know if a DOM element mounted to tree - javascript

I am pretty new to DOM, I wonder if I refer a DOM element which may be removed from DOM tree, how can I check if it is still mounted or not?
Like:
var div = document.createElement("DIV");
document.body.appendChild(div);
Then later I probably select <div> element and remove it, but this operation does only unmount those from the tree, the div variable still points to that DOM element.
I wonder if there is a function to test if the div is still on the page (mounted on the DOM tree) or not?

You can probably try this one
document.body.contains(yourDiv)
contains method will return true or false

if a node is part of the document, its baseURI property will be a string URL, otherwise, it will be null
var div = document.createElement("DIV"),
u1=div.baseURI, u2, u3; //first url, un-attached
document.body.appendChild(div);
u2=div.baseURI; //second url, attached
div.remove();
u3=div.baseURI; //third url, detached
alert(JSON.stringify([u1,u2,u3], null, 2));
run on this page in devtools shows:
[
null,
"http://stackoverflow.com/questions/34640316/how-to-know-if-a-dom-element-mounted-to-tree",
null
]
this means that to determine if a node is attached, you can simply ask for elm.baseURI:
if(div.baseURI){
alert('attached')
}else{
alert('not attached');
}

According to the improved version of Mr Lister.
function isMounted(node) {
if (node.nodeType === Node.DOCUMENT_NODE) return true;
if (node.parentNode == undefined) return false;
return isMounted(node.parentNode);
}

You can use Node.isConnected
div.isConnected
Note: This will not work with old browsers (Internet Explorer, Edge < 79, Safari < 10)

Володимир Яременко's answer is correct, but as an alternative method, you can check if the div has a parent node.
if (theDiv.parentNode==null) {
// Not in the DOM tree
}
else {
// in the DOM tree!
}
This will be null before appending it to the body, and again after removing it from the body.

Related

MutationRecord seems incomplete at the time it is observed?

I'm using a MutationObserver to notice when a certain element is added to the page. The way I do this is by observing the document and iterating through each MutationRecord's addedNodes array and querying for a certain selector:
added_node.querySelectorAll("tr[data-testid|='issue-table--row']")
This does work, but I do not get the results I expect. For example, on a particular page I should see a parentNode being added that has 18 of the tr html element somewhere in the tree.
So I created a script to debug this. What you see below, does output how many tr elements are found in the added Nodes. As well as each MutationRecord it inspects.
Oddly enough, when searchRecord() is invoked automatically during the scripts runtime, I don't see the expected result.
But after manually reviewing all MutationRecords that were printed to the debug logs, I could confirm that one of them indeed has the data I am looking for.
For example, this manual line in the debug console does return what I expect:
temp0[1].addedNodes[2].querySelectorAll("tr[data-testid|='issue-table--row']")
(temp0 being a MutationRecord the MutationObserver observed.)
Typing this into the debug console also yields the expected results:
searchRecord(temp0)
But when the same line is invoked by the script via the callback searchRecord(mutationRecords) then for some dubious reason it never returns the expected result.
What's happening? Is the MutationRecord incomplete at the time it is observed??
function searchRecord(mutationRecords) {
for (const r of mutationRecords) {
/* TARGET tests */
if (r.target instanceof HTMLElement) {
if (r.target.attributes["data-testid"] === "issue-table--body") {
console.debug("Target is 'issue-table--body'")
}
if (r.target.attributes["data-testid"] === "issue-table--row") {
console.debug("Target is 'issue-table--row'")
}
}
/* ADDEDNODES tests */
for (const node of r.addedNodes) {
if (node instanceof HTMLElement) {
/* direct */
if (node.attributes["data-testid"] === "issue-table--body") {
console.debug("Added node is 'issue-table--body'")
console.debug(node)
}
if (node.attributes["data-testid"] === "issue-table--row") {
console.debug("Added node is 'issue-table--row'")
console.debug(node)
}
/* nested */
tbodies = node.querySelectorAll("tbody[data-testid|='issue-table--body']")
if (tbodies.length > 0) {
console.debug(`Added node contains ${tbodies.length} 'issue-table--body'`)
console.debug(node)
}
trows = node.querySelectorAll("tr[data-testid|='issue-table--row']")
if (trows.length > 0) {
console.debug(`Added node contains ${trows.length} 'issue-table--row'`)
console.debug(node)
}
}
}
/* REMOVEDNODES tests */
for (const node of r.removedNodes) {
if (node instanceof HTMLElement) {
/* direct */
if (node.attributes["data-testid"] === "issue-table--body") {
console.debug("Removed node is 'issue-table--body'")
}
if (node.attributes["data-testid"] === "issue-table--row") {
console.debug("Removed node is 'issue-table--row'")
}
/* nested */
tbodies = node.querySelectorAll("tbody[data-testid|='issue-table--body']")
if (tbodies.length > 0) {
console.debug(`Removed node contains ${tbodies.length} 'issue-table--body'`)
}
trows = node.querySelectorAll("tr[data-testid|='issue-table--row']")
if (trows.length > 0) {
console.debug(`Removed node contains ${trows.length} 'issue-table--row'`)
}
}
}
}
}
new MutationObserver(function callback(mutationRecords) {
console.debug("-----------------------------------------------")
console.debug("Mutation observed. Logging mutation records ...")
console.debug(mutationRecords)
searchRecord(mutationRecords)
}).observe(document, {
attributes: false,
childList: true,
subtree: true,
})
Let's simplify the situation to make it easier to talk about: You want to watch for div elements with class example (div.example) being added and see how many span elements with class x (span.x) there are in the div.example that was added.
The nodes you receive are the actual nodes in the DOM document that were added. They will be as fully-populated as they are as of when your observer was called, which will be at some point after the element has been added to the container (because of JavaScript's run-to-completion semantics — the code adding the elements has to finish before any callbacks that triggers, such as your mutation observer, can be run). That means that:
If a div.example is added that contains (say) three span.x elements, your code will see the three span.x elements in the div when your observer callback is called, since they're already there.
If a div.example is added, then just afterward three span.x elements are added to it without yielding to the event loop (that is, without waiting for some asynchronous operation like ajax or a setTimeout), your code will still see those three span.x elements in the div.example when the observer callback is called, because they're there by the time it runs, even though they weren't when the div.example was first added.
Variation: If the div.example is added with span elements in it that don't have the x class yet, but then the x class is added afterward without yielding to the event loop, your code will see span.x elements, because the class will be there by the time it runs.
If a div.example is added, and then the span.x elements are added to it later after the code adding things has yielded to the event loop by waiting for some asynchronous operation, your code may not see the span.x elements in the div.example, since it may run before they're there.
Variation: If the div.example is added with span elements in it that don't have the x class yet, but then later after the code adding things has yielded to the event loop it adds the x class to the span elements, your code may not see span.x elements in the div.example, because although the span elements are there, they don't have the x class you're looking for yet, because your code ran before the class was added.
Here's an example of all three scenarios:
const observer = new MutationObserver((records) => {
for (const record of records) {
if (record.addedNodes) {
for (const node of record.addedNodes) {
if (node.nodeName === "DIV" && node.classList.contains("example")) {
const {
length
} = node.querySelectorAll("span.x");
console.log(`div.example "${node.id}" added, ${length} span.x elements found in it`);
}
if (node.querySelectorAll("span").length > 0) {
node.style.backgroundColor = "lightgreen"
}
}
}
}
});
const container = document.getElementById("container");
observer.observe(container, {
childList: true,
subtree: true,
});
function createDiv(id) {
const div = document.createElement("div");
div.id = id;
div.classList.add("example");
return div;
}
function addSpan(div, text) {
div.insertAdjacentHTML("beforeend", `<span class=x>${text}</span>`);
}
setTimeout(() => {
let div;
// Adding a fully-set-up div
div = createDiv("A: three spans");
addSpan(div, "1");
addSpan(div, "2");
addSpan(div, "3");
console.log("A: spans added");
container.appendChild(div);
// Adding a partially-set-up div, then adding to it after, but without
// yielding to the event loop
div = createDiv("B: three spans, added after (no yield)");
container.appendChild(div);
addSpan(div, "1");
addSpan(div, "2");
addSpan(div, "3");
console.log("B: spans added");
// Adding a partially-set-up div, then adding to it after yielding to
// the event loop
div = createDiv("C: three spans, added after (yield)");
container.appendChild(div);
setTimeout(() => {
addSpan(div, "1");
addSpan(div, "2");
addSpan(div, "3");
console.log("C: spans added");
}, 0);
}, 100);
.as-console-wrapper {
max-height: 70% !important;
}
<div id="container"></div>
So if you're seeing the element without the descendants you expect to see, it would appear you're running into scenario #3 above: The descendants are being added to it (or the charateristics of them you're looking for are set) after a delay.
The crucial point is: The elements you get in the observer callback are the actual elements in the DOM, so they'll have the contents and characteristics those elements have as of when you look at them. They aren't copies or placeholders or representative examples.

Maximum call stack size exceeded when I am modifying words in the webpage through chrome extension's content script

I am building a Chrome Extension that look for a word , and if that word is present on the web page it gets blurred. To achieve this I look for all the text (node type 1) nodes on the web page and replace them with a new node. Problem occurs when I create a new node and assign it the text of the node to be replaced, this script when run gives error "RangeError: Maximum call stack size exceeded"
This problem doesn't occur when I assign a constant string to the node to be created. And this script runs fine.
var targetNode=document.body
var config = { attributes: true, childList: true, subtree: true };
var callback = function(mutationsList, observer) {
walk(document.body);
};
var observer = new MutationObserver(callback);
observer.observe(targetNode, config);
function walk(node)
{
var child, next;
switch ( node.nodeType )
{
case 1: // Element
case 9: // Document
case 11: // Document fragment
child = node.firstChild;
while ( child )
{
next = child.nextSibling;
walk(child);
child = next;
}
break;
case 3: // Text node
handleText(node);
break;
}
}
function handleText(textNode)
{
var str = textNode.nodeValue;
if (str == "Manchester"){
//console.log(str);
p=textNode.parentNode;
const modified = document.createElement('span');
modified.id="bblur";
modified.textContent = "Constant"; // this works
modified.style.filter="blur(5px)";
modified.addEventListener("mouseover", mouseOver, false);
modified.addEventListener("mouseout", mouseOut, false);
p.replaceChild(modified, textNode);
}
//textNode.nodeValue = str;
//textNode.style.filter="blur(5px)";
}
function mouseOver()
{
this.style.filter="blur(0px)";
}
function mouseOut()
{
this.style.filter="blur(5px)";
}
This handleText function doesn't work
function handleText(textNode)
{
var str = textNode.nodeValue;
if (str == "Manchester"){
//console.log(str);
p=textNode.parentNode;
const modified = document.createElement('span');
modified.id="bblur";
modified.textContent = str; //this doesn't work :/
modified.style.filter="blur(5px)";
modified.addEventListener("mouseover", mouseOver, false);
modified.addEventListener("mouseout", mouseOut, false);
p.replaceChild(modified, textNode);
}
}
I don't want new node to be created with a fixed string but i want the text content of old node in the new one. What I can do to avoid this call stack limit reached problem. Thanks!
Infinite loop is caused because you modify DOM from MutationObserver callback. It works with "Constant" because you have condition "if (str == "Manchester")" which prevents DOM modification and does not trigger MutationObserver callback. Try using constant "Manchester" and you will see infinite loop again.
Easiest fix would be to ignore nodes which you already replaced:
function walk(node)
{
var child, next;
switch ( node.nodeType )
{
case 1: // Element
case 9: // Document
case 11: // Document fragment
child = node.firstChild;
while ( child )
{
next = child.nextSibling;
if (child.id !== 'bblur') {
walk(child);
}
child = next;
}
break;
case 3: // Text node
handleText(node);
break;
}
}
Also your code will assign same id to all replaced elements. It would be better to use different way to mark new nodes, for example you can use dataset attributes.
As mentioned, infinite loop, your modifying the DOM, which triggers the callback, which modifies the DOM etc.
Maybe run the code first.
Register your listener,
When the listener fires, remove the listener.
Once changes have been made, register the listener again.
That way your code won’t trigger itself and will only listen to changes when it’s idle.
Think of it like a holding page on an e-commerce website. If your moving the database but still taking orders it’s gonna get messy. So you disable taking new orders whilst your doing the processing and enable them once your done. Same logic here

Why does my HTML constructor object return [object Object] instead of [HTMLElementElement]?

Background
I'm trying to make a completely pure JavaScript GUI for creating HTML content for learning purposes. The only real html in the file will be one <script></script> element. I'm almost finished, I think.
I made a custom HTML element constructor, but when I create an object, [object Object] is displayed instead of [object HTMLWhateverElement] when I do alert(whatever); (see the example below). I think that's preventing me from appending child elements to parent elements when both are made by the constructor.
The main problem
If I could get HTML instances to append to HTML tags and element instances, then I would be very happy.
Constructor
function element(tagName) {
var element = document.createElement(tagName);
this.setText = function(elementText) {
if(element.childNodes.length < 1) {
var text = document.createTextNode(elementText);
element.appendChild(text);
} else {
element.childNodes[0].nodeValue = elementText;
}
}
this.removeText = function() {
element.removeChild(element.childNodes[0]);
}
this.setAttribute = function(attribute,value) {
element.setAttribute(attribute,value);
}
this.removeAttribute = function(attribute) {
element.removeAttribute(attribute);
}
this.appendTo = function(parent) { // Works but not on element instances of the constructor
parent.appendChild(element);
}
this.details = function(){
alert(
"Type: " + element +
"\n\"typeof\": " + typeof(element) // Shouldn't need this right?
);
}
}
Example
ul = new element("ul");
ul.appendTo(body); // works fine if BODY is actually an HTML tag
alert(body); // gives me [object HTMLBodyElement] :)
alert(ul); // gives me [object Object] >(
li = new element("li"); // works :)
li.setText("list item text"); // works :)
li.appendTo(ul); // doesn't work >(
If I could just figure out how to append JavaScript-created (child) elements to other JavaScript-created (parent) elements, I'd be golden. I think it has to do with the return value of instantiated elements made by the constructor.
EDIT
1) Possible answer
#carter-sand Thank you.
Adding a new this.appendChild method works but reinvents the wheel
of HTML object's built in appendChild method.
2) Possible answer
#s4mok Thanks for the hint.
Changing var element to this.elem works but creates a
funky interface:
li.appendTo(ul.elem);
vs.
li.appendTo(ul);
I'm trying to emulate a private member.
Both answers work but neither return the value [object
HTMLWhateverElement] when I do :
alert(li);
Is there a way to reap the benefits of all of the above?
ul = new element("ul");
The above line is instantiating the function element() and the instance which is assigned to ul is an object and not HTMLWhateverElement
ul.appendTo(body); // works fine if BODY is actually an HTML tag
The above works because your code is :
this.appendTo = function(parent) { // Works but not on element instances of the constructor
parent.appendChild(element);
}
whereas the parent is body and an HMTL Element and has a method appendChild, and the element that you are appending is the element from this line:
var element = document.createElement(tagName);
Why the below code does not work?
li = new element("li"); // works :)
li.setText("list item text"); // works :)
li.appendTo(li); // doesn't work >(
The answer to that is first of all li is not a HTML Element which the code
this.appendTo = function(parent) { // Works but not on element instances of the constructor
parent.appendChild(element);
}
will fail since the instance li is not an html element but rather an object instance of function element()
Another error in this code is that you have a circular li.appendTo(li); which if you inspect your code, you are appending as child li to itself. something like a paradox or a circular dependency.
EDIT:
My Recommendation:
First of all, maybe change the name of your "element" inside the function element to avoid confusion. :D
Expose the var element = document.createElement(tagName); so that you could access it from the parameter parent of your method appendTo which will allow you to use parent.getTheExposedElement().appendChild ()
Your function returns an instance of itself [object Object] when created using the "new" keyword. You can override that by specifically returning your element. However you will then need to change "this" to your element instead because "this" refers to the function new instance. Also as suggested it may be less confusing if you changed "element" to something else like "newElement".
function element(tagName) {
var element = document.createElement(tagName);
element.setText = function(elementText) {
if(element.childNodes.length < 1) {
var text = document.createTextNode(elementText);
element.appendChild(text);
} else {
element.childNodes[0].nodeValue = elementText;
}
}
element.removeText = function() {
element.removeChild(element.childNodes[0]);
}
element.setAttribute = function(attribute,value) {
element.setAttribute(attribute,value);
}
element.removeAttribute = function(attribute) {
element.removeAttribute(attribute);
}
element.appendTo = function(parent) { // Works but not on element instances of the constructor
parent.appendChild(element);
}
element.details = function(){
alert(
"Type: " + element +
"\n\"typeof\": " + typeof(element) // Shouldn't need this right?
);
}
return element;
}
ul = new element("ul");
ul.appendTo(document.body); // works fine if BODY is actually an HTML tag
alert(document.body);
alert(ul);
li = new element("li");
li.setText("list item text");
li.appendTo(ul);
If I run your example, I notice the following error message in the console:
Uncaught TypeError: Object #<element> has no method 'appendChild'
This is because your custom element instance does not have the appendChild method that you're using in appendTo. You need to add one to your constructor, like this:
this.appendChild = function(child) {
element.appendChild(child);
}

How do I know which element was clicked?

I'm attempting the surprisingly difficult task of finding out which element was clicked. I have these functions from Head First AJAX:
function getActivatedObject(e) {
var obj;
if (!e) {
obj = window.event.srcElement;
} else if (e.srcElement) {
obj = e.srcElement;
} else {
obj = e.target;
}
return obj;
}
function addEventHandler(obj, eventName, handler) {
if (document.attachEvent) {
obj.attachEvent("on" + eventName, handler);
} else if (document.addEventListener) {
obj.addEventListener(eventName, handler, false);
}
}
And my code:
mainPane = document.getElementById("mainDiv");
contactPane = document.getElementById("contactDiv");
addEventHandler(mainPane, "click", openFunction);
addEventHandler(contactPane, "click", openFunction);
function openFunction(e) {
var me = getActivatedObject(e);
//Some other stuff
}
Unfortunately, the me variable sometimes refers to the div, but it sometimes refers to the image inside the div. Even though the image has no onclick function or any other kind of event! So how can I get the div that triggered the event?
You're dealing with Event bubbling.
Basically, your click on a child element bubbles up through all of that child's parents; if those parents have a click handler bound to them, it will execute.
In pseudocode: you can see what element your current target is, and if it's not a DIV, you know that you need to look for that element's parent.
Using your example, it would look something like this (simplified a bit for your example; in reality, you'd need something much more robust):
function getActivatedObject(e) {
var obj;
if (!e) {
obj = window.event.srcElement;
} else if (e.srcElement) {
obj = e.srcElement;
} else {
obj = e.target;
}
// Images are direct children of our DIV. If our source element was an img, we should
// fetch its parent
if(obj.tagName.toLowerCase() === "img"){
obj = obj.parentNode;
}
return obj;
}
Note that this will work for your example only; in the real world, you would likely need to iterate back up the DOM tree, comparing parentNodes with the elements you are interested in until you've found what you're looking for.
Most JavaScript libraries abstract this away for you (jQuery, for example, sets a currentTarget property of all Events it dispatches that refers to the element you need, encapsulating all the traversal work for you). Makes it easier, but it's not too terrible a job to write that yourself and I'd argue against including the overhead of an entire JavaScript library if that's all you need to do. :-)
EDIT: Totally destroyed my formatting! D'oh!

Why does an onclick property set with setAttribute fail to work in IE?

Ran into this problem today, posting in case someone else has the same issue.
var execBtn = document.createElement('input');
execBtn.setAttribute("type", "button");
execBtn.setAttribute("id", "execBtn");
execBtn.setAttribute("value", "Execute");
execBtn.setAttribute("onclick", "runCommand();");
Turns out to get IE to run an onclick on a dynamically generated element, we can't use setAttribute. Instead, we need to set the onclick property on the object with an anonymous function wrapping the code we want to run.
execBtn.onclick = function() { runCommand() };
BAD IDEAS:
You can do
execBtn.setAttribute("onclick", function() { runCommand() });
but it will break in IE in non-standards mode according to #scunliffe.
You can't do this at all
execBtn.setAttribute("onclick", runCommand() );
because it executes immediately, and sets the result of runCommand() to be the onClick attribute value, nor can you do
execBtn.setAttribute("onclick", runCommand);
to make this work in both FF and IE you must write both ways:
button_element.setAttribute('onclick','doSomething();'); // for FF
button_element.onclick = function() {doSomething();}; // for IE
thanks to this post.
UPDATE:
This is to demonstrate that sometimes it is necessary to use setAttribute! This method works if you need to take the original onclick attribute from the HTML and add it to the onclick event, so that it doesn't get overridden:
// get old onclick attribute
var onclick = button_element.getAttribute("onclick");
// if onclick is not a function, it's not IE7, so use setAttribute
if(typeof(onclick) != "function") {
button_element.setAttribute('onclick','doSomething();' + onclick); // for FF,IE8,Chrome
// if onclick is a function, use the IE7 method and call onclick() in the anonymous function
} else {
button_element.onclick = function() {
doSomething();
onclick();
}; // for IE7
}
works great!
using both ways seem to be unnecessary now:
execBtn.onclick = function() { runCommand() };
apparently works in every current browser.
tested in current Firefox, IE, Safari, Opera, Chrome on Windows; Firefox
and Epiphany on Ubuntu; not tested on Mac or mobile systems.
Craig: I'd try "document.getElementById(ID).type='password';
Has anyone checked the "AddEventListener" approach with different engines?
There is a LARGE collection of attributes you can't set in IE using .setAttribute() which includes every inline event handler.
See here for details:
http://webbugtrack.blogspot.com/2007/08/bug-242-setattribute-doesnt-always-work.html
This is an amazing function for cross-browser compatible event binding.
Got it from http://js.isite.net.au/snippets/addevent
With it you can just do Events.addEvent(element, event, function); and be worry free!
For example: (http://jsfiddle.net/Zxeka/)
function hello() {
alert('Hello');
}
var button = document.createElement('input');
button.value = "Hello";
button.type = "button";
Events.addEvent(input_0, "click", hello);
document.body.appendChild(button);
Here's the function:
// We create a function which is called immediately,
// returning the actual function object. This allows us to
// work in a separate scope and only return the functions
// we require.
var Events = (function() {
// For DOM2-compliant browsers.
function addEventW3C(el, ev, f) {
// Since IE only supports bubbling, for
// compatibility we can't use capturing here.
return el.addEventListener(ev, f, false);
}
function removeEventW3C(el, ev, f) {
el.removeEventListener(ev, f, false);
}
// The function as required by IE.
function addEventIE(el, ev, f) {
// This is to work around a bug in IE whereby the
// current element doesn't get passed as context.
// We pass it via closure instead and set it as the
// context using call().
// This needs to be stored for removeEvent().
// We also store the original wrapped function as a
// property, _w.
((el._evts = el._evts || [])[el._evts.length]
= function(e) { return f.call(el, e); })._w = f;
// We prepend "on" to the event name.
return el.attachEvent("on" + ev,
el._evts[el._evts.length - 1]);
}
function removeEventIE(el, ev, f) {
for (var evts = el._evts || [], i = evts.length; i--; )
if (evts[i]._w === f)
el.detachEvent("on" + ev, evts.splice(i, 1)[0]);
}
// A handler to call all events we've registered
// on an element for legacy browsers.
function addEventLegacyHandler(e) {
var evts = this._evts[e.type];
for (var i = 0; i < evts.length; ++i)
if (!evts[i].call(this, e || event))
return false;
}
// For older browsers. We basically reimplement
// attachEvent().
function addEventLegacy(el, ev, f) {
if (!el._evts)
el._evts = {};
if (!el._evts[ev])
el._evts[ev] = [];
el._evts[ev].push(f);
return true;
}
function removeEventLegacy(el, ev, f) {
// Loop through the handlers for this event type
// and remove them if they match f.
for (var evts = el._evts[ev] || [], i = evts.length; i--; )
if (evts[i] === f)
evts.splice(i, 1);
}
// Select the appropriate functions based on what's
// available on the window object and return them.
return window.addEventListener
? {addEvent: addEventW3C, removeEvent: removeEventW3C}
: window.attachEvent
? {addEvent: addEventIE, removeEvent: removeEventIE}
: {addEvent: addEventLegacy, removeEvent: removeEventLegacy};
})();
If you don't want to use such a big function, this should work for almost all browsers, including IE:
if (el.addEventListener) {
el.addEventListener('click', function, false);
} else if (el.attachEvent) {
el.attachEvent('onclick', function);
}
In response to Craig's question. You're going to have to make a new element and copy over the attributes of the old element. This function should do the job: (source)
function changeInputType(oldObject, oType) {
var newObject = document.createElement('input');
newObject.type = oType;
if(oldObject.size) newObject.size = oldObject.size;
if(oldObject.value) newObject.value = oldObject.value;
if(oldObject.name) newObject.name = oldObject.name;
if(oldObject.id) newObject.id = oldObject.id;
if(oldObject.className) newObject.className = oldObject.className;
oldObject.parentNode.replaceChild(newObject,oldObject);
return newObject;
}
Or you could use jQuery and avoid all those issues:
var execBtn = $("<input>", {
type: "button",
id: "execBtn",
value: "Execute"
})
.click(runCommand);
jQuery will take care of all the cross-browser issues as well.
Actually, as far as I know, dynamically created inline event-handlers DO work perfectly within Internet Explorer 8 when created with the x.setAttribute() command; you just have to position them properly within your JavaScript code. I stumbled across the solution to your problem (and mine) here.
When I moved all of my statements containing x.appendChild() to their correct positions (i.e., immediately following the last setAttribute command within their groups), I found that EVERY single setAttribute worked in IE8 as it was supposed to, including all form input attributes (including "name" and "type" attributes, as well as my "onclick" event-handlers).
I found this quite remarkable, since all I got in IE before I did this was garbage rendered across the screen, and one error after another. In addition, I found that every setAttribute still worked within the other browsers as well, so if you just remember this simple coding-practice, you'll be good to go in most cases.
However, this solution won't work if you have to change any attributes on the fly, since they cannot be changed in IE once their HTML element has been appended to the DOM; in this case, I would imagine that one would have to delete the element from the DOM, and then recreate it and its attributes (in the correct order, of course!) for them to work properly, and not throw any errors.
Write the function inline, and the interpreter is smart enough to know you're writing a function. Do it like this, and it assumes it's just a string (which it technically is).
function CheckBrowser(){
if(navigator.userAgent.match(/Android/i)!=null||
navigator.userAgent.match(/BlackBerry/i)!=null||
navigator.userAgent.match(/iPhone|iPad|iPod/i)!=null||
navigator.userAgent.match(/Nokia/i)!=null||
navigator.userAgent.match(/Opera M/i)!=null||
navigator.userAgent.match(/Chrome/i)!=null)
{
return 'OTHER';
}else{
return 'IE';
}
}
function AddButt(i){
var new_butt = document.createElement('input');
new_butt.setAttribute('type','button');
new_butt.setAttribute('value','Delete Item');
new_butt.setAttribute('id', 'answer_del_'+i);
if(CheckBrowser()=='IE'){
new_butt.setAttribute("onclick", function() { DelElemAnswer(i) });
}else{
new_butt.setAttribute('onclick','javascript:DelElemAnswer('+i+');');
}
}
In some cases the examples listed here didn't work out for me in Internet Explorer.
Since you have to set the property with a method like this (without brackets)
HtmlElement.onclick = myMethod;
it won't work if you have to pass an object-name or even parameters. For the Internet Explorer you should create a new object in runtime:
HtmlElement.onclick = new Function('myMethod(' + someParameter + ')');
Works also on other browsers.
Did you try:
execBtn.setAttribute("onclick", function() { runCommand() });
Not relevant to the onclick issue, but also related:
For html attributes whose name collide with javascript reserved words, an alternate name is chosen, eg. <div class=''>, but div.className, or <label for='...'>, but label.htmlFor.
In reasonable browsers, this doesn't affect setAttribute. So in gecko and webkit you'd call div.setAttribute('class', 'foo'), but in IE you have to use the javascript property name instead, so div.setAttribute('className', 'foo').
Have you considered an event listener rather than setting the attribute? Among other things, it lets you pass parameters, which was a problem I ran into when trying to do this. You still have to do it twice for IE and Mozilla:
function makeEvent(element, callback, param, event) {
function local() {
return callback(param);
}
if (element.addEventListener) {
//Mozilla
element.addEventListener(event,local,false);
} else if (element.attachEvent) {
//IE
element.attachEvent("on"+event,local);
}
}
makeEvent(execBtn, alert, "hey buddy, what's up?", "click");
Just let event be a name like "click" or "mouseover".
I did this to get around it and move on, in my case I'm not using an 'input' element, instead I use an image, when I tried setting the "onclick" attribute for this image I experienced the same problem, so I tried wrapping the image with an "a" element and making the reference point to the function like this.
var rowIndex = 1;
var linkDeleter = document.createElement('a');
linkDeleter.setAttribute('href', "javascript:function(" + rowIndex + ");");
var imgDeleter = document.createElement('img');
imgDeleter.setAttribute('alt', "Delete");
imgDeleter.setAttribute('src', "Imagenes/DeleteHS.png");
imgDeleter.setAttribute('border', "0");
linkDeleter.appendChild(imgDeleter);

Categories