How can I “merge” two HTML tags via JavaScript

Here is my HTML "sandbox":

<p>1 2 3</p>
<span id="myid">
<span id="mytext">
<p>4 5 6</p>
<p>7 8 9</p>
<p>10 11 12</p>
</span>
</span>
<p>13 14 15</p>

I would like to "merge" the p tags accordingly: (To make it easier to see I've added a minus sign where I've made the changes:)

<p>1 2 3 -
<span id="myid">
<span id="mytext">
- 4 5 6</p>
<p>7 8 9</p>
<p>10 11 12 -
</span>
</span>
- 13 14 15</p>

Every place I've added a minus sign, I have actually stripped a <p> or </p> tag making a merge between the two paragraph elements ( between 1 2 3 and 4 5 6 & between 10 11 12 and 13 14 15) - (This merge is only to achieve a display inline for 123-456 & 101112-131415).

How can I achieve this result with 'raw' JavaScript (not libraries please).

Note: I've tried to create something but It took me 80 lines of code - I am sure there is a better way of doing this.

Edit: Jon Hanna mentioned in his comment I could use correct HTML like so (as the result of JavaScript):

    <p>1 2 3 <span class="myclass mytext">4 5 6</span></p>
<p class="myclass mytext">7 8 9</p>
<p><span class="myclass mytext"10 11 12</span> 13 14 15</p>

Answers


(Bear with me, I started writing this before your latest edit. I think it's still useful, though.)

I'm not sure what you want to achieve, but I can tell you that it's very hard to achieve exactly what you're asking for using JavaScript. This is mostly because the HTML would be invalid.

Let's first walk through why the HTML would be invalid. When the browser parses HTML, it builds a nested tree of DOM Nodes. Your first example could be represented like this:

  • <p>
    • 1 2 3
  • <span id="myid">
    • <span id="mytext">
      • <p>
        • 4 5 6
      • <p>
        • 7 8 9
      • <p>
        • 10 11 12
  • <p>
    • 13 14 15

Using JavaScript and the DOM, you can walk through this tree and manipulate it. You could combine the text node from various <p> tags and place them in a single <p> tag without problem. But you won't be able to create the structure of your second example. It's simply not shaped like a DOM tree, so you can't use the DOM to create that HTML.

The only way manipulate the HTML into the shape of your second example is through innerHTML, like Jon Hanna explains. The good news is that browsers will always correct invalid HTML into a valid DOM structure. The bad news is that each browser does this differently, so you never know what you end up with.

The only real solution is to rethink your HTML structure, like you've done in your latest edit.

The way to solve your original problem depends on how static your original HTML is. I'll give a crude example, for which I'll presume that:

  • there's always exactly 5 <p>'s
  • the second through fourth of the <p>'s are always inside the <span>'s, and the first and fifth always outside them

If those two conditions are met, you could use something like this:

var paragraphs = document.getElementsByTagName('p');
var newParagraphs = [];

function createElementWithText(tagname, text, classname) {
    var classname = classname || '';
    var element = document.createElement(tagname);
    element.className = classname;
    element.appendChild(document.createTextNode(text));
    return element;
}

newParagraphs[0] = createElementWithText('p', paragraphs[0].textContent);
newParagraphs[0].appendChild(createElementWithText('span', paragraphs[1].textContent, 'myclass mytext'));

newParagraphs[1] = createElementWithText('p', paragraphs[2].textContent, 'myclass mytext');

newParagraphs[2] = document.createElement('p');
newParagraphs[2].appendChild(createElementWithText('span', paragraphs[3].textContent, 'myclass mytext'));
newParagraphs[2].appendChild(document.createTextNode(paragraphs[4].textContent));

I've posted a working example on JSFiddle: jsfiddle.net/vSrLJ/


You'll have to do it with obtaining the source-code through innerHTML etc, manipulating the string and then outputting the string.

While the DOMs generally won't stop the fact that you incorrectly have <p> inside <span>, it's just not logically possible for them to represent the output you want, since there's no HalfWrittenPElement object.

Your requirement to deliberately break the rules is at odds with the object model being designed to help you, so hacking with strings is all you've got.

Edit:

Though that said, the browsers will all fix it in their internal model anyway, so it's still going to end up as well-formed (if not necessarily valid) HTML. It'll just be so according to implementation-defined corrections rather than any spec.


I haven't tested this, but you'll get the idea. You can do the debugging and crossbrowser fine-tuning yourself ;)

I'll find all the <p> tags and for each of them, check if its siblings are <span>, <span> and <p>. Then I'll put the <span>s and the text of the second <p> inside the first <p>.

var pTags = document.getElementsByTagName('p');  // get all <p>

for (var i = 0; i < pTags.length; i++) {         // for every one of them
   var a = pTags[i];                             // take the <p> (and call it `a`)

   var b = a.nextElementSibling();               // and the next tag (call it `b`)
   if (b == null) continue;

   var c = b.nextElementSibling();               // and the next tag (call it `c`)
   if (c == null) continue;

   var d = c.nextElementSibling();               // and the next tag (call it `d`)
   if (d == null) continue;

   if (b.tagName.toUpperCase() == 'SPAN' &&      //  if b is <span>
       c.tagName.toUpperCase() == 'SPAN' &&      // and c is <span>
       d.tagName.toUpperCase() == 'P') {         // and d is <p> again

         a.appendChild(b);                       // put b inside a
         a.appendChild(c);                       // put c inside a
         a.appendChild(d.firstChild.nodeValue);  // put text of d inside a
   }
}

Note that nextElementSibling method is not cross-browser, but you can emulate it by calling nextSibling until it is of the Element type.

Note also that by calling appendChild the element first gets removed from its original container, and only then it is appended. Which is what we want.

Quick edit:

As others have pointed out, you have invalid html, what I didn't notice at first. But I suppose you wanted to have the <span>s properly closed.


Need Your Help

How do I give users the ability to swipe through an array of images in swift?

ios cocoa-touch swift uiviewcontroller uigesturerecognizer

I'm currently using a combination of UISwipeGestureRecognisers and an array of images but this isn't giving me the desired effect. When I swipe the image just appears but what I'm aiming for is a

offsetWidth not same in different Browsers

javascript html css

I have a difference of 1px between FF and Chrome in element.offsetWidth.

About UNIX Resources Network

Original, collect and organize Developers related documents, information and materials, contains jQuery, Html, CSS, MySQL, .NET, ASP.NET, SQL, objective-c, iPhone, Ruby on Rails, C, SQL Server, Ruby, Arrays, Regex, ASP.NET MVC, WPF, XML, Ajax, DataBase, and so on.