如何将DOM函数发送到AngularJS中的指令?

时间:2014-11-13 12:56:07

标签: angularjs dom angularjs-directive parse-error

我正在关注实施Drag& amp;放入我最初在this StackOverflow answer中找到的Angular,它链接到作家的博客和GitHub代码。

当我实现它并在我的项目中测试它时,我得到了错误:

[$parse:isecdom] Referencing DOM nodes in Angular expressions is disallowed! Expression: dropped(dragEl, dropEl)

According to Angular Docs,问题在于我引用了一个将DOM对象作为DOM属性中的参数的函数 - 它发生在这一行中:

<div x-lvl-drop-target='true' x-on-drop='dropped(dragEl, dropEl)'>drop zone</div>

正如您所看到的,on-drop属性将此dropped函数(在我的控制器中定义)传递给指令lvlDropTarget(在下面发布),该指令将其调用以执行操作用户进行的拖放操作。 我喜欢这个设计,因为它使指令可以在同一个应用程序中重复使用许多不同的拖放可能性。基本上,我只需要在我的控制器中定义一个不同的函数,并通过on-drop属性将其传递给指令。

然而,遗憾的是,这似乎被Angular击落了。有没有人知道如何实现相同的设计和功能,但Angular更适合使用?

这里是lvlDropTarget指令

module.directive('lvlDropTarget', ['$rootScope', 'uuid',
  function ($rootScope, uuid) {
    return {
      restrict: 'A',
      scope: {
        onDrop: '&'
      },
      link: function (scope, el, attrs, controller) {
        var id = angular.element(el).attr("id");
        if (!id) {
          id = uuid.new()
          angular.element(el).attr("id", id);
        }

        el.bind("dragover", function (e) {
          if (e.preventDefault) {
            e.preventDefault(); // Necessary. Allows us to drop.
          }

          if (e.stopPropagation) {
            e.stopPropagation();
          }

          e.dataTransfer.dropEffect = 'move';
          return false;
        });

        el.bind("dragenter", function (e) {
          angular.element(e.target).addClass('lvl-over');
        });

        el.bind("dragleave", function (e) {
          angular.element(e.target).removeClass('lvl-over'); // this / e.target is previous target element.
        });

        el.bind("drop", function (e) {
          if (e.preventDefault) {
            e.preventDefault(); // Necessary. Allows us to drop.
          }

          if (e.stopPropogation) {
            e.stopPropogation(); // Necessary. Allows us to drop.
          }

          var data = e.dataTransfer.getData("text");
          var dest = document.getElementById(id);
          var src = document.getElementById(data);

          scope.onDrop({
            dragEl: src,
            dropEl: dest
          });
        });

        $rootScope.$on("LVL-DRAG-START", function () {
          var el = document.getElementById(id);
          angular.element(el).addClass("lvl-target");
        });

        $rootScope.$on("LVL-DRAG-END", function () {
          var el = document.getElementById(id);
          angular.element(el).removeClass("lvl-target");
          angular.element(el).removeClass("lvl-over");
        });
      }
    }
  }
]);

2 个答案:

答案 0 :(得分:5)

10月14日的问题是logged on github

  

AngularJS现在给出了这个错误:“不允许在Angular表达式中引用DOM节点!”,因为:on-drop =“controller.dropped(dragEl,dropEl)”。

提出了以下解决方案:

  

返回DOM元素本身导致更新版本的AngularJS中的$ parse:isecdom错误。简而言之,让dropped(...)回调句柄获取元素。

suggested code change on github

因此,如果接受此更改,则将修改该指令以返回拖放的元素ID。然后由你的回调来做document.getElementById

$scope.dropped = function(dragId, dropId) {
  var dragEl = document.getElementById(dragId);
  var dropEl = document.getElementById(dropId);

  console.log(dragEl, dropEl);
}

话虽如此,Angular有充分的理由将isecdom validation添加到他们的expression sandboxing

  

AngularJS限制从表达式中访问DOM节点,因为它是执行任意Javascript代码的已知方法。

     

此检查仅在Angular表达式中的对象索引和函数调用上执行。这些是开发人员更难保护的地方。虚线成员访问(例如a.b.c)不执行此检查 - 开发人员不应直接在范围链上公开此类敏感和强大的对象。

     

要解决此错误,请避免访问DOM节点。

我倾向于选择use ngDraggable - 而不是试图绕过这种保护 - 这鼓励使用declarative views而不需要DOM manipulation in your controller。好的电话。

答案 1 :(得分:1)

我认为,您的问题的解决方案相当简单 - 使用中间对象来包装dragEldropEl,如下所示:

在链接功能中:

scope.onDrop({ 
        event : {
          dragEl: src,
          dropEl: dest
        }
      });

in html:

<div x-lvl-drop-target='true' x-on-drop='dropped(event)'>drop zone</div>

在外面:

$scope.dropped = function(event){
   console.log('dropped', event);
}

正在工作DEMO


或者,您可以在value中保留有关拖动事件的信息,如下所示:

app.value('dropEvent', {dropEl:null, dragEl:null});

app.directive('lvlDropTarget',
  function ($rootScope, dropEvent) {
    return {

 //... 

      link: function (scope, el, attrs, controller) {

 //...

        el.bind("dragover", function (e) {

 //... 

          dropEvent.dragEl = src;
          dropEvent.dropEl = dest;

          scope.onDrop();
        });
 //...

    }
  }
);

然后在控制器/外部环境中使用它:

app.controller('MainCtrl', function($scope, dropEvent) {
  $scope.dropped = function(){
    console.log('dropped', dropEvent);
  }
});

DEMO