1

I'm using font-awesome SVG, I had some issues with scope earlier, but now I am having a new issue - I asked about this issue in a comment on my other question and was told it was an entirely separate issue.

Here is my current code:

$('body').on('click','.switchButton', function(){
    $(this).attr('data-prefix', 'fas').attr('data-icon', 'spinner').addClass('fa-pulse');
    $.ajax({
        url: 'tasks/update_table.php',
        type: 'post',
        data: { "uid": $(this).attr('data-uid')},
        context: this,
        success: function(response) { 
            $(this).attr('data-icon', 'check-square').removeClass("fa-pulse");
            if(response == 1) {
                $(this).attr('data-prefix', 'far');
            } else {
                $(this).attr('data-prefix', 'fas');
            }
            console.log(this);
        }
    });
});

(Note: even the removeClass() above does not work, it seems like the element cannot be altered from inside the success function?)

Now, when I do console.log(this), I get some results that seem weird to me.

The first time I click on the icon, console.log() I get this result:

<svg class="svg-inline--fa fa-square fa-w-14 fa-lg switchButton fa-pulse" data-uid="1" aria-hidden="true" data-prefix="fas" data-icon="check-square" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" data-fa-i2svg="">

And every time after that, I always get this result:

<svg class="svg-inline--fa fa-spinner fa-w-16 fa-lg switchButton fa-pulse" data-uid="1" aria-hidden="true" data-prefix="far" data-icon="check-square" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" data-fa-i2svg="">

And the icon is never changed on the actual page, it just stays as the spinner. I don't really understand what is going wrong here. This seems like it should be such a simple thing to do but is not working for me.

The element is initially loaded on the page as:

<i data-uid="<?=$task['uid'];?>" class="far fa-square fa-lg switchButton"></i>

and I am using font-awesome CDN version v5.0.8.

I've tried several things, such as editing the attributes directly as you see here which I found is the correct way to do it from this StackOverflow question, and I also tried editing the actual class of the SVG object ( $(this) in context ), but that also had no effect.

When I inspect element on the object, chrome tells me the code is activated because the element tag sort of "flashes", but none of the data is actually changed.

What is going wrong?

GrumpyCrouton
  • 8,486
  • 7
  • 32
  • 71
  • Your spinner never goes away because you never tell it to. At least in the code you provided. If you console.log() your response, is it == 1 on the first click? – Ryan Gibbs Mar 13 '18 at 17:44
  • @RyanGibbs What do you mean I never tell it to? In the success function it either changes to an empty box icon or a checked box icon. The spinner is only activated directly before the ajax call, the 2 attribute codes inside the success function should alter it back to a regular state. As for it == 1, it depends. Every other click rotates the return value, because it is a boolean. So first click may == 0, 2nd would == 1, then 0 etc. – GrumpyCrouton Mar 13 '18 at 17:50
  • @RyanGibbs The problem is that nothing inside the success function is actually altering anything on the page for some reason - that is what I am trying to figure out. – GrumpyCrouton Mar 13 '18 at 17:53
  • Sorry, ignore that. I misunderstood what you were doing with the data-icon property. – Ryan Gibbs Mar 13 '18 at 17:55
  • @RyanGibbs It's all good :) – GrumpyCrouton Mar 13 '18 at 17:57
  • @RyanGibbs Update makes it a bit easier to understand – GrumpyCrouton Mar 13 '18 at 17:58
  • So I've been reading up on this and I think it has to do with how it replaces the with the . The documentation notes you have to use .toggleClass() to remove the old class and add the new each time. I've not been able to get that to work within a jsfiddle, though. – Ryan Gibbs Mar 13 '18 at 18:40
  • @RyanGibbs If that's true, then why does the initial change to the loading icon work just fine? – GrumpyCrouton Mar 13 '18 at 18:45
  • Okay, I've figured it out. Posting an answer for you. – Ryan Gibbs Mar 13 '18 at 19:13

2 Answers2

1

See Update Below...

Original answer:

This behavior comes from a combination of the use of the 'this' keyword and the way that the animated font-awesome icons are handled. As we discussed in the comments, it replaces the tag with an one, however, in reading through their documentation I saw that it does so with each change, it's very dynamic.

The problem is that when you pass the 'this' keyword into your ajax context, you're locking in that instance of the svg control, but it is replaced after that. So when you return 'this' you see that the class has changed successfully on the old control, but the new control remains having the spinner.

The solution to this is to use "$('[data-fa-i2svg]')" instead of "$('this')" in your success callback function. That targets the current control.

I found this solution here:

https://fontawesome.com/how-to-use/svg-with-js

It states "If you absolutely need to attach events to the icon you can use the data-fa-i2svg attribute but you need to allow for the dynamic creation of the svg element."

Update:

Using the "$('[data-fa-i2svg]')" selector doesn't work if you have multiple icons on the same page, as it will update them all.

You can set a font awesome property that will nest the svg inside the original and then use a selector to get the child of that tag. This works, however I think GrumpyCrouton's adaptation of just using the $('#taskid-'+uidOfTask); is probably more elegant in this situation.

Since this is marked as the solution I'm including his code below, but see his answer for further detail.

success: function(response) { 
    var element = $('#taskid-'+uidOfTask);
    element.attr('data-icon', 'check-square').removeClass("fa-pulse");
    if(response == 1) {
        element.attr('data-prefix', 'far');
    } else {
        element.attr('data-prefix', 'fas');
    }
} 
Ryan Gibbs
  • 1,292
  • 1
  • 15
  • 27
  • How will this work if the page contains multiple icons that all work the same? – GrumpyCrouton Mar 13 '18 at 19:21
  • At the very bottom of their documentation they say you can switch to a nested svg. "FontAwesomeConfig = { autoReplaceSvg: 'nest' }". Then you could use a selector that targets the and changed the child of that . – Ryan Gibbs Mar 13 '18 at 19:26
  • Thank you for taking the time to look at and answer my question, I really appreciate it. – GrumpyCrouton Mar 13 '18 at 19:29
0

Ryan Gibbs answer is spot on, but his solution is not exactly what I needed.

Ryan stated that:

This behavior comes from a combination of the use of the 'this' keyword and the way that the animated font-awesome icons are handled.

and:

The solution to this is to use "$('[data-fa-i2svg]')" instead of "$('this')" in your success callback function. That targets the current control.

However, this changed ALL of the icons on the page. This seems like very weird default behavior to me, but regardless my solution was to just assign an ID to every icon related to this onclick event.

I used my already unique uid from my database to assign an id called taskid-#, this should work no matter how many are on the page because the ID is always unique.

Then, in my success callback, instead of using $(this), I just called the element directly by its ID which seems to have worked.

success: function(response) { 
    var element = $('#taskid-'+uidOfTask);
    element.attr('data-icon', 'check-square').removeClass("fa-pulse");
    if(response == 1) {
        element.attr('data-prefix', 'far');
    } else {
        element.attr('data-prefix', 'fas');
    }
}
GrumpyCrouton
  • 8,486
  • 7
  • 32
  • 71