2

I'd like to have a button that calls a different function if it is pressed longer then 3 sec. Following code works fine with mouse events, but fails on mobile devices with touch events:

angular.module('myApp', []).controller('myCtrl', function($scope, $timeout) {

  var mapService = {
    setHome: function() {
      console.log("setHome called");
    },

    goHome: function() {
      console.log("goHome called");
    }
  };
  
  var _homeDownTimeout = null;
  var _homeWasSet = false;

  $scope.homeDown = function() {
    _homeDownTimeout = $timeout(function() {
      mapService.setHome();
      _homeWasSet = true;
    }, 3000);
  };

  $scope.homeUp = function() {
    if (_homeDownTimeout) {
      $timeout.cancel(_homeDownTimeout);
    }
    if (!_homeWasSet) {
      mapService.goHome();
    } else {
      _homeWasSet = false;
    }
  };

});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
<div ng-app="myApp" ng-controller="myCtrl">

  <button class="button icon ion-home button-map" ng-mousedown="homeDown()" ng-mouseup="homeUp()">HOME</button>

</div>
Rhumborl
  • 16,349
  • 4
  • 39
  • 45
Chris
  • 234
  • 1
  • 11

3 Answers3

1

Using the mobile emulator in Chrome, it appears your problem isn't so much the code, rather that the behaviour is to select the element when you touch-hold it, and this is interrupting the mouseup event.

A simply fix could be to set user-select:none in the button CSS, as described in this answer.

angular.module('myApp', []).controller('myCtrl', function($scope, $timeout) {

  var mapService = {
    setHome: function() {
      console.log("setHome called");
    },

    goHome: function() {
      console.log("goHome called");
    }
  };
  
  var _homeDownTimeout = null;
  var _homeWasSet = false;

  $scope.homeDown = function() {
    _homeDownTimeout = $timeout(function() {
      mapService.setHome();
      _homeWasSet = true;
    }, 3000);
  };

  $scope.homeUp = function() {
    if (_homeDownTimeout) {
      $timeout.cancel(_homeDownTimeout);
    }
    if (!_homeWasSet) {
      mapService.goHome();
    } else {
      _homeWasSet = false;
    }
  };

});
button {
  -webkit-touch-callout: none; /* iOS Safari */
  -webkit-user-select: none;   /* Chrome/Safari/Opera */
  -khtml-user-select: none;    /* Konqueror */
  -moz-user-select: none;      /* Firefox */
  -ms-user-select: none;       /* IE/Edge */
  user-select: none;           /* non-prefixed version, currently
                                  not supported by any browser */
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
<div ng-app="myApp" ng-controller="myCtrl">

  <button class="button icon ion-home button-map" ng-mousedown="homeDown()" ng-mouseup="homeUp()">HOME</button>

</div>
Community
  • 1
  • 1
Rhumborl
  • 16,349
  • 4
  • 39
  • 45
  • Thanks for your input. I still have the same behaviour with the emulator -> mouseup is interrupted, maybe Ionic has an interaction here. However, on my iPhone, the mouseup event is fired but the mousedown is triggered on touchend and not touchstart and executes right before the mouseup event. – Chris Feb 25 '16 at 10:51
0

Not too sure about angular but using pure javascript I'd do it like this.

Get the current time when the button is pressed and the current time when the button is released using:

var Time = new Date().getTime();

Substract the two and if the difference between the two is greater than 3000 then it's been pressed for more than 3 secs.

gothical
  • 373
  • 1
  • 7
0

I wrote a directive now. Tested with Chrome, FF and iPhone. Problem here was that on the iPhone touch and mouse events are fired (in this order), so I had to unbind the mouse events as soon as the touch events are fired.

HMTL:

<button class="button icon ion-home button-map" ng-holdclick="mapService.goHome(),mapService.setHome(),3000"></button>

JS:

app.directive("ngHoldclick", ['$timeout', function ($timeout) {
return {
    controller: function ($scope, $element, $attrs) {
        $element.bind('touchstart', onTouchStart);
        $element.bind('touchend', onTouchEnd);
        $element.bind('mousedown', onMouseDown);
        $element.bind('mouseup', onMouseUp);
        var params = $element.attr('ng-holdclick').split(",");
        var touchStartTimeout = null;
        var secondFunctionWasCalled = false;
        function onTouchStart(event) {
            $element.unbind('mousedown', onMouseDown);
            $element.unbind('mouseup', onMouseUp);
            $scope.$event = event;
            secondFunctionWasCalled = false;
            if (touchStartTimeout) {
                $timeout.cancel(touchStartTimeout);
            }
            touchStartTimeout = $timeout(function () {
                $scope.$apply(params[1]);
                secondFunctionWasCalled = true;
            }, params[2]);
        };
        function onMouseDown(event) {
            $element.unbind('touchstart', onTouchStart);
            $element.unbind('touchend', onTouchEnd);
            $scope.$event = event;
            secondFunctionWasCalled = false;
            if (touchStartTimeout) {
                $timeout.cancel(touchStartTimeout);
            }
            touchStartTimeout = $timeout(function () {
                $scope.$apply(params[1]);
                secondFunctionWasCalled = true;
            }, params[2]);
        };
        function onTouchEnd(event) {
            $scope.$event = event;
            if (touchStartTimeout) {
                $timeout.cancel(touchStartTimeout);
            }
            if (!secondFunctionWasCalled) {
                $scope.$apply(params[0]);
            } else {
                secondFunctionWasCalled = false;
            }
        };
        function onMouseUp(event) {
            $scope.$event = event;
            if (touchStartTimeout) {
                $timeout.cancel(touchStartTimeout);
            }
            if (!secondFunctionWasCalled) {
                $scope.$apply(params[0]);
            } else {
                secondFunctionWasCalled = false;
            }
        };
    }
};
}]);
Chris
  • 234
  • 1
  • 11