Skip to content

Commit

Permalink
fix: #12 dragging after re-render
Browse files Browse the repository at this point in the history
  • Loading branch information
scottdurow committed Dec 12, 2022
1 parent c1301fa commit 353f1b9
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 22 deletions.
15 changes: 12 additions & 3 deletions code-component/PowerDragDrop/ItemRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,19 @@ import { GetOutputObjectRecord } from './DynamicSchema';
import { IInputs } from './generated/ManifestTypes';
import { DirectionEnum, ItemProperties } from './ManifestConstants';
import { SanitizeHtmlOptions } from './SanitizeHtmlOptions';
import { CSS_STYLE_CLASSES, ORIGINAL_POSITION_ATTRIBUTE, ORIGINAL_ZONE_ATTRIBUTE, RECORD_ID_ATTRIBUTE } from './Styles';
import {
CSS_STYLE_CLASSES,
ORIGINAL_POSITION_ATTRIBUTE,
ORIGINAL_ZONE_ATTRIBUTE,
RECORD_ID_ATTRIBUTE,
RENDER_VERSION_ATTRIBUTE,
} from './Styles';

export class ItemRenderer {
public rendered = false;
public mainContainer: HTMLElement;
public listContainer: HTMLElement;
public renderVersion = 0;

constructor(container: HTMLDivElement) {
// Create root containers
Expand Down Expand Up @@ -55,7 +62,8 @@ export class ItemRenderer {
const currentItems: CurrentItem[] = [];
const originalOrder: string[] = [];
const listContainer = this.listContainer;

this.renderVersion++;
this.listContainer.setAttribute(RENDER_VERSION_ATTRIBUTE, this.renderVersion.toString());
if (!this.checkForAliases(context)) return {};
this.rendered = true;
const isMasterZone = parameters.IsMasterZone.raw === true;
Expand Down Expand Up @@ -117,6 +125,7 @@ export class ItemRenderer {
index++;
const itemRow: HTMLElement = document.createElement('li');
itemRow.classList.add(CSS_STYLE_CLASSES.Item);
itemRow.setAttribute(RENDER_VERSION_ATTRIBUTE, this.renderVersion.toString());

// Style accordingly to the parameters
this.styleItemElement(itemRow, parameters);
Expand Down Expand Up @@ -152,7 +161,7 @@ export class ItemRenderer {
private removeAllExistingElements() {
const listContainer = this.listContainer;
while (listContainer.firstChild && listContainer.firstChild.parentNode) {
listContainer.firstChild.parentNode.removeChild(listContainer.firstChild);
listContainer.removeChild(listContainer.firstChild);
}
}

Expand Down
3 changes: 3 additions & 0 deletions code-component/PowerDragDrop/Styles.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
export const RECORD_ID_ATTRIBUTE = 'data-id';
export const ORIGINAL_POSITION_ATTRIBUTE = 'data-original-position';
export const ORIGINAL_ZONE_ATTRIBUTE = 'data-original-zone';
export const DRAGGED_FROM_ZONE_ATTRIBUTE = 'data-dragged-from-zone';
export const RENDER_VERSION_ATTRIBUTE = 'data-render-version';
export const DRAG_INVALID = 'powerdnd-drag-invalid';
export const ROTATION_CLASSES = [
'powerdnd-drag-rotate-clockwise-small',
'powerdnd-drag-rotate-clockwise-large',
Expand Down
4 changes: 4 additions & 0 deletions code-component/PowerDragDrop/__tests__/ItemRenderer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ describe('ItemRenderer', () => {
>
<ul
class="powerdnd-list"
data-render-version="1"
style="overflow: hidden;"
/>
</div>
Expand All @@ -42,6 +43,7 @@ describe('ItemRenderer', () => {
>
<ul
class="powerdnd-list"
data-render-version="1"
style="overflow: hidden; flex-direction: row; flex-wrap: nowrap; display: flex;"
/>
</div>
Expand All @@ -59,6 +61,7 @@ describe('ItemRenderer', () => {
>
<ul
class="powerdnd-list"
data-render-version="1"
style="overflow: hidden; flex-direction: row; flex-wrap: wrap; display: flex;"
/>
</div>
Expand All @@ -78,6 +81,7 @@ describe('ItemRenderer', () => {
>
<ul
class="powerdnd-list"
data-render-version="1"
style="overflow: hidden; flex-direction: row; flex-wrap: wrap; display: flex;"
/>
</div>
Expand Down
83 changes: 64 additions & 19 deletions code-component/PowerDragDrop/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ import {
} from './ManifestConstants';
import {
CSS_STYLE_CLASSES,
DRAGGED_FROM_ZONE_ATTRIBUTE,
DRAG_INVALID,
ORIGINAL_POSITION_ATTRIBUTE,
ORIGINAL_ZONE_ATTRIBUTE,
RECORD_ID_ATTRIBUTE,
RENDER_VERSION_ATTRIBUTE,
ROTATION_CLASSES,
} from './Styles';

Expand All @@ -40,7 +43,7 @@ const defaultSortableOptions: Sortable.Options = {
scrollSpeed: 10,
forceFallback: true,
fallbackOnBody: true,
removeCloneOnHide: false,
removeCloneOnHide: true,
ghostClass: CSS_STYLE_CLASSES.Ghost,
chosenClass: CSS_STYLE_CLASSES.Chosen,
dataIdAttr: RECORD_ID_ATTRIBUTE,
Expand Down Expand Up @@ -128,22 +131,13 @@ export class PowerDragDrop implements ComponentFramework.StandardControl<IInputs

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

if (!parameters.items.loading && updateItems) {
const renderResult = this.itemRenderer.renderItems(context);
if (renderResult.itemsRendered && renderResult.sortOrder) {
this.currentItems = renderResult.itemsRendered;
this.originalOrder = renderResult.sortOrder;

if (isMasterZone) {
this.notifyOutputChanged();
}
}
this.trace('renderItems', { resetDatasetTriggered, datasetChanged, renderTriggerProperties });
this.renderItems();
}

if (clearChangesTriggered) {
Expand Down Expand Up @@ -179,6 +173,18 @@ export class PowerDragDrop implements ComponentFramework.StandardControl<IInputs
}
}

private renderItems(): void {
const renderResult = this.itemRenderer.renderItems(this.context);
if (renderResult.itemsRendered && renderResult.sortOrder) {
this.currentItems = renderResult.itemsRendered;
this.originalOrder = renderResult.sortOrder;

if (this.isMasterZone()) {
this.notifyOutputChanged();
}
}
}

private getActionFromClass(target: HTMLElement) {
return target.className.split(' ').find((c: string) => c.startsWith(CSS_STYLE_CLASSES.ActionClassPrefix));
}
Expand Down Expand Up @@ -329,6 +335,7 @@ export class PowerDragDrop implements ComponentFramework.StandardControl<IInputs
onUnchoose: this.onUnChoose,
onEnd: this.onEnd,
onMove: this.onMove,
onClone: this.onClone,
filter: this.actionFilter,
});
const zoneRegistration = {
Expand Down Expand Up @@ -436,20 +443,54 @@ export class PowerDragDrop implements ComponentFramework.StandardControl<IInputs
// Check if we have reached the maximum items for the drop zone
if (event.to) {
const targetZoneId = this.getZoneId(event.to as HTMLElement);
const zone = this.zonesRegistered[targetZoneId];
if (zone && zone.maximumItems && zone.maximumItems > 0) {
const currentItemCount = zone.sortable.toArray().length;
return currentItemCount < zone.maximumItems;
const sourceZoneId = this.getZoneId(event.from as HTMLElement);
const targetZone = this.zonesRegistered[targetZoneId];
const sourceZone = this.zonesRegistered[sourceZoneId];
// Check if the source zone has re-rendered - if so this item is invalid
const sourceZoneRenderVersion = sourceZone.sortable.el.getAttribute(RENDER_VERSION_ATTRIBUTE);
const draggedRenderVersion = event.dragged.getAttribute(RENDER_VERSION_ATTRIBUTE);
const originalZone = event.dragged.getAttribute(ORIGINAL_ZONE_ATTRIBUTE);
const invalidDrag = Sortable.utils.is(event.dragged, '.' + DRAG_INVALID);
if (invalidDrag) {
this.trace('onMove - Invalid drag item');
return false;
}
if (sourceZoneRenderVersion !== draggedRenderVersion && originalZone === sourceZoneId) {
this.trace('onMove - Render version mismatch');
return false;
}
if (targetZone && targetZone.maximumItems && targetZone.maximumItems > 0) {
const currentItemCount = targetZone.sortable.toArray().length;
return currentItemCount < targetZone.maximumItems;
}
}
};

onClone = (event: SortableEvent): void => {
const origEl = event.item;
const invalidDragItem = !origEl.parentElement;
Sortable.utils.toggleClass(origEl, DRAG_INVALID, invalidDragItem);
if (invalidDragItem) {
// The item being dragged has no parent container since the zone has been removed
// or the items have been re-rendered. This makes the drag item invalid
origEl.style.display = 'none';
this.trace('onClone -Invalid drag');
}
};

onEnd = (event: SortableEvent): void => {
try {
const draggedElement = event.item; // dragged HTMLElement
const targetZone = event.to; // target list
const sourceZone = event.from; // previous list

// Check if there is an invalid item being dragged
// and prevent it being dropped anywhere
if (Sortable.utils.is(draggedElement, '.' + DRAG_INVALID)) {
this.trace('onEnd - Invalid drop');
return;
}

const newPosition = event.newDraggableIndex; // element's new index within new parent, only counting draggable elements
const itemId = draggedElement.getAttribute(RECORD_ID_ATTRIBUTE) as string;
const targetZoneId = this.getZoneId(targetZone);
Expand All @@ -476,12 +517,16 @@ export class PowerDragDrop implements ComponentFramework.StandardControl<IInputs
}
};

onUnChoose = (): void => {
onUnChoose = (event: SortableEvent): void => {
this.currentItemZone = null;
this.trace('onUnChoose', this.context.parameters.DropZoneID.raw, event.item.innerText);
event.item.removeAttribute(DRAGGED_FROM_ZONE_ATTRIBUTE);
};

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

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

0 comments on commit 353f1b9

Please sign in to comment.