将可拖动事件委托给父元素

Posted

技术标签:

【中文标题】将可拖动事件委托给父元素【英文标题】:Delegating Draggable Events to Parent Elements 【发布时间】:2015-11-02 05:55:14 【问题描述】:

我将可拖动的 li 元素嵌套在 ul 中,而这些元素又嵌套在 div 中,如下所示:

  <div class='group'>
    <div class='header'>
    // some stuff here
    </div>
    <ul>
      <li draggable='true'> 
         Stuff I want to drag and drop to another div.group
      </li>
    </ul>
  </div>

这些div 元素有多个,我正在尝试实现拖放功能,将一个div 组的li 元素移动到另一个组。

我已经在此处连接了 ondragenterondragleave 回调:

 // controller using mithril.js
    ctrl.onDragLeave = function () 
        return function (event) 
            var target;
            // Using isDropzone to recursively search for the appropriate div.group 
            // parent element, as event.target is always the children inside it
            if ((target = isDropzone(event.target)) != null) 
                target.style.background = "";
            
        
    ;
    ctrl.onDragEnter = function () 
        return function (event) 
            var target;
            if ((target = isDropzone(event.target)) != null) 
                target.style.background = "purple";
            
        ;
    ;
    function isDropzone(elem)
        if(elem == null)
            return null;
        
        return elem.className == 'group' ? elem: isDropzone(elem.parentNode)
    

当回调的event.target 总是div 内的嵌套子元素时,问题就出现了,例如li,因此回调会不断被触发。在这种情况下,我将通过回调更改 div.group 的颜色,导致 div.group 不受欢迎地闪烁。

有没有办法委派事件并只允许li 的祖父div 处理事件?或任何其他解决此问题的方法?

编辑:仍然很想知道是否有办法做到这一点,但现在我正在使用我找到的解决方法here。

【问题讨论】:

你把这些处理程序也附加到了哪里? 【参考方案1】:

所以这将适合“你需要从不同的角度解决这个问题”的答案类别。

避免 - 尽可能 - 在附加的处理程序中从 event.target/event.currentTarget 操作 DOM。

一些不同的事情:

    您的 ondragleaveondragenter 处理程序应该在您的控制器/viewModel/stores 中简单地设置一些适当的“状态”属性

    当处理程序被解析时,这通常会触发 Mithril 中的重绘。在内部m.startComputation() 启动,你的处理程序被调用,然后m.endComputation()

    您的“查看功能”再次运行。然后它反映了更改后的模型。您的操作不会更改视图,您的视图会调用影响模型的操作,然后对这些更改做出反应。 MVC,而不是 MVVM


型号

在您的控制器中,设置一个模型来跟踪您显示拖放 UI 所需的所有状态

ctrl.dragging = m.prop(null)

ctrl.groups = m.prop([
  
    name: 'Group A',
    dragOver: false,
    items: ['Draggable One', 'Draggable Two']
  ,
  ...
  // same structure for all groups
])

查看

在您看来,设置一个反映您的模型状态的 UI。拥有事件处理程序,将有关操作的足够信息传递给控制器​​ - 足以让控制器正确响应操作并相应地操作模型


return ctrl.groups.map(function (group, groupIdx) 
  return m('.group',[
    m('.header', group.name),
    m('ul', 
      
        style:  background: (group.dragOver ? 'blue' : ''),
        ondragleave: function () ctrl.handleDragLeave(groupIdx),
        ondragenter: function () ctrl.handleDragEnter(groupIdx),
        ondrop: function () ctrl.handleDrop(groupIdx),
        ondragover: function (e) e.preventDefault()
      ,
      group.items.map(function (item, itemIdx) 
        return m('li',
          
            draggable: true,
            ondragstart: function () ctrl.handleDragStart(itemIdx, groupIdx)
          ,
          item
      )
    )
  ])
)

现在已设置好,组可以通过对控制器中的状态/模型更改做出反应来正确显示。我们不需要操纵 dom 来说一个组有一个新项目,一个组需要一个新的背景颜色,或者任何东西。我们只需要附加事件处理程序,以便控制器可以操作您的模型,然后视图将根据该模型重绘。

控制器

因此,您的控制器可以拥有包含更新模型所需操作的所有信息的处理程序。

您的控制器上的一些处理程序如下所示:

ctrl.handleDragStart = function (itemIdx, groupIdx) 
  ctrl.dragging(itemIdx: itemIdx, groupIdx: groupIdx)


ctrl.handleDragEnter = function (groupIdx) 
  ctrl.groups()[groupIdx].dragOver = true


ctrl.handleDragLeave = function (groupIdx) 
  ctrl.groups()[groupIdx].dragOver = false


ctrl.handleDrop = function (toGroupIdx) 
  var groupIdx = ctrl.dragging().groupIdx
  var itemIdx = ctrl.dragging().itemIdx
  var dropped = ctrl.groups()[groupIdx].items.splice(itemIdx, 1)[0]

  ctrl.groups()[toGroupIdx].items.push(dropped)
  ctrl.groups()[toGroupIdx].dragOver = false
  ctrl.dragging(null)


尝试坚持 Mithril 的 MVC 模型 事件处理程序在您的控制器上调用操作,该控制器操作模型。然后视图会对这些模型中的变化做出反应。这绕过了与 DOM 事件的细节纠缠在一起的需要。

这是一个完整的 JSbin 示例,展示了您要达到的目标:

https://jsbin.com/pabehuj/edit?js,console,output

我得到了想要的效果,完全不用担心事件委托。

另外,请注意在 JSbin 中,ondragenter 处理程序:

ondragenter: function () 
  if (ctrl.dragging().groupIdx !== groupIdx) 
    ctrl.handleDragEnter(groupIdx)
  

这样可放置区域不会在其自身的可拖动区域中改变颜色,这是我认为您在答案中寻找的东西之一。

【讨论】:

以上是关于将可拖动事件委托给父元素的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript中冒泡与事件委托

事件委托

事件委托

JavaScript事件委托

关于事件委托和时间冒泡(以及apply和call的事项)

事件委托(代理)