23

I am trying to achieve a loading effect for a button, as demonstrated in codepen.

I am using bootstrap 4 (beta 2) Jquery 3.2.1.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Group View</title>
    <link rel="stylesheet" media="all" href="../css/bootstrap.css" >
    <script src="../js/jquery.js"></script>
    <script src="../js/bootstrap-bundle.js"></script>
</head>
<body>

  <div>
    <button type="button" class="btn btn-primary btn-lg">Submit Order</button>
  </div>

  <script>
    $(document).ready(function() {
      $('button').data('loading-text', 'loading...');
      $('.btn').on('click', function() {
        var $this = $(this);
        $this.button('loading');
        setTimeout(function() {
          $this.button('reset');
        }, 8000);
      });
    })
  </script>
</body>
</html>

The above code does not display "loading..." when the button is clicked.

Sébastien
  • 11,860
  • 11
  • 58
  • 78
Adarsh Madrecha
  • 6,364
  • 11
  • 69
  • 117

4 Answers4

51

I'm not sure the .button() method in Bootstrap v4 has the options you are trying to use. The Codepen you link to uses Bootstrap v3.

Here is how you could replicate the same behavior with Bootstrap 4.

$(document).ready(function() {
  $('.btn').on('click', function() {
    var $this = $(this);
    var loadingText = '<i class="fa fa-circle-o-notch fa-spin"></i> loading...';
    if ($(this).html() !== loadingText) {
      $this.data('original-text', $(this).html());
      $this.html(loadingText);
    }
    setTimeout(function() {
      $this.html($this.data('original-text'));
    }, 2000);
  });
})
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.3/css/bootstrap.min.css" integrity="sha384-Zug+QiDoJOrZ5t4lssLdxGhVrurbmBWopoEl+M6BdEfwnCJZtKxi1KgxUyJq13dy" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.3/js/bootstrap.min.js" integrity="sha384-a5N7Y/aK3qNeh15eJKGWxsqtnX/wWdSZSKp+81YjTmS15nvnvxKHuzaWwXHDli+4" crossorigin="anonymous"></script>
<div>
  <button type="button" class="btn btn-primary btn-lg">Submit Order</button>
</div>
Sébastien
  • 11,860
  • 11
  • 58
  • 78
  • Can you *please* also add `font awesome 5` to your code – Adarsh Madrecha Jan 13 '18 at 13:10
  • Done. All you have to do is include the stylesheet and add the desired icon to the loading text. – Sébastien Jan 13 '18 at 13:17
  • 3
    This doesn't disable the button from being clicked again while in the loading state. I realize the question didn't specify that behavior. – crush Mar 08 '18 at 17:07
  • 1
    You should put any action you want to execute on click in the `if ($(this).html() !== loadingText) {` condition. Of course we could also use `off()` and re-enable the handler later if we really want to deactivate the click itself. I could add a second example, if you're interested let me know. – Sébastien Mar 08 '18 at 18:43
  • This was not suitable for my needs since a timed state change leaved the button disabled take too long or enables too quickly (eg: AJAX requests, where I tend to use `button('loading')`). Drop-in replacement provided in my answer. works using the same markup as BS3. – Mavelo Oct 26 '18 at 13:01
  • to also disable the button add $this.prop("disabled", true); – GorvGoyl Dec 12 '19 at 19:02
12

This feature is deprecated since v3.3.5 and has been removed in v4. Source

Pang
  • 9,564
  • 146
  • 81
  • 122
user3456070
  • 121
  • 1
  • 4
  • 3
    This is also mentioned in the "Migration to v4" article. https://getbootstrap.com/docs/4.1/migration/#buttons – Bassem Dec 07 '18 at 20:50
10

Ran into this same issue a few days ago and nobody seems to have a solution. So here's my jQuery plugin that seems to provide the same behavior in Boostrap 4.

(function($) {
    $.fn.__bs4_btn = $.fn.button;
    $.fn.button = function(action) {
        if (action === 'loading' && this.data('loading-text')) {
            this.data('original-text', this.html()).html(this.data('loading-text')).prop('disabled', true);
        }
        if (action === 'reset' && this.data('original-text')) {
            this.html(this.data('original-text')).prop('disabled', false);
        }
        return this.__bs4_btn(action);
    };
}(jQuery));

More details: How to Continue Using Buttons with “data-loading-text” in Bootstrap 4 with jQuery (my blog)

Mujnoi Gyula Tamas
  • 1,759
  • 1
  • 12
  • 13
Mavelo
  • 1,199
  • 11
  • 16
  • This seems to be broken in Safari + Chorme. Works in IE11 and Firefox - but on Chrome it doesnt submit the form. – Laurence Nov 26 '18 at 09:16
  • Hard to diagnose your issue without a code sample. This was developed using Chrome so... – Mavelo Nov 26 '18 at 20:02
  • 1
    This will break the built in functions `$().button('toggle')` and `$().button('dispose')`. It is better to use a different plugin name like `$.fn.buttonState`. – Bassem Dec 07 '18 at 21:03
  • I don't use those methods so they were not tested (would not imagine a "loading" button needs them). Good point if that is the case – Mavelo Dec 08 '18 at 20:55
  • 1
    This could be a good solution and it was something that I was tempted to try. However I agree with Bassem, this does override the other button methods documented here: https://getbootstrap.com/docs/4.2/components/buttons/#methods. It is not related to "loading" buttons but if someone uses your code, it would break the `toggle` methods if they used that method somewhere else. I think your answer would be a lot better if you could check the `action` argument and call the built-in `button` function if it was not `loading` or `reset`. – Cave Johnson Jan 12 '19 at 19:53
  • @Bassem I know it's late, but I have suggested a fix for this. Keeping all of the bootstrap predefined functionality while extending it with the loading feature, which op provided. Basically you duplicate the function and then call the function you duplicated after the two if statements. – Mujnoi Gyula Tamas Jun 03 '20 at 09:58
2

I've run into the same problem and tried the example uploaded by Mavelo above however it didn't work on the queries like this:

$('button').button('reset')

which operates on multiple objects. Here's a modified code that supports both single and multiple button queries:

(function($) {
    $.fn.button = function(action) {
        if (this.length > 0) {
            this.each(function(){
                if (action === 'loading' && $(this).data('loading-text')) {
                    $(this).data('original-text', $(this).html()).html($(this).data('loading-text')).prop('disabled', true);
                } else if (action === 'reset' && $(this).data('original-text')) {
                    $(this).html($(this).data('original-text')).prop('disabled', false);
                }
            });
        }
    };
}(jQuery));
ndrwnaguib
  • 5,623
  • 3
  • 28
  • 51