0

I need a button that can be pressed once to execute a single command. But it should also possible to hold the button and execute the command multiple times while holding the button. I'm using AngularJs (although I don't think it is related to the problem)

What I had so far:

<button type="button" 
        class="btn btn-default" 
        ng-click="ChangeSetPoint('Up')"
        ng-mousedown="startLoopingUp()"
        ng-mouseup="stopLoopingUp()"
        ng-mouseleave="stopLoopingUp()">
        +
</button>

and in the controller:

$scope.ChangeSetPoint = function(direction){
            //Stuff to actually change the setpoint
        }

        var looping = false;
        var promis;
        $scope.startLoopingUp = function(){
            looping = true;
            promis = setTimeout(loop('Up'),1000);           
        }

        var loop = function(direction){                                         
            $scope.ChangeSetPoint(direction);
            if(looping){
                promis = setTimeout(loop(direction),300)
            }
        }

        $scope.stopLoopingUp = function(){
           looping = false;
           clearTimeout(promis);
        }

It kind-of work before I was using this 'direction' parameter. Before I used arguments.callee in setTimeout, but when I looked how to pass a argument with that function, I notices that the use of arguments.callee was discouraged (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments/callee). Since then I'm getting 'Maximum call stack size exceeded' errors.

Stijn Van Antwerpen
  • 1,840
  • 17
  • 42

2 Answers2

0

I've used the following function inside a directive some time ago. I created it with the following in mind:

The function may hold three separate callback functions. The "short" callback gets called on a single click. When holding the button, the short callback gets called repeatedly. When still keeping the button down, the "long" callback gets fired repeatedly. At the end, when the user stops pressing, a third, "final" callback will be fired.

It may not be an exact solution to your problem, but maybe it will inspire and help you a bit :) Good luck.

/**
  *
  * @param {Event} evt
  * @param {Function} shortCallback
  * @param {Function} longCallback
  * @param {Function} [finishCallback] optional
  */
var onBtnClick = function (evt, shortCallback, longCallback, finishCallback) {
    //prevent mobile browser from long tap behaviour (simulated right click)
    evt.preventDefault();
    //only react to left mouse button or a touch event
    if (evt.which === 1 || evt.type === "touchstart") {
        //save 'this' context and interval/timeout IDs
        var self = this,
            short = {
                timeout     : null,
                interval    : null,
                callback    : angular.isFunction(shortCallback) ? shortCallback : angular.noop
            },
            long = {
                timeout     : null,
                interval    : null,
                callback    : angular.isFunction(longCallback) ? longCallback : short.callback
            },
            listener = "mouseup mouseleave touchend touchcancel",
            //
            cancelShort = function () {
                $timeout.cancel(short.timeout);
                $interval.cancel(short.interval);
            },
            //
            cancelLong = function () {
                $timeout.cancel(long.timeout);
                $interval.cancel(long.interval);
            };

        //react to a single click
        short.callback();

        //when user leaves the button cancel timeout/interval, lose focus and unbind recently bound listeners
        self.one(listener, function (e) {
            e.preventDefault();
            cancelShort();
            cancelLong();

            if (angular.isFunction(finishCallback)) {
                finishCallback();
            }

            self.blur();
        });

        //on a long click call the callback function within an interval for faster value changing
        short.timeout = $timeout(function () {
            short.interval = $interval(short.callback, 50, 0, false);
        }, 300, false);

        //when pressed even longer, cancel previous callback and fire "long" one
        long.timeout = $timeout(function () {
            cancelShort();
            long.interval = $interval(long.callback, 50, 0, false);
        }, 1500, false);
    }
};

This function has been bound to an element with the following:

/**
 *
 * @param {String} selector
 * @param {Function} clickCallback
 * @param {Function} fastCallback
 * @param {Function} [finishCallback] optional
 */
 var bindEvent = function (selector, clickCallback, fastCallback, finishCallback) {
     $element.on("mousedown touchstart", selector, function (evt) {
         onBtnClick.call($(this), evt, clickCallback, fastCallback, finishCallback);
     });
 };
Fidel90
  • 1,828
  • 6
  • 27
  • 63
0

It was the parameter that did the harm:

when changing

setTimeout(loop, 1000) to setTimeout(loop('Up'), 1000)

I was not giving the function as a parameter, but executing the function and giving the return as a parameter.

I should have done:

promis = setTimeout(function(){ loop('Up') },1000); 
Stijn Van Antwerpen
  • 1,840
  • 17
  • 42