2

When I click on "Add New Item" button I want a popup form to be displayed. Then if I click outside this form I want it to hide itself. To show/hide form I use ng-show directive. To watch for outside clicks I use third-party Angular directive angular-clickout But there is a problem - this directive start working on page load and when I click on "Add New Item" button it immediately invokes my closing function which in turn sets boolean attribute to false value and ng-show hides a form...

HTML:

<button ng-click="vm.displayDialogAddNewItem()>Add New Item</button>
<div class="new-item-dialog" 
     ng-show="vm.dialogAddNewItemIsVisible"     
     click-out="vm.hideDialogAddNewItem()">
    ... omitted code ...
</div>

Controller's code:

vm.displayDialogAddNewItem = function() {
    vm.dialogAddNewItemIsVisible = true;
};

vm.hideDialogAddNewItem = function() {
    vm.dialogAddNewItemIsVisible = false;
};
yaru
  • 1,260
  • 1
  • 13
  • 29

2 Answers2

1

You need to prevent the event from bubbling up the DOM tree, thus preventing the parent handler (on the window, that closes the modal) from being notified of the event.

<!-- Pass the $event to the handler -->
<button ng-click="vm.displayDialogAddNewItem($event)>Add New Item</button>

JS

vm.displayDialogAddNewItem = function($event) {
    vm.dialogAddNewItemIsVisible = true;
    $event.stopPropagation(); // Stop bubbling up.
};
Blaise
  • 13,139
  • 9
  • 69
  • 97
  • This approach has some issues. For instance if you have 2 buttons that can produce a popup. Clicking button1 then button2 will not close the first popup because the shared parent never sees any of the click events. In the case of the clickout module this shared parent is the window. See: https://github.com/neoziro/angular-clickout/blob/master/angular-clickout.js Also see: http://stackoverflow.com/questions/20186438/angular-click-outside-of-an-element-event – Halcyon May 04 '15 at 15:56
0

This has to do with how events work in JavaScript.

Let's say you have this HTML:

<div id="content">
  <input type="button />
</div>

If you click the button, a click event is generated. The event will start in the capturing phase (top down), it will go down starting at window, then div#content then the button. Then the bubbling phase starts (bottom up), first the button, then div#content then window.

Events attach to the bubbling phase by default so your button receives the click event, shows the popup, then the window receives the click and hides the popup. That's a little inconvenient.

One way to get around this is to use stopPropagation to stop the event from bubbling up. This approach has some issues. For instance if you have 2 buttons that can produce a popup. Clicking button1 then button2 will not close the first popup because the shared parent never sees any of the click events.

Another solution is to add the popup close in the capture phase instead. A potential problem here surfaces when you want to block clicks on the popup itself from closing the popup. This block is difficult to determine in the capture phase. An additional solution is to mark the event in the capture phase, and delay closing to the bubble phase (allowing the popup to throw a block). This does not play nice with stopPropagation however. Any element with stopPropagation will cause the popup closer to fail because the event will never bubble up again.

Another way is to let the popup handler know it should ignore the next click. stopPropagation again has the potential to do damage here. For instance when id#content has stopPropagation on it.

You could also use some kind of setTimeout trick to delay rendering of the popup until the click event has finished it's bubble phase. This works but it feels like a terrible hack.


As you can see from this fairly elaborate answer, this is a problem I've run into myself and have put quite a bit of thought into. I don't know the solution yet. Every solution seems to have it's problems. I have decided however that stopPropagation is bad. My reasoning is that stopPropagation breaks modularity. A child node can affect the behavior of a parent node without the parent's consent or knowledge, this seems like a problem.

Halcyon
  • 57,230
  • 10
  • 89
  • 128
  • *"Clicking button1 then button2 will not close the first popup because the click event is aborted."* That's only true when button2 is a parent of button1, which is unlikely. – Blaise May 04 '15 at 15:33
  • @Blaise Keep in mind that the click event to detect clicks outside of the element has to be on a parent element somewhere, and preferably as high as possible. So the first popup doesn't close if they share this parent, which is probably going to be `body`. So I'd say it's _very_ likely. – Halcyon May 04 '15 at 15:33
  • clickout uses `window` as the shared parent. See: https://github.com/neoziro/angular-clickout/blob/master/angular-clickout.js – Halcyon May 04 '15 at 16:00