AngularJS - Hold To Activate Button Directive

I needed a hold button that gave visual feedback. This fills with a color and duration you define then calls whatever method you give it. Arguments are allowed as part of the success callback. I'll continue to tweak on it as I use it more.

Inspiration for this code came from http://www.lazyentropy.com/blog/2015/03/18/hold-button-angular-js.html. I just wanted something a little more self contained than his methods allowed for.

Gist available here: https://gist.github.com/lancegliser/a29dacb4f75f13195b5c

directives.js

  1. app.directive('holdButton', function($parse, $interval) {
  2.   //The framerate of the progress bar, progression will be evaluated every 5ms.
  3.   var tickDelay = 10;
  4.   var minFill = 5;
  5.  
  6.   return {
  7.     restrict: 'A',
  8.     scope: {
  9.       holdButtonSuccess: '&'
  10.     }
  11.     ,link: function postLink(scope, element, attrs) {
  12.       var started, completed, stop;
  13.  
  14.       var engage = function($event){
  15.         var holdDelay = attrs.holdButtonDelay ? ($parse(attrs.holdButtonDelay)(scope) || 400) : 400;
  16.         var counter = 0;
  17.         var nbTick = holdDelay / tickDelay;
  18.         element.removeClass('hold-success', 'hold-failure');
  19.         element.addClass('holding');
  20.         completed = false;
  21.  
  22.         var fillColor = !!attrs.holdButtonFillColor? attrs.holdButtonFillColor : '#A1A1A1';
  23.         element.css('background-image', 'linear-gradient(to right, ' + fillColor + ' ' + minFill + '%, transparent ' + minFill + '%)');
  24.  
  25.         // Call the onTick function `nbTick` times every `tickDelay` ms.
  26.         // stop is the stopper function
  27.         stop = $interval(onTick, tickDelay, nbTick);
  28.         started = true;
  29.         function onTick() {
  30.           counter++;
  31.           var percentage = Math.max(Math.round(counter / nbTick * 100), minFill);
  32.           element.css('background-image', 'linear-gradient(to right, ' + fillColor + ' ' + percentage + '%, transparent ' + percentage + '%)');
  33.  
  34.           // If we reach `nbTick` then we're done
  35.           if (counter === nbTick) {
  36.             if( !!scope.holdButtonSuccess ){
  37.               scope.holdButtonSuccess();
  38.             }
  39.             completed = true;
  40.             element.addClass('hold-success');
  41.             element.css('background-image', null);
  42.           }
  43.         }
  44.       };
  45.  
  46.       var disengage = function($event){
  47.         // Prevent standard events for all interactions
  48.         $event.stopPropagation();
  49.         $interval.cancel(stop);
  50.         element.removeClass('holding');
  51.         element.css('background-image', null);
  52.  
  53.         if( !started || completed ){
  54.           return;
  55.         }
  56.         element.addClass('hold-failure');
  57.         // TODO add a failure callback?
  58.       };
  59.  
  60.       element.on('mousedown', function($event) {
  61.         engage($event);
  62.       });
  63.       element.on('mouseup', function($event) {
  64.         disengage($event);
  65.       });
  66.       element.on('mouseleave', function($event) {
  67.         disengage($event);
  68.       });
  69.  
  70.       // Touch events
  71.       element.on('touchstart', function($event) {
  72.         engage($event);
  73.       });
  74.       element.on('touchend', function($event) {
  75.         disengage($event);
  76.       });
  77.     }
  78.   };
  79. });

Usage:

HTML

  1.        hold-button
  2.        hold-button-success="holdAction()" <!-- this should be a $scope function, optionally with parameters -->
  3.         hold-button-delay="" <!-- number of milliseconds required, default 400 -->
  4.         hold-button-fill-color="" <!-- any acceptable css color string -->
  5.         >Hold to Activate
  6.       </button>

CSS

  1. button.holding {
  2.   border-style: dashed;
  3. }
  4. button.hold-success {
  5.   background-color: #43AC6A;
  6.   border-color: #368a55;
  7.   color: #fff;
  8. }

Protractor / Jasmine Test

It seems this produces errors in Firefox. I haven't yet fixed them: "UnknownError: Cannot press more then one button or an already pressed button.'UnknownError: Cannot press more then one button or an already pressed button.' when calling method: [wdIMouse::down]"

  1. describe('hold buttons', function(){
  2.   // You'll need to write the page definition yourself
  3.   var holdButton = page.getHoldButton();
  4.   it('should give feedback on interaction', function(){
  5.     browser.actions().mouseDown(holdButton).perform();
  6.     browser.sleep(50);
  7.     expect(holdButton.getAttribute('class') ).toMatch('holding');
  8.     browser.actions().mouseUp(holdButton).perform();
  9.   });
  10.   it('should not complete in under .5 seconds', function(){
  11.     browser.actions().mouseDown(holdButton).perform();
  12.     browser.sleep(50);
  13.     browser.actions().mouseUp(holdButton).perform();
  14.     expect(holdButton.getAttribute('class') ).toMatch('hold-failure');
  15.   });
  16.   it('should complete eventually', function(){
  17.     browser.actions().mouseDown(holdButton).perform();
  18.     browser.sleep(2000);
  19.     browser.actions().mouseUp(holdButton).perform();
  20.     expect(holdButton.getAttribute('class') ).toMatch('hold-success');
  21.   });
  22. });
Tags: 

Add new comment

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
By submitting this form, you accept the Mollom privacy policy.