views:

274

answers:

4
<p>Lorem Ipsum <a href="#">Link</a> <div ... </div> </p>

I want to put a span around 'Lorem Ipsum' without using jQuery, so the result looks like:

<p><span>Lorem Ipsum </span><a href="#">Link</a> <div ... </div> </p>

Any ideas? Thanks

A: 

Combine these 2 tutorials:

PPK on JavaScript: The DOM - Part 3

Adding elements to the DOM

Basically you need to access the node value, remove it, and create a new child element who's node value is the value of the parent item's node, then append that element (span in this case) to the parent (paragraph in this case)

Kevin Peno
A: 

How about using a regular expression in javascript and replacing "Lorem Ipsum" with "<span>Lorem Ipsum</span>" (just remember that you will have to get the "innerHTML" of the element and then replace the whole lot again which may be a bit slow)

j3frea
And will clobber all bound event handlers (if any).
Crescent Fresh
the problem is that the text is not ony two words, its a longer text. also the text will appear in different places with different textcontent. so searching for a specific text node won't work here :-/
meow
+4  A: 

First, you need some way of accessing the paragraph. You might want to give it an id attribute, such as "foo":

<p id="foo">Lorem Ipsum <a href="#">Link</a> <div ... </div> </p>

Then, you can use document.getElementById to access that element and replace its children as required:

var p = document.getElementById('foo'),
    firstTextNode = p.firstChild,
    newSpan = document.createElement('span');

// Append "Lorem Ipsum" text to new span:
newSpan.appendChild( document.createTextNode(firstTextNode.nodeValue) );

// Replace old text node with new span:
p.replaceChild( newSpan, firstTextNode );

To make it more reliable, you might want to call p.normalize() before accessing the first child, to ensure that all text nodes before the anchor are merged as one.


Oook, So you want to replace a part of a text node with an element. Here's how I'd do it:

function giveMeDOM(html) {

    var div = document.createElement('div'),
        frag = document.createDocumentFragment();

    div.innerHTML = html;

    while (div.firstChild) {
        frag.appendChild( div.firstChild );
    }

    return frag;
}

var p = document.getElementById('foo'),
    firstChild = p.firstChild;

// Merge adjacent text nodes:
p.normalize();

// Get new DOM structure:
var newStructure = giveMeDOM( firstChild.nodeValue.replace(/Lorem Ipsum/i, '<span>$&</span>') );

// Replace first child with new DOM structure:
p.replaceChild( newStructure, firstChild );

Working with nodes at the low level is a bit of a nasty situation to be in; especially without any abstraction to help you out. I've tried to retain a sense of normality by creating a DOM node out of an HTML string produced from the replaced "Lorem Ipsum" phrase. Purists probably don't like this solution, but I find it perfectly suitable.


EDIT: Now using a document fragment! Thanks Crescent Fresh!

J-P
But, but... where is the *search* (and move to matching spot inside `firstTextNode`) for "Lorem Ipsum"?
Crescent Fresh
Ah, overlooked that, working on it now.
J-P
@J-P: my upvote awaits your edit!!
Crescent Fresh
Edited, in a rush though :P
J-P
@J-P: Damn, I was hoping to see a little `DocumentFragment` action! What about making `giveMeDOM` return a document fragment, at which point a single `p.replaceChild(p.firstChild, documentFragment)` does the magic!
Crescent Fresh
Eg `function giveMeDOM(html) { var div = document.createElement('div'), frag = document.createDocumentFragment(); div.innerHTML = html; while(div.firstChild) { frag.appendChild(div.firstChild); } return frag }`
Crescent Fresh
lol, was typing out that very thing after you suggested createDocumentFragment() ... Edited :)
J-P
@J-P: now with more cowbell! +1 ;)
Crescent Fresh
i cant give the p an id, since it appears in different divs all over the page. but thats should stop ur function from working. ill try it tonight, looks really good, thanks heaps!
meow
oh, and i cant use innerHTML.. so gotta go for .textContent || .innerText
meow
Why can't you use `innerHTML`?
J-P
A: 

UPDATE: The method below will search through the subtree headed by container and wrap all instances of textin a span element. The words can occur anywhere within a text node, and the text node can occur at any position in the subtree.

(OK, so it took more than a few minor tweaks. :P)

function wrapText(container, text) {
  // Construct a regular expression that matches text at the start or end of a string or surrounded by non-word characters.
  // Escape any special regex characters in text.
  var textRE = new RegExp('(^|\\W)' + text.replace(/[\\^$*+.?[\]{}()|]/, '\\$&') + '($|\\W)', 'm');
  var nodeText;
  var nodeStack = [];

  // Remove empty text nodes and combine adjacent text nodes.
  container.normalize();

  // Iterate through the container's child elements, looking for text nodes.
  var curNode = container.firstChild;

  while (curNode != null) {
    if (curNode.nodeType == Node.TEXT_NODE) {
      // Get node text in a cross-browser compatible fashion.
      if (typeof curNode.textContent == 'string')
        nodeText = curNode.textContent;
      else
        nodeText = curNode.innerText;

      // Use a regular expression to check if this text node contains the target text.
      var match = textRE.exec(nodeText);
      if (match != null) {
        // Create a document fragment to hold the new nodes.
        var fragment = document.createDocumentFragment();

        // Create a new text node for any preceding text.
        if (match.index > 0)
          fragment.appendChild(document.createTextNode(match.input.substr(0, match.index)));

        // Create the wrapper span and add the matched text to it.
        var spanNode = document.createElement('span');
        spanNode.appendChild(document.createTextNode(match[0]));
        fragment.appendChild(spanNode);

        // Create a new text node for any following text.
        if (match.index + match[0].length < match.input.length)
          fragment.appendChild(document.createTextNode(match.input.substr(match.index + match[0].length)));

        // Replace the existing text node with the fragment.
        curNode.parentNode.replaceChild(fragment, curNode);

        curNode = spanNode;
      }
    } else if (curNode.nodeType == Node.ELEMENT_NODE && curNode.firstChild != null) {
      nodeStack.push(curNode);
      curNode = curNode.firstChild;
      // Skip the normal node advancement code.
      continue;
    }

    // If there's no more siblings at this level, pop back up the stack until we find one.
    while (curNode != null && curNode.nextSibling == null)
      curNode = nodeStack.pop();

    // If curNode is null, that means we've completed our scan of the DOM tree.
    // If not, we need to advance to the next sibling.
    if (curNode != null)
      curNode = curNode.nextSibling;
  }
}
Annabelle
thanks for that.the problem is that the text is not lorem ipsum, its a long text.the text can also appear on different places with different content. so searching for a specific text node won't work here :-/
meow
So you need it to crawl through a portion (or all?) of the DOM tree and wrap every instance of some text string with a span? That should be possible with a bit of redesign. I will edit my function to do this.
Annabelle
I updated my response. It now crawls through a DOM subtree examining any text node it finds for the specified text.
Annabelle