I thought that this would be rather straightforward, but I think the keywords are just too general so I keep getting query results for things like this and this.
Basical
Heh . . . looks like Oscar Jara and I came up with similar ideas around using the JQuery .contents()
method, but ended up with some considerably different implementations:
$(document).ready(function () {
$("#testDiv").contents().each(function() {
var prevNode = this.previousSibling;
var fillerText = "";
while ((prevNode) && ($.trim($(prevNode).text()) === "")) {
fillerText += prevNode.nodeValue;
prevNode = prevNode.previousSibling;
}
if ((prevNode) && (this.nodeType === 1) && (prevNode.nodeType === 1)) {
$(prevNode).text($(prevNode).text() + fillerText + $(this).text());
$(this).remove();
}
});
});
I tested a few different sets of HTML data (three spans back-to-back, spans with spaces in between and without, etc.) all based on your original code, and it seems to work . . . the key was to skip over any "whitespace only" text nodes in between the <span>
tags, while preserving any needed spacing that they may have contained.
Well, you can try this...
At least it works perfect when using 2 spans
to merge them like your example (when an "empty" element is present). Otherwise, you will need to think a little to handle the span
that lasts.
(To check what I am talking about just take a look commenting the last line: nextElem.remove()
and check the new div
html).
Live Demo: http://jsfiddle.net/oscarj24/t45MR/
HTML:
<div id="test">
Lorem
<span class="highlighted">ipsum</span>
dolor sit amet,
<span class="highlighted">consectetur</span>
<span class="highlighted">adipiscing</span>
elit. Sed massa.
</div>
jQuery:
$(document).ready(function () {
var elem = $('#test');
elem.contents().filter(function(index) {
//Get index of an empty element
if($.trim($(this).text()) === '')
//Merge the previous index span with the next index span texts
mergeSpan(index);
});
//Print new inner html
alert(elem.html());
});
function mergeSpan(index){
//Get all 'div' elements
var elems = $('#test').contents();
//Get previous and next element according to index
var prevElem = elems.eq(index - 1);
var nextElem = elems.eq(index + 1);
//Concat both texts
var concatText = prevElem.text() + ' ' + nextElem.text();
//Set the new text in the first span
prevElem.text(concatText);
//Remove other span that lasts
nextElem.remove();
};
Result:
<div id="test">
Lorem
<span class="highlighted">ipsum</span>
dolor sit amet,
<span class="highlighted">consectetur adipiscing</span>
elit. Sed massa.
<div>
I know you have already accepted a solution, but I wanted to take the challenge to provide a pure javascript solution which can be incorporated into your toolset. Here's what I came up with, and would like any help to make this better.
http://jsfiddle.net/ryanwheale/JhZPK/
function joinNeighborsByClassName( className ) {
var items = document.getElementsByClassName(className),
next = null,
remove = [],
append = '',
i = 0;
while( i < items.length && (next = items[i++]) ) {
while( (next = next.nextSibling) && next !== null ) {
if((next.nodeType === 3 && /^\s+$/.test(next.nodeValue)) ||
(new RegExp("(?:^|\s)" + className + "(?!\S)", "g")).test(next.className) ) {
append += (next.innerHTML || next.nodeValue);
if(next.nodeType !== 3) {
remove.push(next);
}
} else {
break;
}
}
if(append) items[i-1].innerHTML += append;
for(var n = 0; n < remove.length; n++) {
remove[n].parentNode.removeChild(remove[n]);
}
remove = [];
append = '';
}
}
joinNeighborsByClassName('highlighted');
For your last question "Given two tags, how can i find the text between them?"
Well, I have this solution for you.
var divData = $("#test").html(); // Getting html code inside div
Now, using preg_match() you can obtain the text between two words, in your case the text between spans, like this:
preg_match('/'.preg_quote($word1).'(.*?)'.preg_quote($word2).'/is', $html, $matches);
$word1 = '<span class="highlighted">';
$word2 = '<';
$html = $_POST['divData']; // Via post/get you will have to send the html code gotten in "var divData"
and for each match(with a for cycle) concat em in a variable adding whitespaces between them. Then do an echo your result and in your call back function add it to your div
This link could help you in how make a POST call in jquery jquery post
Dropping down to the DOM lets you see text node contents when checking siblings.
Something like:
function combineSpans(span, nextspan)
{
var follower = span.nextSibling;
var concat = true;
while (follower != nextspan)
{
if (follower.nodeName != '#text')
{
concat = false;
break;
}
var len = follower.data.trim().length;
if (len > 0)
{
concat = false;
break;
}
follower = follower.nextSibling;
}
if (concat)
{
$(span).text($(span).text() + " " + $(follower).text());
$(follower).remove();
}
}
Using this with your HTML in this CodePen.
As the title request it, here's a possible way to get text nodes between spans:
var textNodes=$('#test').contents().filter(function(){
return this.nodeType == 3; // text node
});
It is also possible to manually check for consecutive spans that have no empty text node between them by comparing each node with the precedent one. Something like this will do the trick:
function combineSpansIn(selector, spanClass) {
// initialize precedent values
var prec=null;
var precNodeType;
$(selector).contents().each(function(){
if ($.trim( $(this).text() ) !== "") { // empty nodes will be useless here
var nodeType = this.nodeType;
// check if still a combinable span
if (nodeType == 1 && this.className==spanClass && nodeType == precNodeType) {
// append current node to precedent one
$(prec).append(" "+ $(this).text() );
// remove current node
$(this).remove();
} else {
// update precedent values
prec=this;
precNodeType = nodeType;
}
}
});
}
combineSpansIn('#test', 'highlighted');
Please take a look at this FIDDLE.