重新排序元素后未调用 Dragstart

Dragstart not getting invoked after reordering the elements

我正在尝试创建一个列表,可以通过拖动其中的项目对其进行重新排序。

当我第一次拖动元素时 dragstart.trigger="drag($event)" 调用 drag(e)。在 drag(e) 中,我设置了拖动元素的数据。

放下拖动的元素 drop.trigger="drop($event)" 调用 drop(e)。 在 drop(e) 中,我获取拖动的元素并将其从 list/parent 元素中移除 <ul>。 之后我将拖动的元素插入到放置的位置。

问题是一旦拖动了一个元素。我无法将它再次拖动到不同的目标,因为 dragstart.trigger="drag($event)" 没有调用 drag(e)

如何调用 dragstart.trigger="drag($event)"

<ul id="columns" drop.trigger="drop($event)" dragover.trigger="allowDrop($event)">
    <li id="item1" class="column" draggable="true" dragstart.trigger="drag($event)" dragend.trigger="dragend($event)"><header>A</header></li>
    <li id="item2" class="column" draggable="true" dragstart.trigger="drag($event)" dragend.trigger="dragend($event)"><header>B</header></li>
    <li id="item3" class="column" draggable="true" dragstart.trigger="drag($event)" dragend.trigger="dragend($event)"><header>C</header></li>
    <li id="item4" class="column" draggable="true" dragstart.trigger="drag($event)" dragend.trigger="dragend($event)"><header>D</header></li>
    <li id="item5" class="column" draggable="true" dragstart.trigger="drag($event)" dragend.trigger="dragend($event)"><header>E</header></li>
</ul>

JS :

drag(e) {
  console.log('handleDragStart');
  // Target element is the source node.
  this.dragSrcEl = e.currentTarget;
  console.log('dragSrcEl :', this.dragSrcEl);

  e.dataTransfer.effectAllowed = 'move';
  e.dataTransfer.setData('text/html', e.currentTarget.outerHTML);
  e.currentTarget.classList.add('dragElem');
  return true;
}

allowDrop(e) {
  console.log('handleDragover');
  e.preventDefault();
}

dragend() {
  console.log('handleDragEnd');
}

drop(e) {
  console.log('handleDrop');

  if (e.stopPropagation) {
    e.stopPropagation();
  }
  // Don't do anything if dropping the same column we're dragging.  
  if (this.dragSrcEl != e.srcElement) {
    e.currentTarget.removeChild(this.dragSrcEl);

    let dropHTML = e.dataTransfer.getData('text/html');
    e.srcElement.parentNode.insertAdjacentHTML('beforebegin',dropHTML)
  }
  e.currentTarget.classList.remove('over');
  return false;
}

重新排序元素后未调用 dragstart 的原因是因为您并不是真正地 重新排序 它们。您实际上是在删除拖动的元素,然后插入它的新副本。

这个新副本不由 aurelia 的合成引擎处理,因此不会编译,因此 html 中的任何 aurelia-specific 表达式都不会执行任何操作。 .trigger 那时只是一个死标签。

Drag/drop 是一种特殊的野兽,以自然的方式实现起来从来都不是特别简单,尤其是当这些元素附加了各种自定义框架行为时。

这里有3个选项:

  1. 不要使用 aurelia 的 trigger,而是在第一次创建它们时使用 el.addEventListener,然后在创建新副本时使用。

  2. 使用 aurelia 的 ViewEngine 到 re-compile(部分)你的视图,只要你放下一个元素,以便处理 .trigger,在幕后,真的反正就是 el.addEventListener

  3. 将其变成带有 repeat.for 的自定义元素,让 Aurelia 处理 html 方面的事情。

现在选项 1 肯定是让它工作的最快方法,而选项 2 会稍微更健壮和更棘手,但两者都非常 hacky。

我强烈主张使用框架而不是绕过它,因为从长远来看,事情会更容易维护,而且随着项目的发展,您可以更轻松地添加额外的奇特行为。

它可能看起来比你现在做的要复杂得多,但是通过使用更多的框架来处理 low-level 东西,你将拥有 "living" 具有完整功能的可拖动元素Aurelia,你可以用它做更多的事情。

所以这只是您可能如何处理选项 3 的一个示例:

在 app.js 中,使您的列成为 javascript 个对象的列表:

items = [
  { text: "A", id: "item1" },
  { text: "B", id: "item2" },
  { text: "C", id: "item3" },
  { text: "D", id: "item4" },
  { text: "E", id: "item5" }
];

在 app.html 中,将这些项目传递给 columns 自定义元素(为了使 html 与您的示例相似,我将使用 as-element

<template>
  <require from="./resources/elements/columns"></require>
  <ul as-element="columns" items.bind="items"></ul>
</template>

在 resources/elements/columns.js 中,针对单个项目视图模型而不是针对 html 元素工作:

import { customElement, children, bindable } from "aurelia-templating";

@customElement("columns")
export class Columns {
  // keeps a list of the viewmodels of the direct "li" children
  @children("li") children;

  // the columns
  @bindable() items;

  // the currently dragged column
  dragColumn;

  // the customEvent we dispatch from the child "column" element
  handleColDragStart(e) {
    // the viewmodel we passed into the customEvent
    this.dragColumn = e.detail.column;
  }

  allowDrop(e) {
    console.log("handleDragover");
    e.preventDefault();
  }

  drop(e) {
    console.log("handleDrop");

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

    // source drag index
    let dragIdx = this.children.indexOf(this.dragColumn);

    // if we can't resolve to a sibling (e.g. dropped on or outside the list),
    // naively drop it at index 0 instead
    let dropIdx = 0;

    // try to find the drop target
    let dropTarget = e.srcElement;
    while (dropTarget !== document.body) {
      let dropTargetVm = dropTarget.au && dropTarget.au.controller && dropTarget.au.controller.viewModel;
      if (dropTargetVm) {
        dropIdx = this.children.indexOf(dropTargetVm);
        break;
      } else {
        dropTarget = dropTarget.parentElement;
      }
    }

    if (dragIdx !== dropIdx) {
      // only modify the order in the array of javascript objects;
      // the repeat.for will re-order the html for us
      this.items.splice(dropIdx, 0, this.items.splice(dragIdx, 1)[0]);
    }

    return false;
  }
}

在 resources/elements/columns.html 中,只监听我们从 column 元素发送的自定义事件,除此之外只处理 drop:

<template id="columns" drop.trigger="drop($event)" dragover.trigger="allowDrop($event)">
  <require from="./column"></require>
  <li as-element="column" repeat.for="col of items" column.bind="col" coldragstart.trigger="handleColDragStart($event)">
  </li>
</template>

在 resource/elements/column.js 中处理 dragstartdragend 事件,然后分派一个带有对 viewModel 的引用的 customEvent(因此您不必处理 html 太多了):

import { customElement, bindable } from "aurelia-templating";
import { inject } from "aurelia-dependency-injection";

@customElement("column")
@inject(Element)
export class Column {
  el;
  constructor(el) {
    this.el = el;
  }

  @bindable() column;

  dragstart(e) {
    this.el.dispatchEvent(
      new CustomEvent("coldragstart", {
        bubbles: true,
        detail: {
          column: this
        }
      })
    );
    return true;
  }
}

最后,在 resources/elements/column.html 中只监听 dragstart 事件:

<template draggable="true" dragstart.trigger="dragstart($event)">
  <header>${column.text}</header>
</template>

这个解决方案中您可能看起来有点奇怪的部分,也是我仍然认为有点老套的部分,就是我们尝试通过 el.au.controller.viewModel.

获取 ViewModel 的部分

这是你的东西"just need to know"。自定义元素/html 行为总是有一个 au 属性 包含对控制器、视图等行为实例的引用

当直接针对 html 工作时,这基本上是 "get a hold of" 奥里利亚的最简单(有时也是唯一)方法。对于像 drag/drop 这样的事情,我认为没有任何方法可以避免这种情况,因为不幸的是没有原生的 aurelia 支持它。