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.
Так есть ли лучшее решение для этого? Благодаря
Ответы
Ответ 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());