Skip to content

Commit 353f1b9

Browse files
committed
fix: #12 dragging after re-render
1 parent c1301fa commit 353f1b9

File tree

4 files changed

+83
-22
lines changed

4 files changed

+83
-22
lines changed

code-component/PowerDragDrop/ItemRenderer.ts

+12-3
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,19 @@ import { GetOutputObjectRecord } from './DynamicSchema';
66
import { IInputs } from './generated/ManifestTypes';
77
import { DirectionEnum, ItemProperties } from './ManifestConstants';
88
import { SanitizeHtmlOptions } from './SanitizeHtmlOptions';
9-
import { CSS_STYLE_CLASSES, ORIGINAL_POSITION_ATTRIBUTE, ORIGINAL_ZONE_ATTRIBUTE, RECORD_ID_ATTRIBUTE } from './Styles';
9+
import {
10+
CSS_STYLE_CLASSES,
11+
ORIGINAL_POSITION_ATTRIBUTE,
12+
ORIGINAL_ZONE_ATTRIBUTE,
13+
RECORD_ID_ATTRIBUTE,
14+
RENDER_VERSION_ATTRIBUTE,
15+
} from './Styles';
1016

1117
export class ItemRenderer {
1218
public rendered = false;
1319
public mainContainer: HTMLElement;
1420
public listContainer: HTMLElement;
21+
public renderVersion = 0;
1522

1623
constructor(container: HTMLDivElement) {
1724
// Create root containers
@@ -55,7 +62,8 @@ export class ItemRenderer {
5562
const currentItems: CurrentItem[] = [];
5663
const originalOrder: string[] = [];
5764
const listContainer = this.listContainer;
58-
65+
this.renderVersion++;
66+
this.listContainer.setAttribute(RENDER_VERSION_ATTRIBUTE, this.renderVersion.toString());
5967
if (!this.checkForAliases(context)) return {};
6068
this.rendered = true;
6169
const isMasterZone = parameters.IsMasterZone.raw === true;
@@ -117,6 +125,7 @@ export class ItemRenderer {
117125
index++;
118126
const itemRow: HTMLElement = document.createElement('li');
119127
itemRow.classList.add(CSS_STYLE_CLASSES.Item);
128+
itemRow.setAttribute(RENDER_VERSION_ATTRIBUTE, this.renderVersion.toString());
120129

121130
// Style accordingly to the parameters
122131
this.styleItemElement(itemRow, parameters);
@@ -152,7 +161,7 @@ export class ItemRenderer {
152161
private removeAllExistingElements() {
153162
const listContainer = this.listContainer;
154163
while (listContainer.firstChild && listContainer.firstChild.parentNode) {
155-
listContainer.firstChild.parentNode.removeChild(listContainer.firstChild);
164+
listContainer.removeChild(listContainer.firstChild);
156165
}
157166
}
158167

code-component/PowerDragDrop/Styles.ts

+3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
export const RECORD_ID_ATTRIBUTE = 'data-id';
22
export const ORIGINAL_POSITION_ATTRIBUTE = 'data-original-position';
33
export const ORIGINAL_ZONE_ATTRIBUTE = 'data-original-zone';
4+
export const DRAGGED_FROM_ZONE_ATTRIBUTE = 'data-dragged-from-zone';
5+
export const RENDER_VERSION_ATTRIBUTE = 'data-render-version';
6+
export const DRAG_INVALID = 'powerdnd-drag-invalid';
47
export const ROTATION_CLASSES = [
58
'powerdnd-drag-rotate-clockwise-small',
69
'powerdnd-drag-rotate-clockwise-large',

code-component/PowerDragDrop/__tests__/ItemRenderer.test.tsx

+4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ describe('ItemRenderer', () => {
2020
>
2121
<ul
2222
class="powerdnd-list"
23+
data-render-version="1"
2324
style="overflow: hidden;"
2425
/>
2526
</div>
@@ -42,6 +43,7 @@ describe('ItemRenderer', () => {
4243
>
4344
<ul
4445
class="powerdnd-list"
46+
data-render-version="1"
4547
style="overflow: hidden; flex-direction: row; flex-wrap: nowrap; display: flex;"
4648
/>
4749
</div>
@@ -59,6 +61,7 @@ describe('ItemRenderer', () => {
5961
>
6062
<ul
6163
class="powerdnd-list"
64+
data-render-version="1"
6265
style="overflow: hidden; flex-direction: row; flex-wrap: wrap; display: flex;"
6366
/>
6467
</div>
@@ -78,6 +81,7 @@ describe('ItemRenderer', () => {
7881
>
7982
<ul
8083
class="powerdnd-list"
84+
data-render-version="1"
8185
style="overflow: hidden; flex-direction: row; flex-wrap: wrap; display: flex;"
8286
/>
8387
</div>

code-component/PowerDragDrop/index.ts

+64-19
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@ import {
1313
} from './ManifestConstants';
1414
import {
1515
CSS_STYLE_CLASSES,
16+
DRAGGED_FROM_ZONE_ATTRIBUTE,
17+
DRAG_INVALID,
1618
ORIGINAL_POSITION_ATTRIBUTE,
1719
ORIGINAL_ZONE_ATTRIBUTE,
1820
RECORD_ID_ATTRIBUTE,
21+
RENDER_VERSION_ATTRIBUTE,
1922
ROTATION_CLASSES,
2023
} from './Styles';
2124

@@ -40,7 +43,7 @@ const defaultSortableOptions: Sortable.Options = {
4043
scrollSpeed: 10,
4144
forceFallback: true,
4245
fallbackOnBody: true,
43-
removeCloneOnHide: false,
46+
removeCloneOnHide: true,
4447
ghostClass: CSS_STYLE_CLASSES.Ghost,
4548
chosenClass: CSS_STYLE_CLASSES.Chosen,
4649
dataIdAttr: RECORD_ID_ATTRIBUTE,
@@ -128,22 +131,13 @@ export class PowerDragDrop implements ComponentFramework.StandardControl<IInputs
128131

129132
// Event if this is not a master zone, the reset event triggers a re-render to enable items
130133
// to be re-created after drop
134+
const renderTriggerProperties = this.hasPropertyChanged(RENDER_TRIGGER_PROPERTIES);
131135
const updateItems =
132-
!this.itemRenderer.rendered ||
133-
resetDatasetTriggered ||
134-
datasetChanged ||
135-
this.hasPropertyChanged(RENDER_TRIGGER_PROPERTIES);
136+
!this.itemRenderer.rendered || resetDatasetTriggered || datasetChanged || renderTriggerProperties;
136137

137138
if (!parameters.items.loading && updateItems) {
138-
const renderResult = this.itemRenderer.renderItems(context);
139-
if (renderResult.itemsRendered && renderResult.sortOrder) {
140-
this.currentItems = renderResult.itemsRendered;
141-
this.originalOrder = renderResult.sortOrder;
142-
143-
if (isMasterZone) {
144-
this.notifyOutputChanged();
145-
}
146-
}
139+
this.trace('renderItems', { resetDatasetTriggered, datasetChanged, renderTriggerProperties });
140+
this.renderItems();
147141
}
148142

149143
if (clearChangesTriggered) {
@@ -179,6 +173,18 @@ export class PowerDragDrop implements ComponentFramework.StandardControl<IInputs
179173
}
180174
}
181175

176+
private renderItems(): void {
177+
const renderResult = this.itemRenderer.renderItems(this.context);
178+
if (renderResult.itemsRendered && renderResult.sortOrder) {
179+
this.currentItems = renderResult.itemsRendered;
180+
this.originalOrder = renderResult.sortOrder;
181+
182+
if (this.isMasterZone()) {
183+
this.notifyOutputChanged();
184+
}
185+
}
186+
}
187+
182188
private getActionFromClass(target: HTMLElement) {
183189
return target.className.split(' ').find((c: string) => c.startsWith(CSS_STYLE_CLASSES.ActionClassPrefix));
184190
}
@@ -329,6 +335,7 @@ export class PowerDragDrop implements ComponentFramework.StandardControl<IInputs
329335
onUnchoose: this.onUnChoose,
330336
onEnd: this.onEnd,
331337
onMove: this.onMove,
338+
onClone: this.onClone,
332339
filter: this.actionFilter,
333340
});
334341
const zoneRegistration = {
@@ -436,20 +443,54 @@ export class PowerDragDrop implements ComponentFramework.StandardControl<IInputs
436443
// Check if we have reached the maximum items for the drop zone
437444
if (event.to) {
438445
const targetZoneId = this.getZoneId(event.to as HTMLElement);
439-
const zone = this.zonesRegistered[targetZoneId];
440-
if (zone && zone.maximumItems && zone.maximumItems > 0) {
441-
const currentItemCount = zone.sortable.toArray().length;
442-
return currentItemCount < zone.maximumItems;
446+
const sourceZoneId = this.getZoneId(event.from as HTMLElement);
447+
const targetZone = this.zonesRegistered[targetZoneId];
448+
const sourceZone = this.zonesRegistered[sourceZoneId];
449+
// Check if the source zone has re-rendered - if so this item is invalid
450+
const sourceZoneRenderVersion = sourceZone.sortable.el.getAttribute(RENDER_VERSION_ATTRIBUTE);
451+
const draggedRenderVersion = event.dragged.getAttribute(RENDER_VERSION_ATTRIBUTE);
452+
const originalZone = event.dragged.getAttribute(ORIGINAL_ZONE_ATTRIBUTE);
453+
const invalidDrag = Sortable.utils.is(event.dragged, '.' + DRAG_INVALID);
454+
if (invalidDrag) {
455+
this.trace('onMove - Invalid drag item');
456+
return false;
457+
}
458+
if (sourceZoneRenderVersion !== draggedRenderVersion && originalZone === sourceZoneId) {
459+
this.trace('onMove - Render version mismatch');
460+
return false;
461+
}
462+
if (targetZone && targetZone.maximumItems && targetZone.maximumItems > 0) {
463+
const currentItemCount = targetZone.sortable.toArray().length;
464+
return currentItemCount < targetZone.maximumItems;
443465
}
444466
}
445467
};
446468

469+
onClone = (event: SortableEvent): void => {
470+
const origEl = event.item;
471+
const invalidDragItem = !origEl.parentElement;
472+
Sortable.utils.toggleClass(origEl, DRAG_INVALID, invalidDragItem);
473+
if (invalidDragItem) {
474+
// The item being dragged has no parent container since the zone has been removed
475+
// or the items have been re-rendered. This makes the drag item invalid
476+
origEl.style.display = 'none';
477+
this.trace('onClone -Invalid drag');
478+
}
479+
};
480+
447481
onEnd = (event: SortableEvent): void => {
448482
try {
449483
const draggedElement = event.item; // dragged HTMLElement
450484
const targetZone = event.to; // target list
451485
const sourceZone = event.from; // previous list
452486

487+
// Check if there is an invalid item being dragged
488+
// and prevent it being dropped anywhere
489+
if (Sortable.utils.is(draggedElement, '.' + DRAG_INVALID)) {
490+
this.trace('onEnd - Invalid drop');
491+
return;
492+
}
493+
453494
const newPosition = event.newDraggableIndex; // element's new index within new parent, only counting draggable elements
454495
const itemId = draggedElement.getAttribute(RECORD_ID_ATTRIBUTE) as string;
455496
const targetZoneId = this.getZoneId(targetZone);
@@ -476,12 +517,16 @@ export class PowerDragDrop implements ComponentFramework.StandardControl<IInputs
476517
}
477518
};
478519

479-
onUnChoose = (): void => {
520+
onUnChoose = (event: SortableEvent): void => {
480521
this.currentItemZone = null;
522+
this.trace('onUnChoose', this.context.parameters.DropZoneID.raw, event.item.innerText);
523+
event.item.removeAttribute(DRAGGED_FROM_ZONE_ATTRIBUTE);
481524
};
482525

483526
onChoose = (event: SortableEvent): void => {
484527
this.currentItemZone = this.getZoneId(event.from);
528+
this.trace('onChoose', this.context.parameters.DropZoneID.raw, event.item.innerText);
529+
event.item.setAttribute(DRAGGED_FROM_ZONE_ATTRIBUTE, this.currentItemZone);
485530
};
486531

487532
actionFilter = (event: Event | TouchEvent): boolean => {

0 commit comments

Comments
 (0)