重新排序元素后未调用 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个选项:
不要使用 aurelia 的 trigger
,而是在第一次创建它们时使用 el.addEventListener
,然后在创建新副本时使用。
使用 aurelia 的 ViewEngine
到 re-compile(部分)你的视图,只要你放下一个元素,以便处理 .trigger
,在幕后,真的反正就是 el.addEventListener
将其变成带有 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 中处理 dragstart
和 dragend
事件,然后分派一个带有对 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 支持它。
我正在尝试创建一个列表,可以通过拖动其中的项目对其进行重新排序。
当我第一次拖动元素时 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个选项:
不要使用 aurelia 的
trigger
,而是在第一次创建它们时使用el.addEventListener
,然后在创建新副本时使用。使用 aurelia 的
ViewEngine
到 re-compile(部分)你的视图,只要你放下一个元素,以便处理.trigger
,在幕后,真的反正就是el.addEventListener
将其变成带有
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 中处理 dragstart
和 dragend
事件,然后分派一个带有对 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
.
这是你的东西"just need to know"。自定义元素/html 行为总是有一个 au
属性 包含对控制器、视图等行为实例的引用
当直接针对 html 工作时,这基本上是 "get a hold of" 奥里利亚的最简单(有时也是唯一)方法。对于像 drag/drop 这样的事情,我认为没有任何方法可以避免这种情况,因为不幸的是没有原生的 aurelia 支持它。