Automatically pass $event with ng-click?
Asked Answered
G

4

74

I know that I can get access to the click event from ng-click if I pass in the $event object like so:

<button ng-click="myFunction($event)">Give me the $event</button>

<script>
  function myFunction (event) {
    typeof event !== "undefined" // true
  }
</script>

It's a little bit annoying having to pass $event explicitly every time. Is it possible to set ng-click to somehow pass it to the function by default?

Goldeye answered 13/1, 2014 at 21:14 Comment(13)
I know that code is just for demonstration but undefined should be "undefined", otherwise the expression will always evaluate to false as typeof returns a string.Laughable
I'm wondering why you need $event anyway.Pigeonwing
@zeroflagL Event object is usually used to stop the bubbling/propagation.Existence
@Existence myFunction is part of a controller. A controller shouldn't care about the UI. Furthermore a simple button has no default action. And angular does prevent many default actions anyway. So while you're right, of course, it's hard to imagine a good use case.Pigeonwing
@zeroflagL I have a directive contextMenu which provides several functions for the scope (open, close, toggle), which are called with the name of a specific contextMenu, eg ng-click="account_menu.toggle()". The menu itself is in a different element, eg <div context-menu="account_menu"></div>". In order to position the menu correctly on the page right below the trigger element that called toggle(), I want access to ng-click's $event.target element, so that I can get its coordinates. Do let me know if there is a simpler way :)Goldeye
@FabrícioMatté Good catch, I will fix this :)Goldeye
@Goldeye that's my point. This is UI stuff and should definitely be handled by a directive. contextMenu should be self-containing.Pigeonwing
@zeroflagL Yes, contextMenu is a self-containing directive whose functions open, close, and toggle are the ones which want access to $event. ng-click is simply used to trigger those functions (in the contextMenu directive). The point is that I should be able to use whichever of ng-click, ng-mouseover, etc, to handle different contextMenus differently. If you're interested, here is a highly simplified example jsfiddle.net/uYayL/1.Goldeye
Good morning, Elise. Please take a look at this fiddle. It's a limited implementation, but you get the idea. You only declare a directive, all the (UI) action is handled by the directive itself.Pigeonwing
@zeroflagL Thanks for taking the time to explain, I do get your idea. However I feel it makes more sense (in my specific project at least) for the contextMenu to encapsulate the behaviour of the actual menu, and not an arbitrary element triggering actions on it. I mean, the button is not the menu :) either way, UI manipulation is captured within a single directive.Goldeye
It does capture the behavior of the actual menu and the button has a context menu. An arbitrary element is not a title either, but has a title attribute. Which in turn triggers a popup on hover. That's the same concept. And no: in your case the ng-click does the UI manipulation. That fact that it calls a method defined somewhere else doesn't change that.Pigeonwing
@zeroflagL That's a fair point, the title attribute argument is very convincing.. So how would you handle a situation where different buttons need to trigger the context menu in different ways (eg one with hover, one with click), while the menu itself looks and behaves exactly the same?Goldeye
let us continue this discussion in chatPigeonwing
E
65

Take a peek at the ng-click directive source:

...
compile: function($element, attr) {
  var fn = $parse(attr[directiveName]);
  return function(scope, element, attr) {
    element.on(lowercase(name), function(event) {
      scope.$apply(function() {
        fn(scope, {$event:event});
      });
    });
  };
}

It shows how the event object is being passed on to the ng-click expression, using $event as a name of the parameter. This is done by the $parse service, which doesn't allow for the parameters to bleed into the target scope, which means the answer is no, you can't access the $event object any other way but through the callback parameter.

Existence answered 13/1, 2014 at 21:27 Comment(0)
J
46

Add a $event to the ng-click, for example:

<button type="button" ng-click="saveOffer($event)" accesskey="S"></button>

Then the jQuery.Event was passed to the callback:

enter image description here

Jurisdiction answered 5/8, 2014 at 7:39 Comment(3)
...except that this is the exact thing that the OP is trying to NOT do, unless I'm crazy. The entire point of this post is to access the $event without explicitly passing it... which appears to be exactly what you're doing here.Nock
I (on the other hand) can live with itLepus
Ended up here after a google search for how to pass $event. As similar to other upvoters, didn't read the original question :-|Shawnna
T
11

As others said, you can't actually strictly do what you are asking for. That said, all of the tools available to the angular framework are actually available to you as well! What that means is you can actually write your own elements and provide this feature yourself. I wrote one of these up as an example which you can see at the following plunkr (http://plnkr.co/edit/Qrz9zFjc7Ud6KQoNMEI1).

The key parts of this are that I define a "clickable" element (don't do this if you need older IE support). In code that looks like:

<clickable>
  <h1>Hello World!</h1>
</clickable>

Then I defined a directive to take this clickable element and turn it into what I want (something that automatically sets up my click event):

app.directive('clickable', function() {
    return {
        transclude: true,
        restrict: 'E',
        template: '<div ng-transclude ng-click="handleClick($event)"></div>'
    };
});

Finally in my controller I have the click event ready to go:

$scope.handleClick = function($event) {
    var i = 0;
};

Now, its worth stating that this hard codes the name of the method that handles the click event. If you wanted to eliminate this, you should be able to provide the directive with the name of your click handler and "tada" - you have an element (or attribute) that you can use and never have to inject "$event" again.

Hope that helps!

Taction answered 13/1, 2014 at 21:39 Comment(1)
You have to make that handleClick event something that can be passed in, man :)Viaduct
T
2

I wouldn't recommend doing this, but you can override the ngClick directive to do what you are looking for. That's not saying, you should.

With the original implementation in mind:

compile: function($element, attr) {
  var fn = $parse(attr[directiveName]);
  return function(scope, element, attr) {
    element.on(lowercase(name), function(event) {
      scope.$apply(function() {
        fn(scope, {$event:event});
      });
    });
  };
}

We can do this to override it:

// Go into your config block and inject $provide.
app.config(function ($provide) {

  // Decorate the ngClick directive.
  $provide.decorator('ngClickDirective', function ($delegate) {

    // Grab the actual directive from the returned $delegate array.
    var directive = $delegate[0];

    // Stow away the original compile function of the ngClick directive.
    var origCompile = directive.compile;

    // Overwrite the original compile function.
    directive.compile = function (el, attrs) {

      // Apply the original compile function. 
      origCompile.apply(this, arguments);

      // Return a new link function with our custom behaviour.
      return function (scope, el, attrs) {

        // Get the name of the passed in function. 
        var fn = attrs.ngClick;

        el.on('click', function (event) {
          scope.$apply(function () {

            // If no property on scope matches the passed in fn, return. 
            if (!scope[fn]) {
              return;
            }

            // Throw an error if we misused the new ngClick directive.
            if (typeof scope[fn] !== 'function') {
              throw new Error('Property ' + fn + ' is not a function on ' + scope);
            }

            // Call the passed in function with the event.
            scope[fn].call(null, event);

          });
        });          
      };
    };    

    return $delegate;
  });
});

Then you'd pass in your functions like this:

<div ng-click="func"></div>

as opposed to:

<div ng-click="func()"></div>

jsBin: http://jsbin.com/piwafeke/3/edit

Like I said, I would not recommend doing this but it's a proof of concept showing you that, yes - you can in fact overwrite/extend/augment the builtin angular behaviour to fit your needs. Without having to dig all that deep into the original implementation.

Do please use it with care, if you were to decide on going down this path (it's a lot of fun though).

Tonsillectomy answered 23/7, 2014 at 14:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.