Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at the jQuery Conference 2010 (Boston, MA) with:

Ask Ben: Excluding Script Tag Content From The jQuery .text() Method

Posted by Ben Nadel

Hi Ben, I have come across an issue with jQuery when using "text().length" and I am hoping that you may be able to help. I have applied a CSS class called hideEmpty to a div. I am then using jQuery text().length to provide the total length of that element, including child nodes, and hiding it if it is low. This works very well in most situations but falls over when there is some inline script as a child element. IE ignores the script but all other browsers include this in the total length. I am unsure of any way around this - I cannot update the HTML as I am developing for a CMS and that is simply how it can output some areas. If you run the code supplied you should see the issue which I am having... Any help would be greatly appreciated.

Before I answer this question, let me first duplicate the problem so that others can get a good sense of what is going on. Basically, the page has a number of Div elements that may or may not contain child Script tags. If the text() content of these Div elements is small (ie. less than 3 characters), then the Divs are being hidden:

  • <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  • <html>
  • <head>
  • <title>
  • Excluding Script Tag Content From The jQuery .text() Method
  • </title>
  •  
  • <script type="text/javascript" src="jquery-1.3.2.js"></script>
  • <script type="text/javascript">
  •  
  • // When the DOM is ready, intialize it.
  • $(function(){
  •  
  • // Get all of the elements we want to check for
  • // hiding and iterate over them.
  • $( ".hide-empty" ).each(
  • function(){
  • // Gather the length of the content contained
  • // within the given div. This will get all of
  • // the text node values.
  • var content = $( this ).text();
  •  
  • // Get the length of the contente.
  • var contentLength = $.trim( content ).length;
  •  
  • // Alert content length for debugging.
  • alert( "Content Length: " + contentLength );
  •  
  • // Check to see if this content is small
  • // enough to hide.
  • if (contentLength < 3){
  •  
  • // There is practically no content - hide
  • // the container.
  • $( this ).hide();
  •  
  • }
  • }
  • );
  •  
  • });
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <h1>
  • Excluding Script Tag Content From The jQuery .text() Method
  • </h1>
  •  
  • <div class="hide-empty">
  •  
  • <!--- The content we do NOT want to count. --->
  • <script type="text/javascript">
  • var inlineScript = "Some other content";
  • </script>
  •  
  • <!--- The "Real" content of this DIV. --->
  • 12
  •  
  • </div>
  •  
  • </body>
  • </html>

When this page loads, we are grabbing each Div with the class, "hide-empty," and checking its text() content length. If it is less than 3, we are hiding it (the given Div). The problem, however, is that the text() method is picking up the content of the Script tag (in non-IE browsers) and is including it in the aggregated content which gives the length an "inaccurate" value. And, in fact, when we run this code, we get the alert:

Content Length: 55

To get around this, what we want to do is exclude the Script tag from the content aggregation; however, we don't want to actually remove the Script tag from the page as it needs to be there. What we can do, however, is duplicate the given jQuery collection so that we have a mirrored copy outside of the Document Object Model (DOM) branch. Once we have this mirrored copy, we can strip the Script tags out of that before we aggregate its textual content (NOTE: I am only showing the Javascript here as it is the only thing that changed):

  • <script type="text/javascript">
  •  
  • // When the DOM is ready, intialize it.
  • $(function(){
  •  
  • // Get all of the elements we want to check for
  • // hiding and iterate over them.
  • $( ".hide-empty" ).each(
  • function(){
  • // Duplicate the current target DOM tree and
  • // strip out any of the Script tags contained
  • // within it.
  • //
  • // NOTE: The .end() call here is required to
  • // get back to the root of the cloned tree
  • // (instead of staying at the script tags).
  • var clonedTree = $( this )
  • .clone()
  • .find( "script" )
  • .remove()
  • .end()
  • ;
  •  
  • // Gather the length of the content contained
  • // within the cloned tree node. This will get
  • // all of the text node values except for the
  • // Script tags, which were stripped out.
  • var content = clonedTree.text();
  •  
  • // Get the length of the contente.
  • var contentLength = $.trim( content ).length;
  •  
  • // Alert content length for debugging.
  • alert( "Content Length: " + contentLength );
  •  
  • // Check to see if this content is small
  • // enough to hide.
  • if (contentLength < 3){
  •  
  • // There is practically no content - hide
  • // the container.
  • $( this ).hide();
  •  
  • }
  • }
  • );
  •  
  • });
  •  
  • </script>

In this example, once we are in the Div iteration, I clone the current target node. This will create a duplicate copy of the DOM branch in-memory (NOTE: This will not duplicate event bindings as the clone() was not deep). I then search the detached DOM branch for Script tags and remove them from their parent containers. Once I've done that, our parallel, detached DOM tree no longer contains the Script tags since the Script tags were never in the root of the detached branch (which remains in the jQuery collection even when it is removed from the DOM). Taking this Script-less version of the DOM, I then gather the text() and use that to check the content. And, this time, when we run the code, we get the alert:

Content Length: 2

Now, we get the desired content length. This is the easiest way that I have found. I hope it helps!



Reader Comments

Here's another way to do it without having to copy the DOM node into memory. Instead just diff the length of the .hide-empty and script nodes. It requires each .hide-empty node to have an id attribute, and there may be a way around that.

$('.hide-empty').each(function(){
var hidableDiv = $(this);
//total length of this node
var hidableDiv_len = $.trim(hidableDiv.text()).length;
//length of the script node by using child selector
var script_len = $.trim($('#' + hidableDiv.attr('id') + '> script').text()).length;
//length of the node minus the script length
var total_len = hidableDiv_len - script_len;
if(total_len < 3){
$(this).hide();
}
});

Reply to this Comment

@Mike,

That's a good thought, but I think it doesn't quite work due to the way the white space occurs and cannot be trimmed. Because the white space that occurs in the first text() call, between the visible content and the hidden content, does not get trimmed, subtracting the length of the hidden content length will not measure up.

To demonstrate what I mean, take this very simple example (in which spaces are denoted with periods):

<div>..hello..<script>..there</script></div>

First text() result would be (with trim):

hello....there

(length: 14)

Grabbing the script value would result in (no trim):

..there

(length: 7)

Subtracting the two lengths would be: 7. But, there are only 5 characters in Hello.

The problem here is the white space after the visible text, but before the script tag; it never has the opportunity to get stripped out since it's never at the end of a value.

Reply to this Comment

Hi Ben,

I need to show a dynamic tree populated form database using json(I am using dojo framework).

On clicking each node, it makes a call to the server to show the corresponding child node.

I am using cfajaxproxy.Basically I need to use the query object as a json variable.

But I am struggling to make the hierarchy tree using json.

Help me Ben.

Thanks,

Abhijit

Reply to this Comment

hi ben,

i want to copy the tags, contents, and styles of a div into a new window so that i can save it as a separate html file. how do i do it in javascript, jquery or prototype?

thanks so much in advance!

thanks also for this very helpful blog.

maski

Reply to this Comment

@Maski,

I am not sure that would work. Even if you were to write the HTML to the new window using Javascript, I believe any attempt to save the file would save the *original* file source before it was updated with the Javascript.

Reply to this Comment

Thanks for this, Ben - it resolves the issue (and major headache) I was having. I think the differences between browsers means that I will never quite get them to agree exactly on the length of content, but with this script, plus a little bit of 'fuzziness' in the given length (i.e. if (contentLength < 10), say, to allow for a certain amount of white-space/line breaks), then we have ourselves the best way of achieving what I was after...

Reply to this Comment

Post A Comment

?
You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.