我如何使用 angularJS 将可拖动指令应用于 bootstrap 模态?
How can i apply a draggable directive to bootstrap modal using angularJS?
我在我的 Angular 应用程序中使用 bootstrap 模式,它工作正常。我需要让它可以拖动和调整大小,所以我定义了一个指令。现在的问题是它被应用于模态 window 内的内容,因此模态 window 变得透明。
打开 window 时如何将可拖动指令分配给模态 window?
这是代码,
HTML:
<div ng-controller="CustomWidgetCtrl">
<div class="box-header-btns pull-right" style="top:10px" >
<a title="settings" ng-click="openSettings(widget)"><i class="glyphicon glyphicon-cog"></i></a>
</div>
</div>
App.js:
var routerApp = angular.module('DiginRt', ['ui.bootstrap','ngRoute']);
routerApp.controller('CustomWidgetCtrl', ['$scope', '$modal',
function($scope, $modal) {
$scope.openSettings = function(widget) {
$modal.open({
scope: $scope,
templateUrl: 'chart_settings.html',
controller: 'chartSettingsCtrl',
resolve: {
widget: function() {
return widget;
}
}
});
};
}
])
图表设置是另一个 HTML 页面。这是我的 Draggable 指令。
更新:
我已将问题更新为 Plunker
问题:
我找不到将指令添加到 ui-bootstrap
打开的模态的方法,因为它用模态对话框包装了模板。
所以我所做的是使用以下代码将事件设置为拖动到模态对话框本身(而不是指令)。
我知道在指令内将事件添加到另一个元素不是最佳做法,但在这种情况下也不是坏做法,在这种情况下您不能直接将指令设置到元素。
这样做的原因是因为 ui-bootstrap 没有提供向 modal.open
函数
上的 modal-dialog
添加指令的方法
这是要放在指令开头的代码:
element= angular.element(document.getElementsByClassName("modal-dialog"));
我对@Naeem_Shaikh的回答投了赞成票,这基本上是对标准 angular 指令的改进。
但是,标准 angular 可拖动指令还有另一个问题,我已经解决了。
标准指令对整个对话框施加了可拖动性。一方面,这是我们想要的。我们想拖动整个对话框。但这有一个不幸的副作用,即在对话框中的各种编辑字段中单击不起作用:默认行为被阻止!不知何故,按钮已编码为 bootstrap 以克服此问题,但文本编辑字段没有。我猜作者没有考虑我的用例。但对话框不仅仅是按钮!
这很棘手,因为需要拖动的是整个对话框,但您只想通过在 header "hotspot" 中单击来启动拖动。换句话说,发起拖动的热点是要拖动区域的子集。
Naeem 的修复使我能够使其正常工作,以便仅在 header 中单击即可启动拖动。没有他的修复,坐标转换变得混乱。
function clickedWithinHeader(event) {
var target = event.currentTarget;
var hotspot = null;
var hotspots = target.getElementsByClassName("modal-header");
if (hotspots.length > 0) {
hotspot = hotspots.item(0);
}
if (hotspot !== null) {
var eY = event.clientY;
var tOT = target.offsetTop;
var y = eY - tOT;
var hH = hotspot.offsetHeight;
// since the header occupies the full width across the top
// no need to check X. Note that this assumes the header
// is on the top, which should be a safe assumption
var within = (y <= hH);
return within;
} else {
return true;
}
}
// Draggable directive from: http://docs.angularjs.org/guide/compiler
// Modified so that only clicks in the dialog header trigger drag behavior.
// Otherwise clicking on dialog widgets did not give them focus.
angular.module('drag', []).directive('draggable', function($document) {
"use strict";
return function(scope, element) {
var startX = 0, startY = 0, x = 0, y = 0;
element= angular.element(document.getElementsByClassName("modal-dialog"));
element.css({
position : 'fixed',
cursor : 'move',
});
element.on('mousedown', function(event) {
// // OK, where did they touch? Only want to do this
// // when they clicked on the header.
if (!clickedWithinHeader(event)) {
return;
}
// Prevent default dragging of selected content
event.preventDefault();
...
请注意,clickedWithinHeader() 逻辑仅适用于 Naeem 的改进。 可能有更好的方法,但这个方法有效。只有在 header 中单击才会启动拖动,而在其他地方单击会执行它们应该执行的操作。
然而,这并不是全部答案,因为标准指令还在整个对话框上强加了移动光标,这非常令人不安,即使,或者尤其是如果您不能拖动它出现的位置。
从指令中的 element.css 中删除:
element.css({
position : 'fixed',
});
并使用 CSS
将移动光标仅绑定到模式 header
.modal-header
{
cursor: move;
}
提供完整的解决方案。
我将现有解决方案更改为屏幕上的多个模式。
Html:
....
或
... // 模态页眉、正文、页脚。
JS调用模式:
var modalInstance = $uibModal.open({
动画:真实,
控制器:'ModalAndamentoController',
控制器为:'vm',
窗口类:'center-modal',
尺码:'lg',
模板网址:'modalAndamento.html'
});
指令 JS:
(功能 () {
'use strict';
angular
.module('app')
.directive('draggable', draggableDirective);
/** @ngInject */
函数 draggableDirective($document) {
//busca pelo elemento
var serachElement = function (parentElement, element) {
//se o elemento pai corrente é igual ao elemento, então este foi encontrado
if (parentElement == element[0]) {
return true;
}
//aprofunda mais um nível na árvore procurando pelo elemento
var i = 0;
for (i = 0; i < parentElement.childNodes.length; i++) {
return serachElement(parentElement.childNodes[i], element);
}
return false;
};
//recupera o elemento referente ao dialog
var getDialogFromElement = function (element) {
//recupera todos os dialogs da tela
var dialogs = document.getElementsByClassName("modal-dialog")
//se tiver apenas um, então esse é o dialog procurado e o mesmo é retornado
if (dialogs.length == 1) {
return angular.element(dialogs[0]);
}
//senão, varre todos os dialogs, procurando por aquele que contém o elemento corrente na sua árvore de elementos
var i = 0;
for (i = 0; i < dialogs.length; i++) {
//se encontrar o elemento correte, então esse é o dialog procurado
if (serachElement(dialogs[i], element)) {
return angular.element(dialogs[i]);
}
}
};
//movimenta o dialog correspondente ao elemento informado (element)
//O elemento que chega aqui é aquele que contém o atributo draggable
return function (scope, element) {
var startX = 0,
startY = 0,
x = 0,
y = 0;
//recupera o dialog correspondente ao element corrente
element = getDialogFromElement(element);
//coloca o cursor de movimento no header
var header = angular.element(document.getElementsByClassName("modal-header"));
header.css({
cursor: 'move'
});
element.on('mousedown', function (event) {
// Prevent default dragging of selected content
//event.preventDefault();
startX = event.screenX - x;
startY = event.screenY - y;
$document.on('mousemove', mousemove);
$document.on('mouseup', mouseup);
});
function mousemove(event) {
y = event.screenY - startY;
x = event.screenX - startX;
element.css({
top: y + 'px',
left: x + 'px'
});
}
function mouseup() {
$document.unbind('mousemove', mousemove);
$document.unbind('mouseup', mouseup);
}
};
}
})();
我在这里合并了各种代码片段并提出了一个更简单的解决方案。这个不依赖于检测点击事件是否发生在modal-header,因为mousedown事件专门绑定到header。
此解决方案也不依赖于在整个 DOM 中搜索 modal-dialog class,因为它会搜索 parents 对于 modal-dialog 元素。这将允许您在屏幕上有多个对话框,并且只有一个可以拖动。
我还创建了一个 gist 进行检查以防止对话框移出可见范围。
(function (angular) {
"use strict";
angular.module('MyModule')
.directive('modalDraggable', ['$document', modalDraggable]);
function modalDraggable($document) {
return function (scope, element) {
var startX = 0,
startY = 0,
x = 0,
y = 0;
var draggable = angular.element(element.parents('.modal-dialog')[0]);
draggable.find('.modal-header')
.css('cursor', 'move')
.on('mousedown', function (event) {
// Prevent default dragging of selected content
event.preventDefault();
startX = event.screenX - x;
startY = event.screenY - y;
$document.on('mousemove', mousemove);
$document.on('mouseup', mouseup);
});
function mousemove(event) {
y = event.screenY - startY;
x = event.screenX - startX;
draggable.css({
top: y + 'px',
left: x + 'px'
});
}
function mouseup() {
$document.unbind('mousemove', mousemove);
$document.unbind('mouseup', mouseup);
}
};
}
}(window.angular));
我在我的 Angular 应用程序中使用 bootstrap 模式,它工作正常。我需要让它可以拖动和调整大小,所以我定义了一个指令。现在的问题是它被应用于模态 window 内的内容,因此模态 window 变得透明。
打开 window 时如何将可拖动指令分配给模态 window? 这是代码,
HTML:
<div ng-controller="CustomWidgetCtrl">
<div class="box-header-btns pull-right" style="top:10px" >
<a title="settings" ng-click="openSettings(widget)"><i class="glyphicon glyphicon-cog"></i></a>
</div>
</div>
App.js:
var routerApp = angular.module('DiginRt', ['ui.bootstrap','ngRoute']);
routerApp.controller('CustomWidgetCtrl', ['$scope', '$modal',
function($scope, $modal) {
$scope.openSettings = function(widget) {
$modal.open({
scope: $scope,
templateUrl: 'chart_settings.html',
controller: 'chartSettingsCtrl',
resolve: {
widget: function() {
return widget;
}
}
});
};
}
])
图表设置是另一个 HTML 页面。这是我的 Draggable 指令。
更新:
我已将问题更新为 Plunker
问题:
我找不到将指令添加到 ui-bootstrap
打开的模态的方法,因为它用模态对话框包装了模板。
所以我所做的是使用以下代码将事件设置为拖动到模态对话框本身(而不是指令)。
我知道在指令内将事件添加到另一个元素不是最佳做法,但在这种情况下也不是坏做法,在这种情况下您不能直接将指令设置到元素。
这样做的原因是因为 ui-bootstrap 没有提供向 modal.open
函数
modal-dialog
添加指令的方法
这是要放在指令开头的代码:
element= angular.element(document.getElementsByClassName("modal-dialog"));
我对@Naeem_Shaikh的回答投了赞成票,这基本上是对标准 angular 指令的改进。
但是,标准 angular 可拖动指令还有另一个问题,我已经解决了。
标准指令对整个对话框施加了可拖动性。一方面,这是我们想要的。我们想拖动整个对话框。但这有一个不幸的副作用,即在对话框中的各种编辑字段中单击不起作用:默认行为被阻止!不知何故,按钮已编码为 bootstrap 以克服此问题,但文本编辑字段没有。我猜作者没有考虑我的用例。但对话框不仅仅是按钮!
这很棘手,因为需要拖动的是整个对话框,但您只想通过在 header "hotspot" 中单击来启动拖动。换句话说,发起拖动的热点是要拖动区域的子集。
Naeem 的修复使我能够使其正常工作,以便仅在 header 中单击即可启动拖动。没有他的修复,坐标转换变得混乱。
function clickedWithinHeader(event) {
var target = event.currentTarget;
var hotspot = null;
var hotspots = target.getElementsByClassName("modal-header");
if (hotspots.length > 0) {
hotspot = hotspots.item(0);
}
if (hotspot !== null) {
var eY = event.clientY;
var tOT = target.offsetTop;
var y = eY - tOT;
var hH = hotspot.offsetHeight;
// since the header occupies the full width across the top
// no need to check X. Note that this assumes the header
// is on the top, which should be a safe assumption
var within = (y <= hH);
return within;
} else {
return true;
}
}
// Draggable directive from: http://docs.angularjs.org/guide/compiler
// Modified so that only clicks in the dialog header trigger drag behavior.
// Otherwise clicking on dialog widgets did not give them focus.
angular.module('drag', []).directive('draggable', function($document) {
"use strict";
return function(scope, element) {
var startX = 0, startY = 0, x = 0, y = 0;
element= angular.element(document.getElementsByClassName("modal-dialog"));
element.css({
position : 'fixed',
cursor : 'move',
});
element.on('mousedown', function(event) {
// // OK, where did they touch? Only want to do this
// // when they clicked on the header.
if (!clickedWithinHeader(event)) {
return;
}
// Prevent default dragging of selected content
event.preventDefault();
...
请注意,clickedWithinHeader() 逻辑仅适用于 Naeem 的改进。 可能有更好的方法,但这个方法有效。只有在 header 中单击才会启动拖动,而在其他地方单击会执行它们应该执行的操作。
然而,这并不是全部答案,因为标准指令还在整个对话框上强加了移动光标,这非常令人不安,即使,或者尤其是如果您不能拖动它出现的位置。
从指令中的 element.css 中删除:
element.css({
position : 'fixed',
});
并使用 CSS
将移动光标仅绑定到模式 header.modal-header
{
cursor: move;
}
提供完整的解决方案。
我将现有解决方案更改为屏幕上的多个模式。
Html: ....
或
... // 模态页眉、正文、页脚。JS调用模式:
var modalInstance = $uibModal.open({
动画:真实,
控制器:'ModalAndamentoController',
控制器为:'vm',
窗口类:'center-modal',
尺码:'lg',
模板网址:'modalAndamento.html'
});
指令 JS: (功能 () { 'use strict';
angular .module('app') .directive('draggable', draggableDirective);
/** @ngInject */ 函数 draggableDirective($document) {
//busca pelo elemento
var serachElement = function (parentElement, element) {
//se o elemento pai corrente é igual ao elemento, então este foi encontrado
if (parentElement == element[0]) {
return true;
}
//aprofunda mais um nível na árvore procurando pelo elemento
var i = 0;
for (i = 0; i < parentElement.childNodes.length; i++) {
return serachElement(parentElement.childNodes[i], element);
}
return false;
};
//recupera o elemento referente ao dialog
var getDialogFromElement = function (element) {
//recupera todos os dialogs da tela
var dialogs = document.getElementsByClassName("modal-dialog")
//se tiver apenas um, então esse é o dialog procurado e o mesmo é retornado
if (dialogs.length == 1) {
return angular.element(dialogs[0]);
}
//senão, varre todos os dialogs, procurando por aquele que contém o elemento corrente na sua árvore de elementos
var i = 0;
for (i = 0; i < dialogs.length; i++) {
//se encontrar o elemento correte, então esse é o dialog procurado
if (serachElement(dialogs[i], element)) {
return angular.element(dialogs[i]);
}
}
};
//movimenta o dialog correspondente ao elemento informado (element)
//O elemento que chega aqui é aquele que contém o atributo draggable
return function (scope, element) {
var startX = 0,
startY = 0,
x = 0,
y = 0;
//recupera o dialog correspondente ao element corrente
element = getDialogFromElement(element);
//coloca o cursor de movimento no header
var header = angular.element(document.getElementsByClassName("modal-header"));
header.css({
cursor: 'move'
});
element.on('mousedown', function (event) {
// Prevent default dragging of selected content
//event.preventDefault();
startX = event.screenX - x;
startY = event.screenY - y;
$document.on('mousemove', mousemove);
$document.on('mouseup', mouseup);
});
function mousemove(event) {
y = event.screenY - startY;
x = event.screenX - startX;
element.css({
top: y + 'px',
left: x + 'px'
});
}
function mouseup() {
$document.unbind('mousemove', mousemove);
$document.unbind('mouseup', mouseup);
}
};
} })();
我在这里合并了各种代码片段并提出了一个更简单的解决方案。这个不依赖于检测点击事件是否发生在modal-header,因为mousedown事件专门绑定到header。
此解决方案也不依赖于在整个 DOM 中搜索 modal-dialog class,因为它会搜索 parents 对于 modal-dialog 元素。这将允许您在屏幕上有多个对话框,并且只有一个可以拖动。
我还创建了一个 gist 进行检查以防止对话框移出可见范围。
(function (angular) {
"use strict";
angular.module('MyModule')
.directive('modalDraggable', ['$document', modalDraggable]);
function modalDraggable($document) {
return function (scope, element) {
var startX = 0,
startY = 0,
x = 0,
y = 0;
var draggable = angular.element(element.parents('.modal-dialog')[0]);
draggable.find('.modal-header')
.css('cursor', 'move')
.on('mousedown', function (event) {
// Prevent default dragging of selected content
event.preventDefault();
startX = event.screenX - x;
startY = event.screenY - y;
$document.on('mousemove', mousemove);
$document.on('mouseup', mouseup);
});
function mousemove(event) {
y = event.screenY - startY;
x = event.screenX - startX;
draggable.css({
top: y + 'px',
left: x + 'px'
});
}
function mouseup() {
$document.unbind('mousemove', mousemove);
$document.unbind('mouseup', mouseup);
}
};
}
}(window.angular));