Only Internet Explorer seems to have the element property: canHaveHtml
(MSDN, Dottoro). I can't seem to find anything in other browsers to emulate it, other than using a regex with a list of tagnames. Is there a way to determine this in Chrome, Firefox etc?
For example is there a way of checking for the innerHTML
property and is this 100% equivalent?
Only Internet Explorer seems to have the element property: canHaveHtml
(MSDN, Dottoro). I can't seem to find anything in other browsers to emulate it, other than using a regex with a list of tagnames. Is there a way to determine this in Chrome, Firefox etc?
For example is there a way of checking for the innerHTML
property and is this 100% equivalent?
- For what purpose do you need to check the canHaveHtml property? Manybe it can be done using a different property – Shai Aharoni Commented Apr 21, 2013 at 10:34
- Isn't it possible to add children to all tags? since its valid in html/xml? Do you have any error returned when doing so? – Grzegorz Kaczan Commented Apr 21, 2013 at 10:35
-
1
@GrzegorzKaczan —
<input> foo </input>
is not valid in HTML. – Quentin Commented Apr 21, 2013 at 10:35 -
Can't test at the moment, but can you create a test using
document.createElement(elementToTest).appendChild('span');
(though now I'm not sure thatappendChild()
can even be chained like that...) – David Thomas Commented Apr 21, 2013 at 10:39 -
1
Just tested in Chrome, you can append a child to an
<input>
elements (using the HTML5 doctype), but it's not displayed anyway. So the property just doesn't have any sense in Chrome. – MaxArt Commented Apr 21, 2013 at 10:43
4 Answers
Reset to default 5You can use the following function to determine whether a named element may have children.
However, as noted by ZER0, this is probably more like a replacement for IE's canHaveChildren rather than canHaveHtml, as it returns true for any tag name that is "supposed" to not be empty.
function canHaveHtml(tag) {
return document.createElement(tag).outerHTML.indexOf("></") > 0;
}
It uses the fact that a newly created element, that cannot (or should not) have content, has outerHtml
without an end tag.
For example:
document.createElement("input").outerHTML === "<input>"
and
document.createElement("div").outerHTML === "<div></div>"
I believe there is no specifications about that:
http://www.w3/TR/domcore/#concept-node-append
For instance, in Firefox, Chrome and Safari, you can actually add nodes to elements like <input>
for example:
var input = document.createElement("input");
var div = document.createElement("div");
div.textContent = 'hello';
console.log(input.outerHTML, input.childNodes.length);
input.appendChild(div);
console.log(input.outerHTML, input.childNodes.length);
They're just not rendered. But they're considered children of the input
node in both case. In case of Firefox the outerHTML
is not changed, only the childNodes
length reports 1, in case of Chrome and Safari the outerHTML
changes from <input>
to <input></input>
.
In Firefox, as opposite of Safari and Chrome, innerHTML
returns actually the children's HTML, even if it's not rendered and is not returned in outerHTML
.
Update:
As @Bergi pointed out in @MårtenWikström answer, approaches like the previous I made doesn't really works well on element that can have content, like textarea
, or even title
, but not HTML content. Therefore, a better canHaveHTML
could be something like that:
// Improving the `canHaveHTML` using `canHaveChildren`,
// using the approach shown by Mårten Wikström
function canHaveChildren(node) {
// Uses the native implementation, if any.
// I can't test on IE, so maybe it could be worthy to never use
// the native implementation to have a consistent and controlled
// behaviors across browsers. In case, just remove those two lines
if (node && node.canHaveChildren)
return node.canHaveChildren();
// Returns false if it's not an element type node; or if it has a end tag.
// Use the `ownerDocument` of the `node` given in order to create
// the node in the same document NS / type, rather than the current one,
// useful if we works across different windows / documents.
return node.nodeType === 1 && node.ownerDocument
.createElement(node.tagName).outerHTML.indexOf("></") > 0;
}
function canHaveHTML(node) {
// See ment in `canHaveChildren` about native impl.
if (node && node.canHaveHTML)
return node.canHaveHTML();
// We don't bother to create a new node in memory if it
// can't have children at all
if (!canHaveChildren(node))
return false;
// Can have children, then we'll check if it can have
// HTML children.
node = node.ownerDocument.createElement(node.tagName);
node.innerHTML = "<b></b>";
// if `node` can have HTML children, then the `nodeType`
// of the node just inserted with `innerHTML` has to be `1`
// (otherwise will be likely `3`, a textnode).
return node.firstChild.nodeType === 1;
}
Tested in Firefox, Chrome and Safari; that should cover all the nodes and all the scenarios.
I can't seem to find anything in other browsers to emulate it, other than using a regex with a list of tagnames.
It may not seem elegant or clever, but creating a whitelist (or blacklist) is the easiest, fastest, and most reliable approach. You don't need a regular expression; you can create use a simple structure such as an associative array.
// blacklist approach
var noChildren = {
input: true,
meta: true,
br: true,
link: true,
img: true
// other properties here
};
function canHaveChildren(tagName) {
tagName = tagName.toLowerCase();
alert(noChildren[tagName] === undefined);
}
canHaveChildren("BR");
canHaveChildren("div");
Demo: http://jsfiddle/FTbWa/
Reusable function: Github Gist
This paradigm is not without precedence; it's used in many script libraries and HTML parsers/sanitizers. For example, look at the source of jQuery and you'll notice many element-specific tests and arrays of element names and attribute names.
Here I am assuming that if the element does not have a innerHTML property, it should have a value or a src or a type property. Cant think of any other way. You could add specific TagName checks to handle specific cases which the code below fails to handle.
function CheckInnerHTMLSupport(oElement)
{
var oDiv = document.createElement(oElement.tagName);
if('canHaveHTML' in oDiv)
{
return oDiv.canHaveHTML;
}
var bSupportsInnerHTML = oDiv.type === undefined && oDiv.value === undefined && oDiv.src === undefined;
return bSupportsInnerHTML;
}