Skip to content

Instantly share code, notes, and snippets.

@EpokK
Last active January 7, 2022 13:57
Show Gist options
  • Save EpokK/5884263 to your computer and use it in GitHub Desktop.
Save EpokK/5884263 to your computer and use it in GitHub Desktop.
ngEnter directive if you can use submit form(https://twitter.com/ririlepanda)
app.directive('ngEnter', function() {
return function(scope, element, attrs) {
element.bind("keydown keypress", function(event) {
if(event.which === 13) {
scope.$apply(function(){
scope.$eval(attrs.ngEnter);
});
event.preventDefault();
}
});
};
});
@marcofranssen
Copy link

I added a the directive on a module, but somehow the code never executes. Put a breakpoint at the first line of code.

//app.js
angular.module('notify', [
    'notify.controllers',
    'notify.services',
    'notify.directives'
  ]);
//directives.js
angular.module('notify.directives', [])
    .directive('ngEnter', function () {
        return function (scope, elements, attrs) {
            elements.bind('keydown keypress', function (event) {
                if (event.which === 13) {
                    scope.$apply(function () {
                        scope.$eval(attrs.ngEnter);
                    });
                    event.preventDefault();
                }
            });
        };
    });
<button ng-enter="doAction(userId)" ng-click="doAction(userId)"></button>

What am I missing? The click is working!

@zbabtkis
Copy link

@marcofranssen You might need to specify the scope on the directive. I had the same problem and that fixed it. Mine looks like this:

module.directive('ngEnter', function () {
        return {
           controller: 'MyCtrl'
           link: function (scope, elements, attrs) {
              elements.bind('keydown keypress', function (event) {
                  if (event.which === 13) {
                      scope.$apply(function () {
                          scope.$eval(attrs.ngEnter);
                      });
                      event.preventDefault();
                  }
              });
           }
        };
    });

@tlambeir
Copy link

I wrote a simple test for using this in our angular project:

'use strict';

describe('Directive: ngEnter', function () {

  // load the directive's module
  beforeEach(module(app));

  var element,
      scope;

  beforeEach(inject(function ($rootScope) {

    scope = $rootScope;
    scope.mockFunction = function(){};
    compileDirective();

  }));

  /**
   * Compile the directive into HTML
   */
  function compileDirective(){
    element = angular.element('<input type="text" data-ng-enter="mockFunction()" />');
    inject(function($compile){
      element = $compile(element)(scope);
    });
    scope.$apply();
  }

  it('it should call the mock function on pressing enter', function () {
    spyOn(scope,'mockFunction');
    var e = jQuery.Event('keypress');
    e.which = 13; //choose the one you want
    e.keyCode = 13;
    element.trigger(e);
    expect(scope.mockFunction).toHaveBeenCalled();
  });

});

@jobsamuel
Copy link

Thanks for this EpokK, it's really helpful!

@jeffmcmahan
Copy link

Thanks for this, EpokK!

@sachitsac
Copy link

Hi, Thanks for this directive. I just tried to pass $event to a function that was invoked as follows:

<div ng-enter="doSomething(a, b, $event)"></div>

But this did not pass the $event to the custom function i was trying to invoke. Now i checked angularjs inplementation of ng{Events} and found we need to eval $event as well. I fixed it using the code below but i am not sure if this is the correct way. Can anyone here confirm ?

'use strict';

angular.module('myApp').directive('ngEnter', function () {
  return function (scope, element, attrs) {
    element.bind('keydown keypress', function (event) {
      if(event.which === 13) {
        scope.$apply(function (){
            scope.$eval(attrs.ngEnter, {$event:event});
          });
        event.preventDefault();
      }
    });
  };
});

@jaumebonet
Copy link

Very nice!
It work like a charm for my purpose!

@abobwhite
Copy link

Thanks! Very helpful and worked like a charm.

@adswebwork
Copy link

Works like a charm.
Thanx.

@technotrom
Copy link

it doesn't work for me.

//html
<input type="text"
ng-model="Search"
ng-enter='alert(Search)'
class="form-control"

         >

//javascript

angular.module('ui.bootstrap').directive('ngEnter', function() {
return function(scope, element, attrs) {
element.bind("keydown keypress", function(event) {
if(event.which === 13) {
scope.$apply(function(){
scope.$eval(attrs.ngEnter, {'event': event});
});

                event.preventDefault();
            }
        });
    };
});

@japo32
Copy link

japo32 commented Jun 3, 2015

Thanks! I just plugged it in and it worked.

@jbaroudi
Copy link

typescript version if you like

module util {
'use strict';

export class KeyEnterDirective implements angular.IDirective {

    public link;

    restrict = 'A';
    scope = false;

    constructor() {
        this.link = this.unboundLink.bind(this);
    }

    unboundLink(scope: angular.IScope, element: JQuery, attributes: any) {
        element.bind('keydown keypress', function(event: JQueryEventObject) {
            if (event.which === 13) {
                scope.$apply(function () {
                    scope.$eval(attributes.keyEnter);
                });

                event.preventDefault();
            }
        });
    }

    static instance(): ng.IDirectiveFactory {

        var directive = () => new KeyEnterDirective();
        directive.$inject = [];
        return directive;
    }

}
}

and it's loaded like this:

angular.module('myModule', ['ngAnimate', ...])
    .directive('keyEnter', util.KeyEnterDirective.instance())

You can change the directive name ('keyEnter') to decorate your attribute:

<input key-enter="myFunction()" />

Hope this helps someone :)

@Anhvutpbn
Copy link

Thanks!

@mittalabhas1
Copy link

👍 @EpokK

@samuelneff
Copy link

Minor improvement to support ng-enter with no value to trigger the ng-click on the same element, which for me is usually what I want.

'use strict';

angular.module('myApp').directive('ngEnter', function () {
  return function (scope, element, attrs) {
    element.bind('keydown keypress', function (event) {
      if(event.which === 13) {
        scope.$apply(function (){
            scope.$eval(attrs.ngEnter || attrs.ngClick, {$event:event});
          });
        event.preventDefault();
      }
    });
  };
});

@charleswangyi
Copy link

Thanks

@sankety
Copy link

sankety commented Oct 30, 2015

"Error: $apply already in progress"
I got this error in console and some how the event.preventDefault(); isn't working in my case
the cursor goes to next line.

@sankety
Copy link

sankety commented Oct 30, 2015

routerApp.directive('ngEnter', function () {
return function (scope, element, attrs) {
element.bind("keypress", function (event) {
//element.bind("keydown keypress", function (event) {
if(event.which === 13) {
scope.$apply(function (){
scope.$eval(attrs.ngEnter);
});

            event.preventDefault();
        }
    });
};

});

I removed "Keydown" event and error disappeared

@rpalazzo
Copy link

EpokK, would you license this function under a permissive license, like MIT? Thanks!

@alwayrun
Copy link

nice

@cafesanu
Copy link

even better, why not make it more generic to support any key press (ng-keypress does not support all keys... like escape) or an specific key (specified as an attribute)...

// Directive
angular.module('myApp').directive('ngOnKeyPress', function () {
    return function(scope, element, attrs) {
        var validKeyCodes = {
            esc: 27,
            enter: 13
        };

        element.bind('keydown keypress', function(event) {
            if (!attrs.key || event.which === validKeyCodes[attrs.key]) {
                scope.$apply(function() {
                    scope.$eval(attrs.hbOnKeyPress, {$event: event});
                });
                event.preventDefault();
            }
        });
    };
});

// Directive test
describe('Directive: ng-on-key-press', function() {
    var element,
        scope,
        validKeyCodes = {
            esc: 27,
            enter: 13
        };

    beforeEach(module(app));

    beforeEach(inject(function($injector) {
        var $rootScope = $injector.get('$rootScope');

        scope = $rootScope.$new();
        scope.mockFunction = function() {};
    }));

    /**
     * Compile the directive into HTML
     */
    function compileDirective(html) {
        element = angular.element(html);
        inject(function($compile) {
            element = $compile(element)(scope);
        });
        scope.$apply();
    }

    it('it should call the mock function on pressing enter', function() {
        var eventMock = angular.element.Event('keypress');

        compileDirective('<input type="text" ng-on-key-press="mockFunction()" />');

        spyOn(scope, 'mockFunction');
        element.trigger(eventMock);
        expect(scope.mockFunction).toHaveBeenCalled();
    });

    it('it should call the mock function on pressing a key that does match key specified', function() {
        var eventMock = angular.element.Event('keypress');

        compileDirective('<input type="text" ng-on-key-press="mockFunction()" key="esc" />');
        spyOn(scope, 'mockFunction');
        eventMock.which = validKeyCodes.enter;
        eventMock.keyCode = validKeyCodes.enter;
        element.trigger(eventMock);
        expect(scope.mockFunction).not.toHaveBeenCalled();
    });

    it('it should not call the mock function on pressing a key that does NOT match key specified', function() {
        var eventMock = angular.element.Event('keypress');

        compileDirective('<input type="text" ng-on-key-press="mockFunction()" key="esc" />');
        spyOn(scope, 'mockFunction');
        eventMock.which = validKeyCodes.esc;
        eventMock.keyCode = validKeyCodes.esc;
        element.trigger(eventMock);
        expect(scope.mockFunction).toHaveBeenCalled();
    });

    it('it should not call the mock function on pressing a key when wrong key specified', function() {
        var eventMock = angular.element.Event('keypress');

        compileDirective('<input type="text" ng-on-key-press="mockFunction()" key="doesnotexist" />');
        spyOn(scope, 'mockFunction');
        eventMock.which = validKeyCodes.enter;
        eventMock.keyCode = validKeyCodes.enter;
        element.trigger(eventMock);
        expect(scope.mockFunction).not.toHaveBeenCalled();
    });
});

@caviles
Copy link

caviles commented Apr 2, 2016

This works great. You're awesome!!!

@sgsheg
Copy link

sgsheg commented Aug 1, 2016

👍

@dougrchamberlain
Copy link

This is silly. angular provides ng-keypress. what functionality is this providing that ng-keypress doesn't already? This directive has been around since at least 1.2.29 https://code.angularjs.org/1.2.29/docs/api/ng/directive/ngKeypress I looked at the source code too, it's

/*
               * A collection of directives that allows creation of custom event handlers that are defined as
               * angular expressions and are compiled and executed within the current scope.
               */
              var ngEventDirectives = {};

              // For events that might fire synchronously during DOM manipulation
              // we need to execute their event handlers asynchronously using $evalAsync,
              // so that they are not executed in an inconsistent state.
              var forceAsyncEvents = {
                'blur': true,
                'focus': true
              };
              forEach(
                'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '),
                function(eventName) {
                  var directiveName = directiveNormalize('ng-' + eventName);
                  ngEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse, $rootScope) {
                    return {
                      restrict: 'A',
                      compile: function($element, attr) {
                        // We expose the powerful $event object on the scope that provides access to the Window,
                        // etc. that isn't protected by the fast paths in $parse.  We explicitly request better
                        // checks at the cost of speed since event handler expressions are not executed as
                        // frequently as regular change detection.
                        var fn = $parse(attr[directiveName], /* interceptorFn */ null, /* expensiveChecks */ true);
                        return function ngEventHandler(scope, element) {
                          element.on(eventName, function(event) {
                            var callback = function() {
                              fn(scope, {$event:event});
                            };
                            if (forceAsyncEvents[eventName] && $rootScope.$$phase) {
                              scope.$evalAsync(callback);
                            } else {
                              scope.$apply(callback);
                            }
                          });
                        };
                      }
                    };
                  }];
                }
              );

@jose-marin
Copy link

jose-marin commented Aug 11, 2016

You don't need to use eval if you bind the function passed. It also makes the scope isolate, so the directive doesn't depend on a parent controller.

app.directive('onEnter', function() {
    return {
        restrict: "A",
        scope: {
            action: "&onEnter"
        },
        link: function(scope, element, attrs) {
            element.on("keydown keypress", function(event) {
                if(event.which === 13) {
                    scope.$apply(scope.action);
                    event.preventDefault();
                }
            });
        }
    };
});

@adilbenmoussa
Copy link

No was is asking the question if element.on("keydown keypress", fun) should be unbind? when destroying the element?

@georgeportillo
Copy link

georgeportillo commented Sep 21, 2016

@jose-marin thanks for the snippet!

@michalpanek
Copy link

attrs is no needed.

  link: function(scope, element, attrs) {

@w0nderw0man
Copy link

@jbaroudi thanks for typescript users

@developerant
Copy link

@jose-marin - nicely done. Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment