modal - Detenga la propagación de ng-click subyacente dentro de un evento de clic de jQuery
ng-bootstrap angular 6 (3)
La directiva desplegable vincula el evento de clic en el documento, pero al hacer clic en la fila, el evento comienza a propagarse desde el elemento de destino hacia el nodo del documento raíz ( td
-> tr
-> table
-> document
).
Por eso es que siempre se llama a su controlador de ng-click
, que tiene en su fila, a pesar de que la directiva está "deteniendo" la burbuja en el clic del documento.
La solución es utilizar el indicador useCapture al agregar el controlador de clic para el documento.
Después de iniciar la captura, todos los eventos del tipo especificado se enviarán al oyente registrado antes de enviarse a cualquier EventTarget debajo de él en el árbol DOM. mdn
Ahora, para indicar a la directiva desplegable que use su propio controlador, debe cambiar la fuente de la directiva. Pero es una directiva de terceros, y probablemente no quiera hacer eso, por razones de mantenimiento.
Aquí es donde entra en acción el poderoso $ decorator angular. Puede usar el $ decorator para cambiar la fuente del módulo de terceros sobre la marcha, sin tocar realmente los archivos de origen reales.
Por lo tanto, con el decorador en su lugar y el controlador de eventos personalizado en el nodo de documento, esta es la forma en que puede hacer que el menú desplegable se comporte:
var myApp = angular.module(''myApp'', []);
/**
* Original dropdownToggle directive from ui-bootstrap.
* Nothing changed here.
*/
myApp.directive(''dropdownToggle'', [''$document'', ''$location'', function ($document, $location) {
var openElement = null,
closeMenu = angular.noop;
return {
restrict: ''CA'',
link: function(scope, element, attrs) {
scope.$watch(''$location.path'', function() { closeMenu(); });
element.parent().bind(''click'', function() { closeMenu(); });
element.bind(''click'', function (event) {
var elementWasOpen = (element === openElement);
event.preventDefault();
event.stopPropagation();
if (!!openElement) {
closeMenu();
}
if (!elementWasOpen && !element.hasClass(''disabled'') && !element.prop(''disabled'')) {
element.parent().addClass(''open'');
openElement = element;
closeMenu = function (event) {
if (event) {
event.preventDefault();
event.stopPropagation();
}
$document.unbind(''click'', closeMenu);
element.parent().removeClass(''open'');
closeMenu = angular.noop;
openElement = null;
};
$document.bind(''click'', closeMenu); /* <--- CAUSE OF ALL PROBLEMS ----- */
}
});
}
};
}]);
/**
* This is were we decorate the dropdownToggle directive
* in order to change the way the document click handler works
*/
myApp.config(function($provide){
''use strict'';
$provide.decorator(''dropdownToggleDirective'', [
''$delegate'',
''$document'',
function ($delegate, $document) {
var directive = $delegate[0];
var openElement = null;
var closeMenu = angular.noop;
function handler(e){
var elm = angular.element(e.target);
if(!elm.parents(''.dropdown-menu'').length){
e.stopPropagation();
e.preventDefault();
}
closeMenu();
// After closing the menu, we remove the all-seeing handler
// to allow the application click events to work nnormally
$document[0].removeEventListener(''click'', handler, true);
}
directive.compile = function(){
return function(scope, element) {
scope.$watch(''$location.path'', closeMenu);
element.parent().bind(''click'', closeMenu);
element.bind(''click'', function (event) {
var elementWasOpen = (element === openElement);
event.preventDefault();
event.stopPropagation();
if (!!openElement) {
closeMenu();
}
if (!elementWasOpen && !element.hasClass(''disabled'') && !element.prop(''disabled'')) {
element.parent().addClass(''open'');
openElement = element;
closeMenu = function (event) {
if (event) {
event.preventDefault();
event.stopPropagation();
}
$document.unbind(''click'', closeMenu);
element.parent().removeClass(''open'');
closeMenu = angular.noop;
openElement = null;
};
// We attach the click handler by specifying the third "useCapture" parameter as true
$document[0].addEventListener(''click'', handler, true);
}
});
};
};
return $delegate;
}
]);
});
ACTUALIZAR:
Tenga en cuenta que el controlador personalizado actualizado evitará la propagación a menos que el elemento de destino sea la opción desplegable real. Esto solucionará el problema donde el evento de clic se estaba impidiendo incluso al hacer clic en las opciones desplegables.
Esto todavía no evitará que el evento se reduzca a la fila (desde la opción desplegable), pero esto es algo que no está relacionado de ninguna manera con la directiva desplegable. De todos modos, para evitar este burbujeo, puede pasar el objeto $event
a la función de expresión ng-click
y usar ese objeto para detener la uniformidad hasta la fila de la tabla:
<div ng-controller="DropdownCtrl">
<table>
<tr ng-click="clicked(''row'')">
<td>
<div class="btn-group">
<button type="button" class="btn btn-default dropdown-toggle">
Action <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li ng-repeat="choice in items">
<a ng-click="clicked(''link element'', $event)">{{choice}}</a>
</li>
</ul>
</div>
</td>
</tr>
</table>
</div>
function DropdownCtrl($scope) {
$scope.items = [
"Action",
"Another action",
"Something else here"
];
$scope.clicked = function(what, event) {
alert(what + '' clicked'');
if(event){
event.stopPropagation();
event.preventDefault();
}
}
}
Un dropdown
Bootstrap de Twitter está anidado dentro de un tr
. El tr
se puede hacer clic a través de ng-click
. Al hacer clic en cualquier lugar de la página, se contraerá el menú desplegable. Ese comportamiento se define en una directiva a través de $document.bind(''click'', closeMenu)
.
Entonces, cuando se abre el menú y el usuario hace clic en una fila, quiero que el menú se cierre (como lo hace) Y quiero evitar el evento de clic en la fila.
JSFiddle: http://jsfiddle.net/LMc2f/1/
Directiva JSFiddle + en línea : http://jsfiddle.net/9DM8U/1/
Código relevante de ui-bootstrap-tpls-0.10.0.js:
angular.module(''ui.bootstrap.dropdownToggle'', []).directive(''dropdownToggle'', [''$document'', ''$location'', function ($document, $location) {
var openElement = null,
closeMenu = angular.noop;
return {
restrict: ''CA'',
link: function(scope, element, attrs) {
scope.$watch(''$location.path'', function() { closeMenu(); });
element.parent().bind(''click'', function() { closeMenu(); });
element.bind(''click'', function (event) {
var elementWasOpen = (element === openElement);
event.preventDefault();
event.stopPropagation();
if (!!openElement) {
closeMenu();
}
if (!elementWasOpen && !element.hasClass(''disabled'') && !element.prop(''disabled'')) {
element.parent().addClass(''open'');
openElement = element;
closeMenu = function (event) {
if (event) {
event.preventDefault();
event.stopPropagation();
}
$document.unbind(''click'', closeMenu);
element.parent().removeClass(''open'');
closeMenu = angular.noop;
openElement = null;
};
$document.bind(''click'', closeMenu);
}
});
}
};
}]);
No puedo encontrar la manera de detener el evento subyacente ng-click
dentro de closeMenu
.
NOTA: No puedo encontrar una manera de acceder a $event
por lo que no he podido probar $event.stopPropagation()
.
Me inclino por llamar a $event.stopPropagation()
desde la propia plantilla. La lógica relacionada con los eventos probablemente pertenece allí. También debería facilitar las pruebas unitarias. Alguien que esté mirando la plantilla también sabrá que el evento no se disparó sin mirar el controlador subyacente.
<div ng-click="parentHandler()">
<div ng-click="childHandler(); $event.stopPropagation()"></div>
</div>
debe pasar el evento de ng-click en la línea 2 de su violín, y luego haga preventDefault y stopPropigation en ese objeto dentro de su método do
<tr ng-click="do($event)">