4

My app has a swipe to display hidden action on a list where users can decide to delete items in the list.

It uses HammerJS to handle swipe events and SweetAlert2 to confirm the action.

The issue is when the SweetAlert popup is dismissed. Once the user clicks on cancel and the modal close, all the buttons are suddenly visible. The whole document move to the left.

I created a JSFiddle that reproduce it.

Steps to reproduce:

  • Swipe right-to-left on one of the items to display the action;
  • Click on the "x" to delete the item;
  • Click Cancel.

I will also paste the content of the code below for reference:

HTML:

<div ng-app="app" ng-controller="mainCtrl" class="wrapper">
  <div class="items" swipe>
    <div ng-repeat="item in items" class="item">
       <div>
         <div class="item-wrapper">
           <div class="logo">
             <img src="http://2.bp.blogspot.com/-Y2XrnrXJmXs/Uf5Y_bfr4jI/AAAAAAAAALk/ydouC9lEmDE/s1600/Logogap+Logobb.jpg" />
           </div>
           <div class="info">
             <div class="title">
               {{item.title}}
             </div>
             <div class="description">
               {{item.description}}
             </div>
           </div>
         </div>
         <div class="offset-action">
           <button ng-click="delete()">
             X
           </button>
         </div>
       </div>
    </div>
  </div>
</div>

CSS:

body {
  overflow: hidden;
}

.wrapper {
  border: 1px solid grey;
  min-width: 350px;
  max-width: 800px;
}

.items {
  overflow: hidden;
  position: relative;
}

.item {
  padding: 10px 15px;
  position: relative;
  transition: transform 250ms ease-in-out;
}

.item.show-actions {
  transform: translateX(-70px);
}

.item-wrapper {
  align-items: center;
  display: flex;
}

.logo {
  width: 80px;
}

.logo img {
  margin: auto;
  width: 80px;
  height: auto;
}

.offset-action {
  align-items: center;
  background: #B11A1F;
  display: flex;
  position: absolute;
  top: 0;
  bottom: 0;
  text-align: center;
  right: -70px;
  width: 70px;
}

button {
  background: transparent;
  border: none;
  color: white;
  width: 100%;
  height: 100%;
}

Javascript:

angular.module('app', [])

.controller('mainCtrl', function($scope) {
    $scope.items = [
    {title: 'Lorem Ipsum', description: 'Consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore.'},
    {title: 'Lorem Ipsum', description: 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.'},
    {title: 'Lorem Ipsum', description: 'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.'},
    {title: 'Lorem Ipsum', description: 'Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'}
  ];

  $scope.delete = function() {
    swal({
        title: 'Lorem Ipsum',
      text: 'Dolor sit amet?',
      type: 'error',
      showCancelButton: true
    }).then(function(action) {
        if (action.value) {
        console.log('success');
      } else {
        swal.noop;
      }
    })
  }
})  

.directive('swipe', function() {
    return function(scope) {
    scope.$watch('items', function() {
      var $items = $('.item');
      var show_action = 'show-actions';

      function hideActions() {
        $items.removeClass(show_action);
      }

      $items.each(function() {
        var $item = $(this);

        $item.hammer().on('swipeleft', function() {
          var $this = $(this);
          $items.removeClass(show_action);
          if (!$this.hasClass(show_action)) {
            $this.addClass(show_action);
          }
        });

        $item.hammer().on('tap', hideActions);
      });
    });
  };
});
Paolo Forgia
  • 6,572
  • 8
  • 46
  • 58
Ayeye Brazo
  • 3,316
  • 7
  • 34
  • 67
  • I’m not seeing that problem testing the fiddle on Safari/iPad. – James Aug 26 '18 at 11:46
  • I see it on almost all my devices, iPhone 5, 6s and 8. On all my Android devices. And all web browsers... It is supposed to work on all of them... – Ayeye Brazo Aug 26 '18 at 12:43
  • Mixing flex with absolute position will change the behavior of flex . Please refer to this answer: https://stackoverflow.com/a/41033582/4700922 – Ali Sheikhpour Aug 26 '18 at 14:37

4 Answers4

3

There is a conflict between SweetAlert CSS styles and your CSS styles causing this problem.

A simple fix would be adding this CSS to the end of your CSS file:

.offset-action {
  display: none;
}

.show-actions .offset-action {
  display: flex;
}

https://jsfiddle.net/saeedahmadi/1L7zc25b/

Also Razvan's answer will do the job by getting rid of focus after closing the modal:

https://stackoverflow.com/a/52028143/5939933

"adding document.activeElement.blur(); at the top of your delete() method"

Saeed
  • 2,169
  • 2
  • 13
  • 29
  • SweetAlert's CSS styles should only affect its own component. This just hides the element so it can't be focused; now it disappears on click before the animation finishes. – SamVK Aug 26 '18 at 16:46
  • Thanks for your answer, tomorrow I will test and let you know! – Ayeye Brazo Aug 27 '18 at 11:56
  • This was something I already tried and it was good for almost all devices / browsers but Android... – Ayeye Brazo Aug 28 '18 at 10:05
2

Add document.activeElement.blur(); before calling swal().

https://jsfiddle.net/yd3gpsvL/

It looks to be caused by focus returning to the button after the popup closes. There's a discussion about it here.


Alternatively, you can use a div instead of a button (optionally with role="button") to avoid button's focus functionality.

SamVK
  • 3,077
  • 1
  • 14
  • 19
  • There's also an issue where if you click on the button it hides itself and opens the popup, but if you mousedown on it, move the cursor a little, then mouseup, it opens the popup but without hiding itself. I haven't used HammerJS but my guess is that "on click" is triggered for both but "on tap" only triggers if the pointer never shifts. – SamVK Aug 26 '18 at 14:55
  • Thanks for your answer, tomorrow I will test and let you know! – Ayeye Brazo Aug 27 '18 at 11:56
  • 1
    I tested all your suggestions and finally this single row of code did the trick. Thank you all for your help. Very appreciated. – Ayeye Brazo Aug 28 '18 at 10:03
0

Your JavaScript needs to a little brush up and document.activeElement.blur(); at the top of your delete() method:

angular.module('app', [])

.controller('mainCtrl', function($scope) {
    $scope.items = [
    {title: 'Lorem Ipsum', description: 'Consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore.'},
    {title: 'Lorem Ipsum', description: 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.'},
    {title: 'Lorem Ipsum', description: 'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.'},
    {title: 'Lorem Ipsum', description: 'Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'}
  ];

  $scope.delete = function() {
  document.activeElement.blur();
    swal({
        title: 'Lorem Ipsum',
      text: 'Dolor sit amet?',
      type: 'error',
      showCancelButton: true
    }).then(function(action) {
        if (action.value) {
        console.log('success');
      } else {
        swal.noop;
      }
    })
  }
})  

.directive('swipe', function() {
    return function(scope) {
    scope.$watch('items', function() {
      var $items = $('.item');
      var show_action = 'show-actions';

      function showActions() {
          var $this = $(this);
          $items.removeClass(show_action);
          if (!$this.hasClass(show_action)) {
            $this.addClass(show_action);
          }
        }

      function hideActions() {
        $items.removeClass(show_action);
      }

      $items.each(function() {
        var $item = $(this);

        $item.hammer().on('swipeleft', showActions);

        $item.hammer().on('tap', hideActions);
      });
    });
  };
});

Here is an update to your jsFiddle.

Razvan Zamfir
  • 4,209
  • 6
  • 38
  • 252
0

Once the user clicks on cancel and the modal close, all the buttons are suddenly visible...

In order to overcome this point i suggest to remove the class only when the swal closes:

  • remove this line: $item.hammer().on('tap', hideActions);
  • add index parameter to the delete call: ng-click="delete($index)" in your html and $scope.delete = function (idx) { in your js
  • remove class on swal close after a few milliseconds:

    setTimeout(function(){
      $('.item').eq(idx).removeClass('show-actions');
    }, 200);
    

The updated fiddle

UPDATE

Thanks for your answer, unfortunately I need the tap to hide the button

I updated the fiddle removing jQuery and jQuery for Hammer.

In order to hide the buttons on swal close I hide the current button on tap and show again on swipeleft.

angular.module('app', []).controller('mainCtrl', function ($scope) {
    $scope.items = [{title: 'Lorem Ipsum', description: 'Consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore.'},
        {title: 'Lorem Ipsum', description: 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.'},
        {title: 'Lorem Ipsum', description: 'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.'},
        {title: 'Lorem Ipsum', description: 'Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'}];
    $scope.delete = function () {
        swal({
            title: 'Lorem Ipsum',
            text: 'Dolor sit amet?',
            type: 'error',
            showCancelButton: true
        }).then(function (action) {
            if (action.value) {
                console.log('success');
            } else {
                swal.noop;
            }
        })
    }
}).directive('swipe', function () {
    return function (scope) {
        scope.$watch('items', function () {
            document.querySelectorAll('.item').forEach(function (ele, idx) {
                var mc = new Hammer(ele);
                mc.on("swipeleft", function (e) {
                    e.target.closest('.item').classList.add('show-actions');
                    e.target.closest('.item').querySelector('.offset-action').style.display = '';
                }).on("tap", function (e) {
                    e.target.closest('.item').classList.remove('show-actions');
                    e.target.closest('.item').querySelector('.offset-action').style.display = 'none';
                });
            });
        });
    };
});
body {
    overflow: hidden;
}

.wrapper {
    border: 1px solid grey;
    min-width: 350px;
    max-width: 800px;
}

.items {
    overflow: hidden;
    position: relative;
}

.item {
    padding: 10px 15px;
    position: relative;
    transition: transform 250ms ease-in-out;
}

.item.show-actions {
    transform: translateX(-70px);
}

.item-wrapper {
    align-items: center;
    display: flex;
}

.logo {
    width: 80px;
}

.logo img {
    margin: auto;
    width: 80px;
    height: auto;
}

.offset-action {
    align-items: center;
    background: #B11A1F;
    display: flex;
    position: absolute;
    top: 0;
    bottom: 0;
    text-align: center;
    right: -70px;
    width: 70px;
}

button {
    background: transparent;
    border: none;
    color: white;
    width: 100%;
    height: 100%;
}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/limonte-sweetalert2/7.26.11/sweetalert2.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.8/angular.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/hammer.js/2.0.8/hammer.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/limonte-sweetalert2/7.26.11/sweetalert2.all.js"></script>

<div ng-app="app" ng-controller="mainCtrl" class="wrapper">
    <div class="items" swipe>
        <div ng-repeat="item in items" class="item">
            <div>
                <div class="item-wrapper">
                    <div class="logo">
                        <img src="http://2.bp.blogspot.com/-Y2XrnrXJmXs/Uf5Y_bfr4jI/AAAAAAAAALk/ydouC9lEmDE/s1600/Logogap+Logobb.jpg"/>
                    </div>
                    <div class="info">
                        <div class="title">
                            {{item.title}}
                        </div>
                        <div class="description">
                            {{item.description}}
                        </div>
                    </div>
                </div>
                <div class="offset-action">
                    <button ng-click="delete()">
                        X
                    </button>
                </div>
            </div>
        </div>
    </div>
</div>
gaetanoM
  • 41,594
  • 6
  • 42
  • 61