2

We're writing a web app that relies on Javascript/jQuery. It involves users filling out individual words in a large block of text, kind of like Mad Libs. We've created a sort of HTML format that we use to write the large block of text, which we then manipulate with jQuery as the user fills it out.

Part of a block of text might look like this:

<span class="fillmeout">This is a test of the <span>NOUN</span> Broadcast System.</span>

Given that markup, I need to separately retrieve and manipulate the text before and after the inner <span>; we're calling those the "prefix" and "suffix".

I know that you can't parse HTML with simple string manipulation, but I tried anyway; I tried using split() on the <span> and </span> tags. It seemed simple enough. Unfortunately, Internet Explorer casts all HTML tags to uppercase, so that technique fails. I could write a special case, but the error has taught me to do this the right way.

I know I could simply use extra HTML tags to manually denote the prefix and suffix, but that seems ugly and redundant; I'd like to keep our markup format as lean and readable and writable as possible.

I've looked through the jQuery docs, and can't find a function that does exactly what I need. There are all sorts of functions to add stuff before and after and around and inside elements, but none that I can find to retrieve what's already there. I could remove the inner <span>, but then I don't know how I can tell what came before the deleted element apart from what came after it.

Is there a "right" way to do what I'm trying to do?

Community
  • 1
  • 1
75th Trombone
  • 1,364
  • 15
  • 28

9 Answers9

1

With simple string manipulations you can also use Regex. That should solve your problem.

var array = $('.fillmeout').html().split(/<\/?span>/i);
iMoses
  • 4,338
  • 1
  • 24
  • 39
1

Use your jQuery API! $('.fillmeout').children() and then you can manipulate that element as required.

http://api.jquery.com/children/

Mike Samuel
  • 118,113
  • 30
  • 216
  • 245
1

For completeness, I thought I should point out that the cleanest answer is to put the prefix and suffix text in it's own <span> like this and then you can use jQuery selectors and methods to directly access the desired text:

<span class="fillmeout">
    <span class="prefix">This is a test of the </span>
    <span>NOUN</span>
    <span class="suffix"> Broadcast System.</span>
</span>

Then, the code would be as simple as:

var fillme = $(".fillmeout").eq(0);
var prefix = fillme.find(".prefix").text();
var suffix = fillme.find(".suffix").text();

FYI, I would not call this level of simplicity "ugly and redundant" as you theorized. You're using HTML markup to delineate the text into separate elements that you want to separately access. That's just smart, not redundant.

By way of analogy, imagine you have toys of three separate colors (red, white and blue) and they are initially organized by color and you know that sometime in the future you are going to need to have them separated by color again. You also have three boxes to store them in. You can either put them all in one box now and manually sort them out by color again later or you can just take the already separated colors and put them each into their own box so there's no separation work to do later. Which is easier? Which is smarter?

HTML elements are like the boxes. They are containers for your text. If you want the text separated out in the future, you might as well put each piece of text into it's own named container so it's easy to access just that piece of text in the future.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
1

Several of these answers almost got me what I needed, but in the end I found a function not mentioned here: .contents(). It returns an array of all child nodes, including text nodes, that I can then iterate over (recursively if needed) to find what I need.

75th Trombone
  • 1,364
  • 15
  • 28
  • Why is `.contents()` better than the other answers (like mine) which work, also iterate through the native DOM nodes and you didn't even bother to give an upvote to? While iterating through `.contents()`, you're going to need almost all the same logic of checking node type and tagName as I've included in my answer. – jfriend00 Apr 02 '12 at 15:19
  • I guess I'm just feeling like I gave you a tested, working answer with a jsFiddle with a lot of the detail you will need even with `.contents()` and you didn't even acknowledge it. Why? Instead, you post your own answer that needs almost all the same logic as the answers already provided. From SO's point of view, your answer (with no code in it) does not help future readers more than the answers that contain working code either. – jfriend00 Apr 02 '12 at 15:23
  • @jfriend00: pointing out `.contents()` did help me as I was unaware of that function and it does provide exactly what was asked for in the question. Your answer, while interesting, requires a modification of the `HTML` code which is not possible when parsing `HTML` code you don't generate yourself. – Max Aug 18 '12 at 10:02
0

I'm not sure if this is the 'right' way either, but you could replace the SPANs with an element you could consistently split the string on:

jQuery('.fillmeout span').replaceWith('|');

http://api.jquery.com/replaceWith/

http://jsfiddle.net/mdarnell/P24se/

Matthew Darnell
  • 4,538
  • 2
  • 19
  • 29
0

You could use

$('.fillmeout span').get(0).previousSibling.textContent
$('.fillmeout span').get(0).nextSibling.textContent

This works in IE9, but sadly not in IE versions smaller than 9.

YMMD
  • 3,730
  • 2
  • 32
  • 43
0

Based on your example, you could use your target as a delimiter to split the sentence.

var str = $('.fillmeout').html();
str = str.split('<span>NOUN</span>');

This would return an array of ["This is a test of the ", " Broadcast System."]. Here's a jsFiddle example.

j08691
  • 204,283
  • 31
  • 260
  • 272
0

If you want to use the DOM instead of parsing the HTML yourself and you can't put the desired text in it's own elements, then you will need to look through the DOM for text nodes and find the text nodes before and after the span tag.

jQuery isn't a whole lot of help when dealing with text nodes instead of element nodes so the work is mostly done in plain javascript like this:

$(".fillmeout").each(function() {
    var node = this.firstChild, prefix = "", suffix = "", foundSpan = false;
    while (node) {
        if (node.nodeType == 3) {
            // if text node
            if (!foundSpan) {
                prefix += node.nodeValue;
            } else  {
                suffix += node.nodeValue;
            }
        } else if (node.nodeType == 1 && node.tagName == "SPAN") {
            // if element and span tag
            foundSpan = true;
        }
        node = node.nextSibling;
    }
    // here prefix and suffix are the text before and after the first
    // <span> tag in the HTML
    // You can do with them what you want here
});

Note: This code does not assume that all text before the span is located in one text node and one text node only. It might be, but it also might not be so it collates all the text nodes together that are before and after the span tag. The code would be simpler if you could just reference one text node on each side, but it isn't 100% certain that that is a safe assumption.

This code also handles the case where there is no text before or after the span.

You can see it work here: http://jsfiddle.net/jfriend00/P9YQ6/

jfriend00
  • 683,504
  • 96
  • 985
  • 979
0

You could just use the nextSibling and previousSibling native JavaScript (coupled with jQuery selectors):

$('.fillmeout span').each(
    function(){
        var prefix = this.previousSibling.nodeValue,
            suffix = this.nextSibling.nodeValue;
    });

JS Fiddle proof of concept.

References:

David Thomas
  • 249,100
  • 51
  • 377
  • 410
  • This assumes that all text before the span is in one text node and likewise that all text after the span is in one text node. Might be a valid assumption in some cases, but isn't necessarily guaranteed. – jfriend00 Mar 28 '12 at 16:25
  • This is true, yes. But given the sample mark-up it seemed a reasonable assumption. – David Thomas Mar 28 '12 at 16:26