14

This should be real easy. Given below is the HTML.

<div id='attachmentContainer'>
    #Attachment#
    <span id='spnAttachmentName' class='hidden'>#AttachmentName#</span>
    <span id='spnAttachmentPath' class='hidden'>#AttachmentPath#</span>
</div>  

I want to get just the #Attachment# and not the other text. When I tried

$("#attachmentContainer").text() 

it gives out all #Attachment#, #AttachmentName# as well as #AttachmentPath#. I know I could just put #Attachment# into another span and access it directly but I was just intrigued on how to do this. Any help is much appreciated.

Raja
  • 3,608
  • 3
  • 28
  • 39
  • This question was already answered here: http://stackoverflow.com/questions/1476787/jquery-innertext-not-including-sub-element – Gerald Senarclens de Grancy May 05 '10 at 19:10
  • 1
    This might be one of those rare times where breaking out into pure javascript may not be a bad idea ... – James Westgate May 05 '10 at 19:27
  • @James: jQuery is "pure JavaScript" from top to bottom. I'm not sure why people always think that "JavaScript" and "HTML DOM handling" are the same thing. JavaScript *is the language*, and the DOM is merely one API that is available to it. – Tomalak May 05 '10 at 20:13
  • Yep. It is javascript. But is it pure? No. Its a functional layer to abstract and unify DOM manipulation. – James Westgate May 05 '10 at 20:19
  • @James: To be pedantic, jQuery is pure JavaScript as well, by all definitions of the word "pure". But I understand what you mean. ;) – Tomalak May 05 '10 at 20:27

6 Answers6

11

Since your text happens to be the first child node of the <div>:

var firstChild = $("#attachmentContainer")[0].firstChild;
var textValue  = firstChild.nodeType == 3 ? $.trim(firstChild.nodeValue) : "";

The nodeType check is meant to be a safeguard - it makes sure you are actually handling a text node - the firstChild might be something different after all. React accordingly, this is just an example.

To retrieve the value of all text children (or a specific one), just loop over the childNodes collection of your element, concatenating all bits you find into a string:

// the optional "at" parameter lets you define which text node you want
// if not given, this returns all text nodes concatenated
$.fn.ownText = function(at) { 
  var result = [], node = this[0];
  if (!(node && node.childNodes)) return;
  for (var i=0; i<node.childNodes.length; i++) {
    var child = node.childNodes[i];
    if (child.nodeType != 3) continue;
    var t = $.trim(child.nodeValue);
    if (t != '') result.push(t);
  }
  return at ? result[at-1] : result.join(' ');
}

var text = $("#attachmentContainer").ownText();  // all text children
var text = $("#attachmentContainer").ownText(1); // first text child only
Tomalak
  • 332,285
  • 67
  • 532
  • 628
6

This will get you just that items text

var $item = $("#attachmentContainer").clone();
$item.children().remove(); 
alert($item.text());

clone the object so you don't have to remove the actual items children. Then you can remove the child elements and that will leave the innerText of the item you want.

And here's a handy little method to do this easily

jQuery.fn.trueText = function(obj){
    var $item = $(obj).clone();
    $item.children().remove(); 
    return $item.text();
};

Now you can call $("#attachmentContainer").trueText()

hunter
  • 62,308
  • 19
  • 113
  • 113
  • I think this is more readable and more jQuery-able than Tomalaks answer, but to each its own – hunter May 05 '10 at 19:12
  • 1
    @Hunter This might involve less code, but it certainly involves a *lot* more DOM manipulation, which is one of the slower parts of JavaScript. – user229044 May 05 '10 at 19:21
  • @hunter: I guess `clone()` can be expensive under certain circumstances. And it would work if there aren't any other text nodes that you are *not* interested in - it can not get the first text child. – Tomalak May 05 '10 at 19:23
  • @hunter - An even more jQuery way that doesn't involve cloning/altering nodes has been posted - http://stackoverflow.com/questions/2775893/how-to-get-the-text-of-a-div-which-is-not-a-part-of-any-other-container-in-jquery/3003950#3003950 – gnarf Jun 09 '10 at 11:51
2

$('#attachmentContainer').contents().filter(function(){return this.nodeType==3;}).text()

  • I believe you should explicitly check that `nodeType===3` (text node) rather than nodeType!=1. See [here](https://developer.mozilla.org/en/DOM/element.nodeType) for the other potential nodeType values your `filter` function will return. Otherwise, very elegant solution. – user229044 May 05 '10 at 19:26
  • @meagar: thanks, you're totally right. Had to look more thoroughly for node types. – Klaster_1 Нет войне May 05 '10 at 19:31
  • Nice straightforward solution, +1. It will return excess whitespace, though. *(Please also edit in the correction from @meagar's comment.)* – Tomalak May 05 '10 at 19:35
1

Copied from my own answer on a similar thread

This example uses .contents() to get all the children nodes (including text nodes), then uses .map() to turn each child node into a string based on the nodeType. If the node is a text node (i.e. text not within the spans), we return its nodeValue.

This returns a jQuery set containing strings, so we call .get() to get a 'standard' array object that we can call .join() on.

// our 'div' that contains your code:
var $html = $("<div id='attachmentContainer'>\n    #Attachment#\n    <span id='spnAttachmentName' class='hidden'>#AttachmentName#</span>\n    <span id='spnAttachmentPath' class='hidden'>#AttachmentPath#</span>\n</div>");

// Map the contents() into strings
$html.contents().map(function() { 
  // if the node is a textNode, use its nodeValue, otherwise empty string
  return this.nodeType == 3 ? this.nodeValue : ''; 
  // get the array, and join it together:
}).get().join('');

// " 
//     #Attachment# 
//     
//     
// "

If you want to trim extra whitespace, you can use $.trim(this.nodeValue)

If you need to do this a lot, you could even make a plugin (now with some options):

$.fn.directText = function(settings) {
   settings = $.extend({},$.fn.directText.defaults, settings);
   return this.contents().map(function() {
     if (this.nodeType != 3) return undefined; // remove non-text nodes
     var value = this.nodeValue;
     if (settings.trim) value = $.trim(value);
     if (!value.length) return undefined; // remove zero-length text nodes
     return value;
   }).get().join(settings.joinText);
};

$.fn.directText.defaults = {
   trim: true,
   joinText: ''
};
Community
  • 1
  • 1
gnarf
  • 105,192
  • 25
  • 127
  • 161
0

I think the text is actually a text element - a child of the parent div. So you just need to query for the first child. Not sure though. hth

Assaf Lavie
  • 73,079
  • 34
  • 148
  • 203
0

I think the proper well-formed angle would be to put that first part in a <p> </p> (if a span was not appropriate).

I thought I could get a .filter to work on it, but couldn't quite get it...

user229044
  • 232,980
  • 40
  • 330
  • 338