1

I have a slideUp configuration area that should close when clicked outside of it, but of course not when clicked inside the element. All the questions I could find here about that subject, was about hiding menus, in which case it doesn't really matter where the click target is.

After several hours I devised a working solution - available in this plunker - but I had to use jQuery to test if the click-target was indeed outside of the element.

As I'm in the process of learning AngularJS I'd like to avoid using jQuery; I was told that was the best way to learn it ;)

Is there a way to detect that? I bet it's very obvious, but the most trivial things takes hours at the stage I'm currently on :D

Edit: The real issue here is detecting if the click is in- or outside of the element without "eating" any relevant events.

tanghus
  • 55
  • 3
  • 10
  • http://stackoverflow.com/questions/12931369/click-everywhere-but-here-event voted to close heres the duplicate – shaunhusain Apr 04 '14 at 05:57
  • As well as the suggestion by @shaunhusain, you should refactor any Angular code that manipulates the DOM into a directive. Here's a Plunker as an example (sans the defocus feature you are chasing) http://plnkr.co/edit/pRPen4Om97hez3iybeEV?p=preview – miqh Apr 04 '14 at 06:03
  • @shaunhusain The [click-anywhere-but-here](http://stackoverflow.com/questions/12931369/click-everywhere-but-here-event) works insofar that it catches the events, but it is too greedy because it also catches legit events inside the config area as shown in [this plunker](http://plnkr.co/edit/eadaY6?p=preview) – tanghus Apr 04 '14 at 14:19
  • @midiq Yes, you're right that it should be in a directive. I haven't reached that chapter yet ;) – tanghus Apr 04 '14 at 14:27
  • I'll have to correct myself: The [second answer](http://stackoverflow.com/a/16742128/373007) in the linked question does what I want it to do - at least when I implement it on my development site. I tried to update the [plunker](http://plnkr.co/edit/OSvQS0?p=preview) but when run there I get `Error: Multiple directives [clickAnywhereButHere, ngController] asking for isolated scope on:
    `..?
    – tanghus Apr 04 '14 at 16:21
  • I'm not sure I should close this as duplicate (I don't know how to either?). This has a better answer than the one linked to. – tanghus Apr 07 '14 at 09:40

2 Answers2

2

The "Angular way" would be to put in a directive, here's a simple working example of a generic directive that can hide/show contents and use the body to hide as well.

http://jsbin.com/wanupero/1/edit

It doesn't need jQuery (uses jqLite that is bundled with Angular).

Edit: here's a version that demonstrates the directive does not swallow all clicks... http://jsbin.com/wanupero/4/edit

JeremyWeir
  • 24,118
  • 10
  • 92
  • 107
  • I like the "Angular way" of your approach, but it creates another problem, that I've also mentioned in the question comments. It is greedy and "eats" any legit click events inside the element. If for example you have a button with an `ng-click` directive inside, it will not be triggered. – tanghus Apr 04 '14 at 14:34
  • Are you sure? I modified the jsbin to show that's not true. See the edit in the answer – JeremyWeir Apr 04 '14 at 16:48
  • Dang, you're right! :) That is a more elegant solution as the show/hide logic is totally contained in the directive. Is the double `
    ` in the template on purpose, or can it be omitted?
    – tanghus Apr 04 '14 at 17:38
  • No, it can't be omitted, an angular template needs to have one parent element. It's the element that gets injected into the linking function. – JeremyWeir Apr 05 '14 at 19:05
  • Yeah, I though I had read something like that - somewhere. Thanks again. – tanghus Apr 07 '14 at 09:24
0

HTML/CSS Solution

How about creating a layer over the whole page with an ng-click to hide the slideUp and then you can safely add your slideUp element on top of that layer?

HTML

  <body ng-controller="MainCtrl">
    <div class="mask" ng-click="toggleSlideUp()"></div>
    <div class="slide-up" ng-show="slideUp.show"></div>
  </body>

JS

var app = angular.module('plunker', []);

app.controller('MainCtrl', function($scope) {
  $scope.slideUp = {show:true};
  $scope.toggleSlideUp = function() {
    $scope.slideUp.show = !$scope.slideUp.show;
  };
});

CSS

.mask {
  position:absolute;
  z-index:1;
  top:0;
  bottom:0;
  left:0;
  right:0;
}

.slide-up {
  width:200px;
  height:200px;
  background-color:blue;
  position:absolute;
  top:50%;
  left:50%;
  margin-left:-100px;
  margin-top:-100px;
  z-index:2;
}

the slideUp has z-index of 2 so toggleSlideUp will not be triggered if you click it.

plunkr: http://plnkr.co/edit/Mg4awi23z6AYrXyCdihH

Alternatively

You can use angular.element.bind(document, function(event){ ... }); The event object passed to the callback has a target property you can use compare with the slideUp element.

So in your controller you could have

angular.element(document).bind(document, function(event){
    if(event.target !== slideUpElement) {
        hideSlideUpElement
    }
}):
km6zla
  • 4,787
  • 2
  • 29
  • 51
  • I don't have control over the entire document. My use case is an ownCloud app where I only can control the designated app space. – tanghus Apr 04 '14 at 17:25
  • Which part exactly doesn't fit into your use case? If you are able to create elements on the page and supply css for them you should be able to do this. Forgive me, I am admittedly unfamiliar with ownCloud apps. – km6zla Apr 04 '14 at 17:32
  • ownCloud apps have a div where they render their content. I can only attach the `ng-click` handler to that div inside the document. Any clicks on the main header and navigation wouldn't be caught. – tanghus Apr 04 '14 at 19:20
  • Don't you *need* to catch them to hide your div? Can you use the code at the end of my answer in your controller? – km6zla Apr 04 '14 at 19:46
  • Ah, I didn't notice you had edited and added an "Alternatively" part. Yes, that is basically what is in the [answer](http://stackoverflow.com/a/22854809/373007) suggested by @JeremyWeir – tanghus Apr 05 '14 at 12:24