我如何使用 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"));

plunkr

我对@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));