Clique em todos os lugares, mas aqui evento

Estava imaginando como eu implementaria um evento “Clique em todos os lugares, mas neste elemento”.

Eu tenho algo que você pode comparar a uma lista de arquivos em um explorador de arquivos. Você pode selecionar certos elementos, mas se você clicar fora do controlador do elemento, ele precisará desmarcar tudo.

insira a descrição da imagem aqui

Adicionado uma captura de canvas para torná-lo mais claro. Então, o que eu quero fazer é que, se eu clicar em qualquer lugar, mas nos elementos de linguagem, ele deve triggersr um evento.

Atualizar

Para esclarecer, não estou perguntando como posso fazer isso com jQuery.

EDIT: Houve alguns problemas nesta resposta antiga e antiga.

* Também: Marking Community Wiki (sem pontos para mim) porque erros

  1. N pede N usos da diretiva. Isso provavelmente não é desejável para usos dentro do mesmo escopo com expressões correspondentes.

  2. NADA estava USANDO OS MANIPULADORES DO EVENTO !!!! MAU! MAU! MAU!

Então, estou atualizando esta resposta. Espero que isso não tenha causado muitos problemas.

Resposta atualizada

Aqui está um novo plunker com esses problemas corrigidos … há provavelmente outras coisas que os desenvolvedores de aplicativos individuais irão encontrar. Este é apenas um exemplo de como lidar com esse problema.

app.factory('clickAnywhereButHereService', function($document){ var tracker = []; return function($scope, expr) { var i, t, len; for(i = 0, len = tracker.length; i < len; i++) { t = tracker[i]; if(t.expr === expr && t.scope === $scope) { return t; } } var handler = function() { $scope.$apply(expr); }; $document.on('click', handler); // IMPORTANT! Tear down this event handler when the scope is destroyed. $scope.$on('$destroy', function(){ $document.off('click', handler); }); t = { scope: $scope, expr: expr }; tracker.push(t); return t; }; }); app.directive('clickAnywhereButHere', function($document, clickAnywhereButHereService){ return { restrict: 'A', link: function(scope, elem, attr, ctrl) { var handler = function(e) { e.stopPropagation(); }; elem.on('click', handler); scope.$on('$destroy', function(){ elem.off('click', handler); }); clickAnywhereButHereService(scope, attr.clickAnywhereButHere); } }; }); 

Resposta original (com correções para desassembly de manipuladores de events)

Você estava perto com a única resposta que encontrou, mas eu juntei um plunk para você mostrar o que estava faltando.

 app.directive('clickAnywhereButHere', function($document){ return { restrict: 'A', link: function(scope, elem, attr, ctrl) { var elemClickHandler = function(e) { e.stopPropagation(); }; var docClickHandler = function() { scope.$apply(attr.clickAnywhereButHere); }; elem.on('click', elemClickHandler); $document.on('click', docClickHandler); // teardown the event handlers when the scope is destroyed. scope.$on('$destroy', function() { elem.off('click', elemClickHandler); $document.off('click', docClickHandler); }); } } }) 

HTML

 Don't Click Me! 

O problema com a resposta atual aceita é que se você usar a diretiva várias vezes, todo elemento DOM que tiver a diretiva anexada evitará o borbulhamento (portanto, se você tiver em dois elementos e clicar em um deles, os retornos de chamada de ambos serão bloqueado).

EDIT – evite jQuery, clean up – Defina uma function em seu escopo e passe-a diretamente para esta diretiva (sem parênteses), e o evento será passado para ela quando chamado.

 app.directive('clickAnywhereButHere', function($document, $parse) { return { restrict: 'A', scope: { callback : '=clickAnywhereButHere' }, link: function(scope, element, attr, ctrl) { var handler = function(event) { if (!element[0].contains(event.target)) { scope.callback(event); } }; $document.on('click', handler); scope.$on('$destroy', function() { $document.off('click', handler); }); } } }); 

Uso em HTML

  

Uso no Controlador

  $scope.myFunction = function (event) { ... } 

Observe que você pode precisar scope.callback(event) com o scope.$apply()

Se você tiver muitos elementos que precisam desta diretiva, aqui está outra solução otimizada para desempenho. (Exemplo de uma lista com mais de 100 linhas, cada uma com esta diretiva)

Isso sempre conterá apenas um ouvinte de $ document

 angular.module('app').directive('clickElsewhere', ['$document', function ($document) { return { link: function postLink(scope, element, attr) { var elsewhere = true; element.on('click', function(e) { elsewhere = false; $document.off('click', clickElsewhere); $document.on('click', clickElsewhere); }); var clickElsewhere = function() { if (elsewhere) { scope.$apply(attr.clickElsewhere); $document.off('click', clickElsewhere); } elsewhere = true; }; } }; }]); 

O problema com a solução do Max Bates é que todas as diretivas estão adicionando um ouvinte para o $ document.on (‘click’, function (…)); evento que causa problemas de desempenho.

Problema com resposta aceita tem Max Bates allready afirmou.

Encontrei a resposta nesta postagem do blog .

Directiva:

 app.directive('documentClick', function ($document, $parse) { var linkFunction = function ($scope, $element, $attributes) { var scopeExpression = $attributes.documentClick; var invoker = $parse(scopeExpression); $document.on('click', function (event) { $scope.$apply(function () { invoker($scope, { $event: event }); }); }); }; return linkFunction; }); 

Controlador:

 app.controller('PageCtrl', function ($scope) { $scope.click = function (e) { if (!$(e.target).is('.language')) { //do stuff } }; }); 

Visão:

  

Aqui está uma variante baseada na solução de Max, mas mais natural em termos de diretivas de events angulares padrão:

 app.directive('clickOut', function($document) { return { restrict: 'A', scope: { clickOut: '&' }, link: function (scope, element) { var handler = function(event) { if (!element[0].contains(event.target)) { scope.$apply(function () { scope.clickOut({ $event : event }); }); } }; $document.on('click', handler); scope.$on('$destroy', function() { $document.off('click', handler); }); } }; }); 

Uso

 

Evento de clique de passagem

 

A maneira mais simples é verificar o escopo do elemento: $ id e o clickScope. $ Id (o escopo de destino do evento click. $ Id).

 link: function(scope, element, attrs) { //close element when mouse click outside var documentClickHandler = function(event) { var clickScope = angular.element(event.target).scope(); if (clickScope.$id != scope.$id) { //clickScope.$parent is for clicking on the directive children scope if(clickScope.$parent === null || clickScope.$parent.$id != scope.$id){ //Click everywhere but on this element //Do anything you want here, like close your element; } } }; $document.on('click', documentClickHandler); scope.$on('$destroy', function() { $document.off('click', documentClickHandler); }); } 

Benefício desta solução:

  1. Você só precisa de uma binding para $(document) . Outro disparo de events dependeria do $emit de events de escopo.
  2. Você poderia usar ambas as expressões click-elsewhere="show=false" e click-elsewhere="fn()" , graças a $parse .

Código:

 // broadcast click event within AppCtrl app.controller('AppCtrl', function($rootScope) { $(document).on('click', function(e) { // use $emit so the event stays inside $rootScope $rootScope.$emit('click', {target: e.target}); }; }; app.directive('clickElsewhere', function($rootScope) { return { restrict: 'A', compile: function($element, attr) { // store fn in compile so it only execute once var fn = $parse(attr['clickElsewhere']); return function(scope, element) { var offEvent = $rootScope.$on('click', function(event, target) { if ( (element.find($(target)).length) || element.is($(target)) ) return; scope.$apply(function() { fn(scope, {$event: event}); }); }); scope.$on('$destroy', offEvent); }; } }; 

Uso em HTML:

  1. click-elsewhere="fn()"
  2. click-elsewhere="show=false"

é possivelmente um pouco fora do contexto, mas você pode classificar (com por exemplo “selecionado”) todos os itens selecionados e quando um usuário clica no elemento ‘não clique aqui’, você pode desclassificar todos os itens atualmente classificados como “selecionados” classifique o elemento específico …