2

I've got a hard time understanding why one version of my code is working, while the other one isn't.

Background info:
I've got some text strings that hold the following character in it, to separate one part of the text from another: |

My goal is to wrap the text following this character in a <span> which is also working with the code beneath, using jQuery .each(). However, as I have single elements (the original element can be on each page up to 40 times) that also contains this text, and needs the same process, I wanted to target these "individually" (not looping through all of them). However, when I target the elements individually the entire text of the element is deleted rather than being wrapped / replaced. You can see the code that is not working a little further down, and in the bottom an example.

I've got the following code which is working as intended/imagined:

// This part works fine 

$('.aa').each(function() {
$(this).html($(this).text().replace('| ', '<span>')).append('</span>');
});

This part is not working as imagined:

// Not working as imagined

$(".bb").html($(this).text().replace('| ', '<span>')).append('</span>');

Here's a live example:

// This part works fine 
$('.aa').each(function() {
  $(this).html($(this).text().replace('| ', '<span>')).append('</span>');
});


// This part does not work as imagined?
setTimeout(function(){
// ---------------
$(".bb").html($(this).text().replace('| ', '<span>')).append('</span>');
// ---------------
},3000)
span{
  color:red;
  display:block;
}
.aa{
  margin-bottom: 30px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="aa">
Hi | There</div>

<div class="bb">
a | b
</div>

Question:
Why doesn't the last code work as I would have imagined, and rather than replacing the text it completely deletes it all?
And on another note, is there a better way of wrapping the remaining text than the one I'm using?



Additional question
Following up on the splendid answers given, I've got another question:
Since it's not possible to use the $(this) selector like this due to scope issues, how would one then target multiple elements such as this:

$(".bb, .cc").html($(this).text().replace('| ', '<span>')).append('</span>');

Note that I'm aware that the above piece is incorrect - but the $(this) part is just to underline my question as to how you should target those different elements. Is it optimal to use the .each() function for this purpose?

For sake of answering other people's problem regarding this matter: The answer for the additional question is "yes" to the use of .each() based on the comment of @mhodges in the answer from @woodrow

Chri.s
  • 1,386
  • 1
  • 12
  • 23
  • 2
    `this` context refers to the global object (likely `window`) in 2nd example. It refers to the current jQuery element in the `.each()` – mhodges Jul 24 '17 at 21:34
  • @mhodges man i would've never catched that one :O So, replacing `$(this)` with `$(".bb")` actually solves it and works as I would have imagined. Could you possibly explain why? – Chri.s Jul 24 '17 at 21:36
  • 1
    try `var bb = $(".bb"); bb.html(bb.text.replace(...)).append(...);` instead – mhodges Jul 24 '17 at 21:37
  • Your functions aren't equivalent. In the first, `this` is in the same scope both times. In the second, you've replaced one `this` with $(".bb"), presumably because you realize doing $(this).innerHTML would refer to something generic - the window. But this applies to the second one as well. In the second example, the functions are *chained*, not nested. In the first, they are nested, not chained. – Bricky Jul 24 '17 at 21:38
  • Kind of a lengthy explanation if you are unfamiliar with the concept of context. Basically, the `this` keyword refers to different things depending on the calling context in which it is used. I can point you to some references if you'd like to get a deeper understanding – mhodges Jul 24 '17 at 21:39
  • That would be neat @mhodges - although you shouldn't be wasting your time on it if you have to find the references first, as I could do that my self, now that I know what i'm looking for :-) – Chri.s Jul 24 '17 at 21:40
  • 1
    @Chri.s Here's a link to a [stack answer](https://stackoverflow.com/a/40556348/4987197) I posted a while back. Also, check out Kyle Simpson's [YDKJS](https://github.com/getify/You-Dont-Know-JS/blob/master/this%20&%20object%20prototypes/README.md#you-dont-know-js-this--object-prototypes) book on `this` and object prototypes. – mhodges Jul 24 '17 at 21:44

1 Answers1

1

Please see below. The "$(this)" inside of your setTimeout is referring to the scope within the setTimeout function, which could be "window", which isn't valid, whereas "$(this)" inside of the each statement for "aa" refers to each "aa" element in the loop. You need to reference "bb" as the class inside the setTimeout function..

// This part works fine 
$('.aa').each(function() {
  $(this).html($(this).text().replace('| ', '<span>')).append('</span>');
});


// This part does not work as imagined?
setTimeout(function() {
  // ---------------
  $(".bb").html($(".bb").text().replace('| ', '<span>')).append('</span>');
  // ---------------
}, 3000)
span {
  color: red;
  display: block;
}

.aa {
  margin-bottom: 30px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="aa">
  Hi | There</div>

<div class="bb">
  a | b
</div>
Woodrow
  • 2,740
  • 1
  • 14
  • 18
  • It's definitely working - but removing the `setTimeout` function doesn't change it's behaviour, so why doesn't it target the `.bb` class? Just looking for some clarification – Chri.s Jul 24 '17 at 21:39
  • 1
    Because "this" is referring to the scope of the window, not the actual bb element. Please see this for more on js scope: https://toddmotto.com/everything-you-wanted-to-know-about-javascript-scope/ – Woodrow Jul 24 '17 at 21:44
  • Scoping in JS is done by functions. Your first example, `$(this).html($(this).text().replace('| ', '')).append('');` is nested inside the function `$('.aa').each()` So `this` is handled by `$.each` which is likely explicitly setting it to be the specific element of that iteration. In your second example, `$(".bb").html($(".bb").text().replace('| ', '')).append('');` is nested within `setTimeout` which isn't equipped to handle `this` – Bricky Jul 24 '17 at 21:45
  • Ah! Thank you. But then how would one go about it, if you had to target multiple elements such as this: `$(".bb, .cc").html($(this).text().replace('| ', '')).append('');`. I know that the code isn't correct, but is it even possible to do it like this, when you can't use the `$(this)` target? I will update my question with this as well, in case you feel like answering it :-) – Chri.s Jul 24 '17 at 21:47
  • 1
    @Chri.s That's when you would use `.each()`. And in that case, jQuery internally sets the `this` context to the current DOM element for that iteration of the loop. This is not default JS behavior - that's a jQuery thing. Normally, the `this` context would be set to the owning/containing object that `.forEach()` was called on (as per my stack answer I listed in the comments on the original question) – mhodges Jul 24 '17 at 21:52
  • 1
    @mhodges thank you very much for the clarification and help - it's perfect and I appreciate it! – Chri.s Jul 24 '17 at 21:53