10

Is there a clean and robust way in which I can test (using pure javascript or also jQuery) if an HTML element can contain some text?

For instance, <br>, <hr> or <tr> cannot contain text nodes, while <div>, <td> or <span> can.

The simplest way to test this property is to control the tag names. But is this the best solution? I think it is one of the worst...

EDIT: In order to clarify the sense of the question, need to point out that the perfect answer should consider two problems:

  1. According to HTML standards, can the element contain a text node?
  2. If the element contains some text, will it be shown?

Obviously, there is a sub-answer for each point of the previous list.

Vito Gentile
  • 13,336
  • 9
  • 61
  • 96
  • add some text, see if it added, then remove it? ugly but i don't think there's any other way – jbabey Nov 05 '13 at 13:46
  • 1
    Well, the problem is that technically `` *can* contain text, it just gets rendered above the table. – Niet the Dark Absol Nov 05 '13 at 13:47
  • 3
    Blacklisting tag names may be the only reliable option... – Niet the Dark Absol Nov 05 '13 at 13:47
  • @jbabey Not that simple. `$('
    test').text().length;` returns 4.
    – h2ooooooo Nov 05 '13 at 13:47
  • 2
    Maybe you'd be better off just created your own whitelist of 'text-able' tags in an array. – George Nov 05 '13 at 13:48
  • 1
    checking node name is probably the best solution `var node = $('#test')[0].nodeName; node.match(/br|hr|tr/gi);` – Anton Nov 05 '13 at 13:49
  • @Niet the Dark Absol: Yes, and the answer should consider also this problem: a) test if an element can contain text (considering the HTML standards); b) simply test if the text inside the element is displayed (also if the element could not contain nothing). – Vito Gentile Nov 05 '13 at 13:51
  • walk the dom by Douglas Crockford and some if's and your done – j0hnstew Nov 05 '13 at 13:58
  • @lombausch [Not according to the standard that specifies it as a void element.](http://dev.w3.org/html5/markup/hr.html#hr) – h2ooooooo Nov 05 '13 at 14:02
  • @h2ooooooo sure, but if you really want you can place some text in it thus besides blacklisting you hardly get a reliable method to detect void elements. – webduvet Nov 05 '13 at 14:05

3 Answers3

5

The W3 standard for "void elements" specifies:

Void elements
area, base, br, col, embed, hr, img, input, keygen, link, menuitem, meta, param, source, track, wbr

And apparently there's some unofficial tags as well.

You can make a black list and use .prop('tagName') to get the tag name:

(function ($) {
    var cannotContainText = ['AREA', 'BASE', 'BR', 'COL', 'EMBED', 'HR', 'IMG', 'INPUT', 'KEYGEN', 'LINK', 'MENUITEM', 'META', 'PARAM', 'SOURCE', 'TRACK', 'WBR', 'BASEFONT', 'BGSOUND', 'FRAME', 'ISINDEX'];

    $.fn.canContainText = function() {
        var tagName = $(this).prop('tagName').toUpperCase();

        return ($.inArray(tagName, cannotContainText) == -1);
    };
}(jQuery));

$('<br>').canContainText(); //false
$('<div>').canContainText(); //true

Here you can also add your own tags to cannotContainText (eg. to add <tr> which is not officially a void element as it doesn't match the specification "A void element is an element whose content model never allows it to have contents under any circumstances. Void elements can have attributes.").

Community
  • 1
  • 1
h2ooooooo
  • 39,111
  • 8
  • 68
  • 102
  • It is a very nice answer. But do you know some way (or an official list like the above one) to consider all the "non-void" elements that should not contain text? – Vito Gentile Nov 05 '13 at 14:06
  • 1
    @VitoShadow I'm not sure but on the [w3 page](http://www.w3.org/TR/html-markup/td.html#td) you can see what elements are allowed. If "flow content" is allowed, then it allows text. You might want to crawl w3 for elements without "flow control", but I don't know any other method as of now. [tr, for example, doesn't have flow content](http://www.w3.org/TR/html-markup/tr.html#tr). – h2ooooooo Nov 05 '13 at 14:12
  • Very interesting. Now we only need a method to test if an element can have flow content :) – Vito Gentile Nov 05 '13 at 14:19
1

I believe you're looking for a list of void HTML tags:

The following is a complete list of the void elements in HTML:

area, base, br, col, command, embed, hr, img, input, keygen, link, meta, param, source, track, wbr

From there you would just test the node to see if it is in the list. For example:

var voidNodeTags = ['AREA', 'BASE', ...];
var isNodeVoid = function (node) {
    return voidNodeTags.indexOf(node.nodeName) !== -1;
};

http://jsfiddle.net/3uQjH/

Vito Gentile
  • 13,336
  • 9
  • 61
  • 96
jbabey
  • 45,965
  • 12
  • 71
  • 94
  • Very simple, clean and working solution. But I also need a solution that also consider all the "non-void" elements that should not contain text. Do you some ideas? – Vito Gentile Nov 05 '13 at 14:07
  • @VitoShadow if there are more elements you want to blacklist, add their tags to the array. – jbabey Nov 05 '13 at 14:15
  • 1
    Do note that `indexOf` on arrays was first added in IE9. – h2ooooooo Nov 05 '13 at 14:16
0

I mostly agree with everybody that a blacklist would be a very orthodox way of doing it but if we are really required to test for the ability, then this would be one way I think (with some jquery):

isTextContainerTag=function(tagName){
    try{       
        var test = $('<'+tagName+'></'+tagName+'>');
        test.html(123);
        if(test.html()=='123'){
            return true;   
        }else{
            return false;   
        }
    }
    catch(err){return false;}
}

I tested it on Chrome on various tags names and got these results:

    console.log('input',isTextContainerTag('input'));//answer:false
    console.log('textarea',isTextContainerTag('textarea'));//true
    console.log('option',isTextContainerTag('option'));//true
    console.log('ul',isTextContainerTag('ul'));//true
    console.log('li',isTextContainerTag('li'));//true
    console.log('tr',isTextContainerTag('tr'));//true
    console.log('td',isTextContainerTag('td'));//true
    console.log('hr',isTextContainerTag('hr'));//false
    console.log('br',isTextContainerTag('br'));//false
    console.log('div',isTextContainerTag('div'));//true
    console.log('p',isTextContainerTag('p'));//true
    console.log('html',isTextContainerTag('html'));//false
    console.log('body',isTextContainerTag('body'));//false
    console.log('table',isTextContainerTag('table'));//false
    console.log('tbody',isTextContainerTag('tbody'));//true

We may also test some real tags using jquery "prop" on these two examples:

<div id="A">AAAAAA</div>
<br id="B">

Which gives:

var obj1 = $('#A').prop('tagName');
var obj2 = $('#B').prop('tagName');
console.log('id:A (div)',isTextContainerTag(obj1));//true
console.log('id:B (br)',isTextContainerTag(obj2));//false

I'm sure it is way away from perfect though, but it was fun to look into.

ImShogun
  • 72
  • 8
  • I tested your solution in Firefox 24, and it doesn't work... It always returns `true`. I think the problem is that `$('<'+tagName+'>'+tagName+'>')` doesn't create a rendered node... Is it possible? – Vito Gentile Nov 05 '13 at 17:05
  • According to the standards `tr` and `tbody` can't contain text either, so while this *should* give a more accurate representation than a blacklist, it still seems to fail specific times. – h2ooooooo Nov 06 '13 at 09:14