Looping over text with conditionals

I’m trying to grab an element, which has <p> tags in it, <a>, and <span>. I’m trying to loop over each word in this element, and i need to be able to determine the words direct parent HTML element. I need to also be able to check the classes on the direct parent HTML

I’m just unsure of a starting point. Not sure how I can accomplish this. Is there a method I’ve overlooked?

<figcaption>
  <p>
    <span class="purple">KNOW</span> <span class="red">AND</span> <span class="orange">BE</span> KNOWN
  </p>
</figcaption>

One of the goals is to detect all text that does not have an anchor or a span as a direct parent (aka “KNOWN”, in this example) and wrap it with custom HTML.

$.each($(this).find("figcaption, .fsDescription").text().split(' '), function(i, text) {
  console.log(text.parentElement);
  // console.log(text.parentNode.nodeName);
});
1 Like

Hi there RyanReese,

does this help…

HTML:-

<figcaption>
  <p>
    <span class="purple">KNOW</span> 
	<span class="red">AND</span> 
	<span class="orange">BE</span> 
	KNOWN
  </p>
</figcaption>
<figcaption>
  <p>
    <span class="blue">NOT KNOWN</span> 
	<span class="grey">AND</span> 
	<span class="black">BE</span> 
	<span class="green">UNKNOWN</span>
  </p>
</figcaption>

JavaScript:-

(function(d) {

   var fig = d.querySelectorAll( 'figcaption' ), c , k;

   for ( c = 0; c < fig.length; c ++ ) {
      if ( fig[c].querySelector('p') && fig[c].querySelectorAll( 'span' )) {
         for ( k = 0; k < fig[c].querySelectorAll( 'span' ).length; k ++ ) {
             console.log(
			    'figcaption number ' + c+ '\n',
                'class="' + fig[c].querySelectorAll( 'span' )[k].className + '"\n',
                'Text="' + fig[c].querySelectorAll( 'span' )[k].textContent+ '"\n'
               );
           }
       }
 
     }
}( document ));

coothead

Hey coothead, thank you, but I see that “KNOWN” is not logging in your first figcaption example. In my case, I need that to be detected (all words that do not have a span / anchor parent) and wrap that in a <span>. With it not logging in your example, I’m not sure how to modify it to target it :thinking: .

Your example is looking for spans, but I need it to loop over individual words to be safer (unless there’s another way?)

sorry about that, I missed that requirement. :wonky:

I will try again. :winky:

coothead

DOM works best as a tree. Specifically, what you’re trying to do is a Pre-Order Tree Traversal of the root node.
In other words, instead of trying to figure out what the parent of each word is, figure out which parent the group of words belongs to.

1 Like

(Spitballing, untested.)

function dive(root) {
  $(root).contents().each(function(node) {
    if(node.nodeType == 3) { console.log(node+" belongs to "+root.nodeName+" with class "+root.classList; }
    else { dive(node); }
  });
}
dive($('figcaption'));

Or if i actually read the spec properly, you obviously could key off of root.nodeName instead of logging, to wrap your text.

1 Like

I think I’m getting there - you were close. Still struggling, but thought I’d post my current progress…

function dive(root) {
  $(root).contents().each(function(node) {
    if(this.nodeType == 3) {
    // console.log(this);
     console.log(this.wholeText+" belongs to "+this.nodeName+" with class "+this.classList);
    } else {
      dive(this);
    }
  });
}
dive($('figcaption'));

Think I got it!

function dive(root) {
  $(root).contents().each(function(node) {
    if(this.nodeType == 3 && this.wholeText.trim() !== '') {
     console.log(this.wholeText+" belongs to "+this.parentNode.tagName+" with class "+this.parentNode.classList);
    } else {
      dive(this);
    }
  });
}
dive($('figcaption'));

Results in console:

KNOW belongs to SPAN with class purple
AND belongs to SPAN with class red
BE belongs to SPAN with class orange
KNOWN belongs to P with class 
NOT KNOWN belongs to SPAN with class blue
AND belongs to SPAN with class grey
BE belongs to SPAN with class black
UNKNOWN belongs to SPAN with class green
1 Like

Just playing with your script, I hope you don’t mind.

A variation with vanilla JS and a walk the dom function.

(function(doc){

	const walkNodes = function(root, callback) {
		
		for (let i = 0, len = root.length; i < len; i += 1) {
			
			if(root[i].hasChildNodes()) {

				walkNodes(root[i].childNodes, callback)
			}
			callback(root[i], i, root)
		}
	}

	walkNodes( doc.querySelectorAll('figCaption'), function(elem) {
		
		if ( elem.nodeType === 3 && elem.wholeText.trim() !== "" ) {

			let parent = elem.parentNode;
			
			console.log(`${elem.wholeText.trim()} belongs to ${parent.tagName} with class ${parent.classList}`)
		}
	})

}(window.document))
3 Likes

I’ll try that in my project on Monday :slight_smile: .

1 Like

Hey guys, coming back here. I was trying to grab stray words, which this script works great, but if I have something like this

<figcaption>
  <p>This is Text No Style</p>
</figcaption>

It ends up wrapping the whole word in 1 span.

function textDive(el) {
          $(el).contents().each(function() {
            if(this.nodeType === 3 && this.wholeText.trim() !== '') {
              console.log(this.wholeText+" belongs to "+this.parentNode.tagName+" with class "+this.parentNode.classList);
              console.log(this.wholeText.substring(0, 1));
              if(this.parentNode.tagName !== "SPAN" && this.parentNode.tagName !== "A") {
                let spanWrap = '<span class="default">'+this.wholeText.trim()+'</span>';
                if(this.wholeText.substring(0, 1) === " ") {
                  spanWrap = ' ' + spanWrap;
                }
                if(this.wholeText.substring(this.wholeText.length - 1, this.wholeText.length) === " ") {
                  spanWrap = spanWrap + ' ';
                }
                $(this).replaceWith(spanWrap);
              }
            } else {
              textDive(this);
            }
          });
        }
        textDive($(this).find("figcaption, .fsDescription"));

Is there an easy way to fix this? Below is the console result

This is Text No Style belongs to P with class 
T

EDIT- https://codepen.io/ryanreese09/pen/rNVreeN

An easy thing to play with

What are you trying to achieve?

Do you want spans around <span>each</span> <span>word</span>?

Can you show a couple of before and after examples in html?

To start with reading through your code I find difficult. A bit of line spacing etc would definitely help.

Below I have refactored your code in a way I personally think is clearer and less cluttered, but obviously the choice is yours:)

/* 
    Making 'dive' a separate function which takes a function/callback as an argument
    means it can be used multiple times for different tasks.
*/

function dive(root, fn) {

    /* 
        jquery's .each method take a callback function with two arguments (index, element) 
        it also binds 'this' to the element. this === element = true
    */
    
    $(root).contents().each(function(i, elem) {

        /* 
            I have swapped index and element around for the callback as I prefer 
            the element to be a key first argument and the index an optional second.
        */
        
        fn(elem, i) 
        
        if($(elem).contents().length) dive(elem, fn)
    });
}

/* 
    Keeping our 'childOf' function separate I think makes it less cluttered
    and easier to debug. 
*/

function childOf(elem) {

    if(elem.nodeType === 3) {

        let parent = elem.parentNode,
            wholeText = elem.wholeText,
            text = wholeText.trim()
        
        if(text) {

            if(parent.tagName !== 'SPAN' && parent.tagName !== 'A') {
                
                let spanWrap = `<span class='red'>${text}</span>`;

                /* not sure what you are trying to achieve here */

                if(wholeText[0] === ' ') {

                    spanWrap = '*' + spanWrap;
                }

                if(wholeText[wholeText.length - 1] === " ") {

                    spanWrap = spanWrap + '#';
                }

                $(elem).replaceWith(spanWrap)
            }
        }
    }
}

dive($('figcaption'), childOf)

and minus comments

function dive(root, fn) {
    
    $(root).contents().each(function(i, elem) {
        
        fn(elem, i) 
        
        if($(elem).contents().length) dive(elem, fn)
    })
}

function childOf(elem) {

    if(elem.nodeType === 3) {

        let parent = elem.parentNode,
            wholeText = elem.wholeText,
            text = wholeText.trim()
        
        if(text) {

            if(parent.tagName !== "SPAN" && parent.tagName !== "A") {
                
                let spanWrap = `<span class='red'>${text}</span>`;

                if(wholeText[0] === ' ') {

                    spanWrap = ' ' + spanWrap;
                }

                if(wholeText[wholeText.length - 1] === " ") {

                    spanWrap = spanWrap + ' ';
                }

                $(elem).replaceWith(spanWrap)
            }
        }
    }
}

dive($('figcaption'), childOf)

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.