javascript 如何使用 AngularJS 处理文档点击并通知其他控制器?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/19602482/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to handle document click and notify other controllers using AngularJS?
提问by Vinod
I have created a horizontal drop down menu using AngularJS.
我使用 AngularJS 创建了一个水平下拉菜单。
The menu section is managed by an angular controller called menuController. Standard menu behavior is implemented, so that on hover main menu item gets highlighted unless it is disabled. On clicking the main menu item, the sub menu toggles. If Sub menu is in a open state, I want it to go away when user clicks anywhere else on the document. I tried to create a directive to listen for document click event but not sure on how to notify menu-controller about it. How should I implement this scenario in a AngularJS way?
菜单部分由名为 menuController 的角度控制器管理。实现了标准菜单行为,因此在悬停时主菜单项会突出显示,除非它被禁用。单击主菜单项时,子菜单会切换。如果子菜单处于打开状态,我希望它在用户单击文档上的任何其他地方时消失。我试图创建一个指令来监听文档点击事件,但不确定如何通知菜单控制器。我应该如何以 AngularJS 的方式实现这个场景?
Partially working Original Plunkwithout document click handling mechanism.
部分工作的原始 Plunk没有文档点击处理机制。
UPDATE:
更新:
Based on answered suggestion, I went with Brodcast approach and updated the script to reflect my latest changes. It is working as per my expectation. I made the globalController $broadcast a message and menuController subscribe to that message.
根据回答的建议,我采用了 Brodcast 方法并更新了脚本以反映我的最新更改。它按我的期望工作。我让 globalController $broadcast 一条消息和 menuController 订阅该消息。
UPDATE 2:Modified code to inject global events definition data.
更新 2:修改代码以注入全局事件定义数据。
var eventDefs = (function() {
return {
common_changenotification_on_document_click: 'common.changenotification.on.document.click'
};
}());
var changeNotificationApp = angular.module('changeNotificationApp', []);
changeNotificationApp.value('appEvents', eventDefs);
changeNotificationApp.directive("onGlobalClick", ['$document', '$parse',
function($document, $parse) {
return {
restrict: 'A',
link: function($scope, $element, $attributes) {
var scopeExpression = $attributes.onGlobalClick;
var invoker = $parse(scopeExpression);
$document.on("click",
function(event) {
$scope.$apply(function() {
invoker($scope, {
$event: event
});
});
}
);
}
};
}
]);
changeNotificationApp.controller("globalController", ['$scope', 'appEvents',
function($scope, appEvents) {
$scope.handleClick = function(event) {
$scope.$broadcast(appEvents.common_changenotification_on_document_click, {
target: event.target
});
};
}
]);
//menu-controller.js
changeNotificationApp.controller('menuController', ['$scope', '$window', 'appEvents',
function($scope, $window, appEvents) {
$scope.IsLocalMenuClicked = false;
$scope.menu = [{
Name: "INTEGRATION",
Tag: "integration",
IsDisabled: false,
IsSelected: false,
SubMenu: [{
Name: "SRC Messages",
Tag: "ncs-notifications",
IsDisabled: false,
AspNetMvcController: "SearchSRCMessages"
}, {
Name: "Target Messages",
Tag: "advisor-notifications",
IsDisabled: false,
AspNetMvcController: "SearchTaregtMessages"
}]
}, {
Name: "AUDITING",
Tag: "auditing",
IsDisabled: true,
IsSelected: false,
SubMenu: []
}];
$scope.appInfo = {
Version: "1.0.0.0",
User: "VB",
Server: "azzcvy0623401v",
IsSelected: false
};
var resetMenu = function() {
angular.forEach($scope.menu, function(item) {
item.IsSelected = false;
});
$scope.appInfo.IsSelected = false;
};
$scope.toggleDropDownMenu = function(menuItem) {
var currentDropDownState = menuItem.IsSelected;
resetMenu($scope.menu, $scope.appInfo);
menuItem.IsSelected = !currentDropDownState;
$scope.IsLocalMenuClicked = true;
};
$scope.loadPage = function(menuItem) {
if (menuItem.AspNetMvcController)
$window.location.href = menuItem.AspNetMvcController;
};
$scope.$on(appEvents.common_changenotification_on_document_click,
function(event, data) {
if (!$scope.IsLocalMenuClicked)
resetMenu($scope.menu, $scope.appInfo);
$scope.IsLocalMenuClicked = false;
});
}
]);
UPDATE 3:Modified code in previous implementation to fix a bug where document click fires multiple times. Almost similar approach, but this time, if any one clicks again anywhere on the menu, the click is ignored. Please refer to the New Working Plunkfor full code example
更新 3:修改了先前实现中的代码以修复多次触发文档单击的错误。几乎类似的方法,但是这一次,如果有人再次单击菜单上的任何位置,则单击将被忽略。完整代码示例请参考New Working Plunk
changeNotificationApp.directive("onGlobalClick", ['$document', '$parse',
function ($document, $parse) {
return {
restrict: 'A',
link: function ($scope, $element, $attributes) {
var scopeExpression = $attributes.onGlobalClick;
var invoker = $parse(scopeExpression);
$document.on("click",
function (event) {
var isClickedElementIsChildOfThisElement = $element.find(event.target).length > 0;
if (isClickedElementIsChildOfThisElement) return;
$scope.$apply(function () {
invoker($scope, {
$event: event
});
});
}
);
}
};
}
]);
UPDATE 4:Implemented another alternate option. Please refer to the Option 2 Plunkfor full code example
更新 4:实施了另一个替代选项。请参阅Option 2 Plunk以获取完整代码示例
var eventDefs = (function () {
return {
on_click_anywhere: 'common.changenotification.on.document.click'
};
}());
var changeNotificationApp = angular.module('changeNotificationApp', []);
changeNotificationApp.value('appEvents', eventDefs);
changeNotificationApp.directive("onClickAnywhere", ['$window', 'appEvents',
function($window, appEvents) {
return {
link: function($scope, $element) {
angular.element($window).on('click', function(e) {
// Namespacing events with name of directive + event to avoid collisions
$scope.$broadcast(appEvents.on_click_anywhere, e.target);
});
}
};
}
]);
//menu-controller.js
changeNotificationApp.controller('menuController', ['$scope', '$window', 'appEvents', '$element',
function ($scope, $window, appEvents, $element) {
$scope.menu = [
{
Name: "INTEGRATION",
Tag: "integration",
IsDisabled: false,
IsSelected: false,
SubMenu: [
{
Name: "SRC Messages",
Tag: "ncs-notifications",
IsDisabled: false,
AspNetMvcController: "SearchSRCMessages"
},
{
Name: "Target Messages",
Tag: "advisor-notifications",
IsDisabled: false,
AspNetMvcController: "SearchTaregtMessages"
}
]
},
{
Name: "AUDITING",
Tag: "auditing",
IsDisabled: true,
IsSelected: false,
SubMenu: []
}
];
$scope.appInfo = {
Version: "1.0.0.0",
User: "VB",
Server: "azzcvy0623401v",
IsSelected: false
};
var resetMenu = function () {
angular.forEach($scope.menu, function (item) {
item.IsSelected = false;
});
$scope.appInfo.IsSelected = false;
};
$scope.toggleDropDownMenu = function (menuItem) {
var currentDropDownState = menuItem.IsSelected;
resetMenu($scope.menu, $scope.appInfo);
menuItem.IsSelected = !currentDropDownState;
};
$scope.loadPage = function (menuItem) {
if (menuItem.AspNetMvcController)
$window.location.href = menuItem.AspNetMvcController;
};
$scope.$on(appEvents.on_click_anywhere, function(event, targetElement) {
var isClickedElementIsChildOfThisElement = $element.find(targetElement).length > 0;
if (isClickedElementIsChildOfThisElement) return;
$scope.$apply(function(){
resetMenu($scope.menu, $scope.appInfo);
});
});
}
]);
回答by tasseKATT
You can simplify the directive into something like this:
您可以将指令简化为如下所示:
changeNotificationApp.directive('onDocumentClick', ['$document',
function($document) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
var onClick = function() {
scope.$apply(function() {
scope.$eval(attrs.onDocumentClick);
});
};
$document.on('click', onClick);
scope.$on('$destroy', function() {
$document.off('click', onClick);
});
}
};
}
]);
And then pass a function from the menuController to it:
然后从 menuController 传递一个函数给它:
<section class="local-nav" ng-controller="menuController" on-document-click="someFunction()">
No need for the globalController this way.
不需要以这种方式使用 globalController。
If you want to keep the globalController and handle it from there, you can:
如果您想保留 globalController 并从那里处理它,您可以:
1.) Make the menu into a service and then inject it into all controllers that need to be able to control it.
1.) 将菜单变成一个服务,然后将它注入到所有需要能够控制它的控制器中。
2.) Broadcast an event from globalController and listen for it in menuController.
2.) 从 globalController 广播一个事件并在 menuController 中监听它。
Specific alternative solution: You can turn the directive into a 'on-outside-element-click' and use it like this:
具体的替代解决方案:您可以将指令转换为“on-outside-element-click”并像这样使用它:
<ul on-outside-element-click="closeMenus()">
The directive looks like this and will only call closeMenus()
if you click outside the ul
:
该指令看起来像这样,只有closeMenus()
在您在 之外单击时才会调用ul
:
changeNotificationApp.directive('onOutsideElementClick', ['$document',
function($document) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
element.on('click', function(e) {
e.stopPropagation();
});
var onClick = function() {
scope.$apply(function() {
scope.$eval(attrs.onOutsideElementClick);
});
};
$document.on('click', onClick);
scope.$on('$destroy', function() {
$document.off('click', onClick);
});
}
};
}
]);
Working Plunker: http://plnkr.co/edit/zVo0fL2wOCQb3eAUx44U?p=preview
工作Plunker:http://plnkr.co/edit/zVo0fL2wOCQb3eAUx44U?p=preview
回答by Chandermani
Well you have done things well. If you apply the same directive over the menuController
好吧,你已经把事情做好了。如果您将相同的指令应用于menuController
<section class="local-nav" ng-controller="menuController" on-global-click="handleClick($event)>
and have the click handler defined in your menuController
you are all set to go.
并在您的中定义点击处理程序,您menuController
就可以开始了。
I don't think there is any harm in having multiple handlers for the event on document. So where ever you define this directive that element can respond to the global document click event.
我认为为文档上的事件设置多个处理程序没有任何害处。因此,无论您在何处定义此指令,该元素都可以响应全局文档单击事件。
Update: As i tested this, it leads to another problem where this method get called, where ever you click on the page. You need a mechanism to differentiate now.
更新:当我对此进行测试时,它会导致另一个问题,即在您单击页面的任何位置调用此方法。您现在需要一种机制来区分。