Подтвердить что ты не робот

Angular Нажмите вне события элемента

Я знаю, что есть много вопросов, которые задают подобную вещь. Но никто не решает мою проблему.

Я пытаюсь создать директиву, которая будет выполнять выражение при щелчке мыши за текущим элементом.

Зачем мне нужна эта функция? Я создаю приложение, в этом приложении есть 3 раскрывающегося меню, 5 выпадающих списков (как выбрано). Все это директивы angular. Предположим, что все эти директивы различны. Итак, у нас есть 8 директив. И все они нуждаются в одной и той же функции: при щелчке по элементу нужно скрыть раскрывающееся меню.

У меня есть 2 решения для этого, но у обоих возникла проблема:

Решение A:

app.directive('clickAnywhereButHere', function($document){
  return {
    restrict: 'A',
    link: function(scope, elem, attr, ctrl) {
      elem.bind('click', function(e) {
        // this part keeps it from firing the click on the document.
        e.stopPropagation();
      });
      $document.bind('click', function() {
        // magic here.
        scope.$apply(attr.clickAnywhereButHere);
      })
    }
  }
})

Вот пример решения A: нажмите здесь

Когда вы нажимаете на первый раскрывающийся список, затем работаете, затем нажимаете второй ввод, первый должен скрываться, но нет.

Решение B:

app.directive('clickAnywhereButHere', ['$document', function ($document) {
    directiveDefinitionObject = {
        link: {
            pre: function (scope, element, attrs, controller) { },
            post: function (scope, element, attrs, controller) {
                onClick = function (event) {
                    var isChild = element.has(event.target).length > 0;
                    var isSelf = element[0] == event.target;
                    var isInside = isChild || isSelf;
                    if (!isInside) {
                        scope.$apply(attrs.clickAnywhereButHere)
                    }
                }
                $document.click(onClick)
            }
        }
    }
    return directiveDefinitionObject
}]);

Вот пример для решения B: нажмите здесь

Решение A работает, если на странице есть только одна директива, но не в моем приложении. Поскольку это предотвращает пузыри, так что сначала, когда я нажимаю на dropdown1, покажу dropdown1, затем нажмите dropdown2, нажмите событие, чтобы предотвратить, поэтому dropdown1 все еще показывает, что даже я выхожу за пределы dropdown1.

Решение B работает в моем приложении, которое я использую сейчас. Но проблема в том, что это вызывает проблемы с производительностью. Слишком много событий клика обрабатываются при каждом нажатии на любое приложение. В моем текущем случае существует 8 привязок к событию с документом, поэтому каждый клик выполняет 8 функций. Что вызывает мое приложение очень медленно, особенно в IE8.

Так есть ли лучшее решение для этого? Благодаря

4b9b3361

Ответ 1

Я бы не использовал event.stopPropagation(), поскольку он вызывает именно те проблемы, которые вы видите в решении A. Если возможно, я также прибегаю к размытию и фокусу. Когда вы выпадаете на вход, вы можете закрыть его, когда вход теряет фокус.

Однако обработка событий кликов в документе также не так уж плоха, поэтому, если вы хотите не обрабатывать одно и то же событие click несколько раз, просто отвяжите его от документа, когда он больше не нужен. В дополнение к выражению, которое оценивается при нажатии за пределами раскрывающегося списка, директива должна знать, активна она или нет:

app.directive('clickAnywhereButHere', ['$document', function ($document) {
    return {
        link: function postLink(scope, element, attrs) {
            var onClick = function (event) {
                var isChild = $(element).has(event.target).length > 0;
                var isSelf = element[0] == event.target;
                var isInside = isChild || isSelf;
                if (!isInside) {
                    scope.$apply(attrs.clickAnywhereButHere)
                }
            }
            scope.$watch(attrs.isActive, function(newValue, oldValue) {
                if (newValue !== oldValue && newValue == true) {
                    $document.bind('click', onClick);
                }
                else if (newValue !== oldValue && newValue == false) {
                    $document.unbind('click', onClick);
                }
            });
        }
    };
}]);

При использовании директивы просто укажите другое выражение следующим образом:

<your-dropdown click-anywhere-but-here="close()" is-active="isDropdownOpen()"></your-dropdown>

Я не тестировал вашу функцию onClick. Я предполагаю, что он работает так, как ожидалось. Надеюсь, это поможет.

Ответ 2

Вы должны использовать ngBlur и ngFocus показать или скрыть ваши выпадающие меню. Когда кто-то нажимает на него, он становится сфокусированным, иначе он становится размытым.

Также обратитесь к этому вопросу Как установить фокус на поле ввода? для настройки фокуса в AngularJS.

ИЗМЕНИТЬ: Для каждой директивы (выпадающего меню или списка, давайте назовем ее Y) вам нужно будет отображать ее, когда вы нажимаете на элемент (позволяет называть его X), и вам нужно скрыть его, когда вы нажимаете где-нибудь вне Y (исключая, очевидно, X), Y имеет свойство Yvisisble. Поэтому, когда кто-то нажимает на X (ng-click), установите "isYvisible" , чтобы быть правдой, и установите Focus на Y. Когда кто-то нажимает за пределы Y (ng-blur), вы устанавливаете "isYvisible" равным false, он скрывается. Вам нужно разделить переменную ( "isYvisible" ) между двумя разными элементами/директивами, и вы можете использовать объем контроллера или служб для этого. Существуют и другие альтернативы этому, но это выходит за рамки вопроса.

Ответ 3

Ваше решение A является наиболее правильным, но вы должны добавить еще один параметр в директиву для отслеживания, если он открыт:

link: function(scope, elem, attr, ctrl) {
  elem.bind('click', function(e) {
    // this part keeps it from firing the click on the document.
    if (isOpen) {
      e.stopPropagation();
    }
  });
  $document.bind('click', function() {
    // magic here.
    isOpen = false;
    scope.$apply(attr.clickAnywhereButHere);
  })
}

Ответ 4

post: function ($scope, element, attrs, controller) { 
  element.on("click", function(){
    console.log("in element Click event");
    $scope.onElementClick = true;
    $document.on("click", $scope.onClick);
  });

  $scope.onClick = function (event) {
    if($scope.onElementClick && $scope.open)
    {
      $scope.onElementClick = false;
      return;
    }
    $scope.open = false;
    $scope.$apply(attrs.clickAnywhereButHere)
    $document.off("click", $scope.onClick);
  };
}

Ответ 5

Вот решение, которое я использую (возможно, немного поздний ответ, но, надеюсь, полезно для других, кто проходит через это)

 link: function (scope, element, attr) {

        var clickedOutsite = false;
        var clickedElement = false;

        $(document).mouseup(function (e) {
            clickedElement = false;
            clickedOutsite = false;
        });

        element.on("mousedown", function (e) {

                clickedElement = true;
                if (!clickedOutsite && clickedElement) {
                    scope.$apply(function () {
                    //user clicked the element
                    scope.codeCtrl.elementClicked = true;
                    });
                }

        });

        $(document).mousedown(function (e) {
            clickedOutsite = true;
            if (clickedOutsite && !clickedElement) {
                scope.$apply(function () {
                    //user clicked outsite the element 
                    scope.codeCtrl.elementClicked = false;
                });
            }
        });
    }

Ответ 6

Вот решение, которое я использовал, которому требуется только событие click (доступно как $event в директиве ngClick). Мне нужно меню с элементами, которые при нажатии:

  • переключить отображение подменю
  • скрыть любое другое подменю, если оно было отображено
  • скрыть подменю, если на экране появился клик.

Этот код устанавливает класс "active" в элементе меню, чтобы его можно было отображать или скрывать подменю

// this could also be inside a directive link function.
// each menu element will contain data-ng-click="onMenuItemClick($event)".
// $event is the javascript event object made available by ng-click.
$scope.onMenuItemClick = function(menuElementEvent) {
    var menuElement = menuElementEvent.currentTarget,
        clickedElement = menuElementEvent.target,
        offRootElementClick; // where we will save angular event unbinding function

    if (menuElement !== clickedElement) {
        return;
    }

    if (menuElement.classList.contains('active')) {
        menuElement.classList.remove('active');
        // if we were listening for outside clicks, stop
        offRootElementClick && offRootElementClick();
        offRootElementClick = undefined;
    } else {
        menuElement.classList.add('active');
        // listen for any click inside rootElement.
        // angular bind returns a function that can be used to stop listening
        // I used $rootElement, but use $document if your angular app is nested in the document
        offRootElementClick = $rootElement.bind('click', function(rootElementEvent) {
            var anyClickedElement = rootElementEvent.target;
            // if it not a child of the menuElement, close the submenu
            if(!menuElement.contains(anyClickedElement)) {
                menuElement.classList.remove('active');
                // and stop outside listenting
                offRootElementClick && offRootElementClick();
                offOutsideClick = undefined;
            }
        });
    }
}

Ответ 7

Ответ на

@lex82 хорош и составляет основу этого ответа, но мой отличается несколькими способами:

  • Его в TypeScript
  • Он удаляет привязку кликов при уничтожении области, что означает, что вам не нужно управлять привязкой кликов отдельно с помощью свойства
  • Тайм-аут гарантирует, что если объект с click-out on создается с помощью события мыши, то то же самое событие мыши фактически не запускает механизм закрытия

    export interface IClickOutDirectiveScope extends angular.IScope {
    
        clickOut: Function;
    }
    
    export class ClickOutDirective implements angular.IDirective {
    
        public restrict = "A";
        public scope = {
            clickOut: "&"
        }
    
        public link: ($scope: IClickOutDirectiveScope, element: angular.IAugmentedJQuery, attrs: angular.IAttributes) => void;
    
        constructor($timeout: angular.ITimeoutService, $document: angular.IDocumentService) {
    
            ClickOutDirective.prototype.link = ($scope: IClickOutDirectiveScope, $element: angular.IAugmentedJQuery, attrs: ng.IAttributes) => {
    
                var onClick = (event: JQueryEventObject) => {
                    var isChild = $element[0].contains(event.target);
                    var isSelf = $element[0] === event.target;
                    var isInside = isChild || isSelf;
    
                    if (!isInside) {
                        if ($scope.clickOut) {
                            $scope.$apply(() => {
                                $scope.clickOut();
                            });
                        }
                    }
                }
    
                $timeout(() => {
                    $document.bind("click", onClick);
                }, 500);
    
                $scope.$on("$destroy", () => {
                    $document.unbind("click", onClick);
                });
            }
        }
    
        static factory(): ng.IDirectiveFactory {
            const directive = ($timeout: angular.ITimeoutService, $document: angular.IDocumentService) => new ClickOutDirective($timeout, $document);
    
            directive.$inject = ["$timeout", "$document"];
    
            return directive;
        }
    }
    
    angular.module("app.directives")
        .directive("clickOut", ClickOutDirective.factory());