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 »
10,640 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 »
10,640 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 »
10,640 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
InVision App - Prototyping Made Beautiful With Prototyping Tools Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
Feb 10, 2012 at 7:21 PM
jQuery AJAX Strips Script Tags And Inserts Them After Parent-Most Elements
Update! Instead of $(eval(options.insertAfter)).after(data['insertData']); I now use: var ajaxNode = document.createElement('span'); var parent = $(eval(options.insertAfter))[0].parentNode; ... read »
Feb 10, 2012 at 6:18 PM
jQuery AJAX Strips Script Tags And Inserts Them After Parent-Most Elements
encountered this same, what I consider, jQuery bug last week. I'm building a site in which I load some content via AJAX. This content contains Linkedin share button placeholders which Linkedin API ne ... read »
Feb 10, 2012 at 11:30 AM
Cross-Origin Resource Sharing (CORS) AJAX Requests Between jQuery And Node.js
After you understand the concepts here, this is an awesome cheatsheet for enabling CORS in just about anything http://enable-cors.org/ ... read »
JM
Feb 10, 2012 at 9:10 AM
My Safari Browser SQLite Database Hello World Example
@Amy, Here is a very good tutorial on how to use JOIN: http://www.sqltutorial.org/sqljoin-innerjoin.aspx ... read »
Feb 10, 2012 at 4:42 AM
Building A Twitter-Inspired RESTful API Architecture In ColdFusion
This is great, very useful Ben. I spotted a small typo in the api.cgm listing: <cfthrow type="Unauthroized" /> Cheers Stefan ... read »
Feb 9, 2012 at 10:35 PM
CFDirectory Filtering Uses Pipe Character For Multiple Filters (Thanks Steve Withington)
I was wondering if there would be a filter you could apply so that you got everything but what you included in the filter. As in show me all docs that are not a .pdf. ... read »
Feb 9, 2012 at 10:29 PM
Learning ColdFusion 9: Application-Specific Data Sources
@Ben, No offence, but if people were really wanting advanced features they would be using a platform like ASP.NET MVC. CFML is so structurally compromised as a tag-based scripting language that ... read »
Feb 9, 2012 at 10:03 PM
Subversion - Cleanup Failed To Process The Following Paths
@Leviaguirre, do you still have problems with this? ... read »