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

Posted September 22, 2009 at 9:26 AM by Ben Nadel

Tags: Javascript / DHTML, Ask Ben

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

Sep 22, 2009 at 2:49 PM // reply »
1 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();
}
});


Sep 23, 2009 at 8:00 AM // reply »
11,314 Comments

@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.


Oct 11, 2009 at 2:32 PM // reply »
33 Comments

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


Oct 13, 2009 at 11:50 AM // reply »
1 Comments

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


Oct 15, 2009 at 8:34 AM // reply »
11,314 Comments

@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.


Oct 20, 2009 at 3:20 AM // reply »
3 Comments

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...


Oct 31, 2009 at 4:34 PM // reply »
11,314 Comments

@John,

Awesome - glad you got it working.


Post A Comment

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.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
Jun 19, 2013 at 2:01 PM
Experimenting With The Amazon Simple Storage Service (S3) API Using ColdFusion
I have coincidentally been beating my head against the S3 API for the last week or so. One big "gotcha" I had to work around was file names and paths containing spaces. Remember to URL Enco ... read »
Jun 19, 2013 at 1:27 PM
Using Slice(), Substring(), And Substr() In Javascript
very good article. By the way IE supports negative values in substr or slice in verson 10. ... read »
Jun 19, 2013 at 11:33 AM
Filter vs. ngHide With ngRepeat In AngularJS
In your assessment, is it correct to say that given a list of say 500 items its more performant to use the `ngHide` method over the `filter` method? ... read »
Jun 19, 2013 at 10:18 AM
ColdFusion Path Usage And Manipulation Overview
Anyone happen to know if the file created by getTempFile will be automatically removed at any point? Nothing mentioned in the docs, and restarting CF doesn't remove them, so it seems it needs manu ... read »
Jun 19, 2013 at 9:41 AM
Working With Inherited Collections In AngularJS
I actually just ran into this same situation with a demo I was putting together. Your implementation of multi-lvl $scope's > Mine :) ... read »
Jun 19, 2013 at 8:17 AM
My Experience With AngularJS - The Super-heroic JavaScript MVW Framework
@Prateek, to match a word or text you should use .toContain('word') that's a jasmine reference. website is : http://pivotal.github.io/jasmine/ ... read »
Jun 19, 2013 at 8:10 AM
My Experience With AngularJS - The Super-heroic JavaScript MVW Framework
Hi Guys, Actually i am doing e2e test of angular js of my project but i am not getting one thing that is how to press enter key through the test when my form is filled as i am not using a button but ... read »
Jun 18, 2013 at 9:20 PM
Mapping AngularJS Routes Onto URL Parameters And Client-Side Events
I couldn't find examples of passing multiple arguments using the when() routing statement so figured out through trial and error that you can pass multiple arguments using the following format: .whe ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools