diff --git a/articles/components/grid/drag-drop.adoc b/articles/components/grid/drag-drop.adoc index d6aae68ada..4b19ed24da 100644 --- a/articles/components/grid/drag-drop.adoc +++ b/articles/components/grid/drag-drop.adoc @@ -105,21 +105,21 @@ Drag-and-drop filters determine which rows are draggable and which rows are vali ifdef::lit[] [source,typescript] ---- -include::{root}/frontend/demo/component/grid/grid-drag-drop-filters.ts[render,tags=snippet,indent=0,group=Lit] +include::{root}/frontend/demo/component/tree-grid/tree-grid-drag-drop.ts[render,tags=snippet,indent=0,group=Lit] ---- endif::[] ifdef::flow[] [source,java] ---- -include::{root}/src/main/java/com/vaadin/demo/component/grid/GridDragDropFilters.java[render,tags=snippet,indent=0,group=Flow] +include::{root}/src/main/java/com/vaadin/demo/component/treegrid/TreeGridDragDrop.java[render,tags=snippet,indent=0,group=Flow] ---- endif::[] ifdef::react[] [source,tsx] ---- -include::{root}/frontend/demo/component/grid/react/grid-drag-drop-filters.tsx[render,tags=snippet,indent=0,group=React] +include::{root}/frontend/demo/component/tree-grid/react/tree-grid-drag-drop.tsx[render,tags=snippet,indent=0,group=React] ---- endif::[] -- diff --git a/frontend/demo/component/grid/grid-drag-drop-filters.ts b/frontend/demo/component/grid/grid-drag-drop-filters.ts deleted file mode 100644 index 9d648bfc1d..0000000000 --- a/frontend/demo/component/grid/grid-drag-drop-filters.ts +++ /dev/null @@ -1,120 +0,0 @@ -import 'Frontend/demo/init'; // hidden-source-line -import '@vaadin/grid'; -import '@vaadin/grid/vaadin-grid-tree-column.js'; -import { html, LitElement } from 'lit'; -import { customElement, query, state } from 'lit/decorators.js'; -import type { - Grid, - GridDataProviderCallback, - GridDataProviderParams, - GridDragStartEvent, - GridDropEvent, - GridExpandedItemsChangedEvent, - GridItemModel, -} from '@vaadin/grid'; -import { getPeople } from 'Frontend/demo/domain/DataService'; -import { applyTheme } from 'Frontend/demo/theme'; -import type Person from 'Frontend/generated/com/vaadin/demo/domain/Person'; - -// tag::snippet[] -@customElement('grid-drag-drop-filters') -export class Example extends LitElement { - protected override createRenderRoot() { - const root = super.createRenderRoot(); - applyTheme(root); - return root; - } - - @query('vaadin-grid') - private grid!: Grid; - - @state() - private draggedItem: Person | undefined; - - @state() - private items: Person[] = []; - - @state() - private managers: Person[] = []; - - @state() - private expandedItems: Person[] = []; - - protected override async firstUpdated() { - const { people } = await getPeople(); - this.items = people; - this.managers = this.items.filter((item) => item.manager); - // Avoid using this method - this.grid.clearCache(); - } - - private dataProvider = ( - params: GridDataProviderParams, - callback: GridDataProviderCallback - ) => { - const { page, pageSize, parentItem } = params; - const startIndex = page * pageSize; - const endIndex = startIndex + pageSize; - - /* - We cannot change the underlying data in this demo so this dataProvider uses - a local field to fetch its values. This allows us to keep a reference to the - modified list instead of loading a new list every time the dataProvider gets - called. In a real application, you should always access your data source - here and avoid using grid.clearCache() whenever possible. - */ - const result = parentItem - ? this.items.filter((item) => item.managerId === parentItem.id) - : this.managers.slice(startIndex, endIndex); - - callback(result, result.length); - }; - - protected override render() { - return html` - - - - - - `; - } -} -// end::snippet[] diff --git a/frontend/demo/component/grid/react/grid-drag-drop-filters.tsx b/frontend/demo/component/grid/react/grid-drag-drop-filters.tsx deleted file mode 100644 index adde4b2610..0000000000 --- a/frontend/demo/component/grid/react/grid-drag-drop-filters.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { reactExample } from 'Frontend/demo/react-example'; // hidden-source-line -import React, { useEffect, useMemo } from 'react'; -import { useSignals } from '@preact/signals-react/runtime'; // hidden-source-line -import { useSignal } from '@vaadin/hilla-react-signals'; -import { - Grid, - type GridDataProviderCallback, - type GridDataProviderParams, -} from '@vaadin/react-components/Grid.js'; -import { GridColumn } from '@vaadin/react-components/GridColumn.js'; -import { GridTreeColumn } from '@vaadin/react-components/GridTreeColumn.js'; -import { getPeople } from 'Frontend/demo/domain/DataService'; -import type Person from 'Frontend/generated/com/vaadin/demo/domain/Person'; - -function Example() { - useSignals(); // hidden-source-line - // tag::snippet[] - const draggedItem = useSignal(undefined); - const items = useSignal([]); - const expandedItems = useSignal([]); - - useEffect(() => { - getPeople().then(({ people }) => { - items.value = people; - }); - }, []); - - const dataProvider = useMemo( - () => (params: GridDataProviderParams, callback: GridDataProviderCallback) => { - const { page, pageSize, parentItem } = params; - const startIndex = page * pageSize; - const endIndex = startIndex + pageSize; - - /* - We cannot change the underlying data in this demo so this dataProvider uses - a local field to fetch its values. This allows us to keep a reference to the - modified list instead of loading a new list every time the dataProvider gets - called. In a real application, you should always access your data source - here and avoid using grid.clearCache() whenever possible. - */ - const result = parentItem - ? items.value.filter((item) => item.managerId === parentItem.id) - : items.value.filter((item) => item.manager).slice(startIndex, endIndex); - - callback(result, result.length); - }, - [items.value] - ); - - return ( - { - expandedItems.value = event.detail.value; - }} - rowsDraggable - dropMode={draggedItem.value ? 'on-top' : undefined} - onGridDragstart={(event) => { - draggedItem.value = event.detail.draggedItems[0]; - }} - onGridDragend={() => { - draggedItem.value = undefined; - }} - onGridDrop={(event) => { - const manager = event.detail.dropTargetItem; - if (draggedItem.value) { - draggedItem.value.managerId = manager.id; - items.value = [...items.value]; - } - }} - dragFilter={(model) => { - const { item } = model; - return !item.manager; - }} - dropFilter={(model) => { - const { item } = model; - return item.manager && item.id !== draggedItem.value?.managerId; - }} - > - - - - - ); - // end::snippet[] -} - -export default reactExample(Example); // hidden-source-line diff --git a/frontend/demo/component/tree-grid/react/tree-grid-drag-drop.tsx b/frontend/demo/component/tree-grid/react/tree-grid-drag-drop.tsx index adde4b2610..27ecc860b2 100644 --- a/frontend/demo/component/tree-grid/react/tree-grid-drag-drop.tsx +++ b/frontend/demo/component/tree-grid/react/tree-grid-drag-drop.tsx @@ -57,7 +57,6 @@ function Example() { expandedItems.value = event.detail.value; }} rowsDraggable - dropMode={draggedItem.value ? 'on-top' : undefined} onGridDragstart={(event) => { draggedItem.value = event.detail.draggedItems[0]; }} @@ -71,13 +70,17 @@ function Example() { items.value = [...items.value]; } }} + dropMode={draggedItem.value ? 'on-top' : undefined} dragFilter={(model) => { const { item } = model; - return !item.manager; + return !item.manager; // Only drag non-managers }} dropFilter={(model) => { const { item } = model; - return item.manager && item.id !== draggedItem.value?.managerId; + return ( + item.manager && // Can only drop on a supervisor + item.id !== draggedItem.value?.managerId // Disallow dropping on the same manager + ); }} > diff --git a/frontend/demo/component/tree-grid/tree-grid-drag-drop.ts b/frontend/demo/component/tree-grid/tree-grid-drag-drop.ts index ee3bcd40d5..ecd4f5dab4 100644 --- a/frontend/demo/component/tree-grid/tree-grid-drag-drop.ts +++ b/frontend/demo/component/tree-grid/tree-grid-drag-drop.ts @@ -75,14 +75,13 @@ export class Example extends LitElement { return html` managers; - private final Map> staffGroupedByMangers; - - private Person draggedItem; - - public GridDragDropFilters() { - - List people = DataService.getPeople(); - managers = people.stream().filter(Person::isManager).toList(); - staffGroupedByMangers = people.stream() - .filter(person -> person.getManagerId() != null) - .collect(Collectors.groupingBy(Person::getManagerId)); - - // tag::snippet[] - TreeGrid treeGrid = setupTreeGrid(); - - TreeData treeData = new TreeData<>(); - treeData.addItems(managers, this::getStaff); - TreeDataProvider treeDataProvider = new TreeDataProvider<>( - treeData); - treeGrid.setDataProvider(treeDataProvider); - treeGrid.setRowsDraggable(true); - - // Only allow dragging staff - treeGrid.setDragFilter(person -> !person.isManager()); - // Only allow dropping on managers - treeGrid.setDropFilter(person -> person.isManager()); - - treeGrid.addDragStartListener(e -> { - treeGrid.setDropMode(GridDropMode.ON_TOP); - draggedItem = e.getDraggedItems().get(0); - }); - - treeGrid.addDropListener(e -> { - Person newManager = e.getDropTargetItem().orElse(null); - boolean isSameManager = newManager != null - && newManager.getId().equals(draggedItem.getManagerId()); - - if (newManager == null || isSameManager) - return; - - draggedItem.setManagerId(newManager.getId()); - treeData.setParent(draggedItem, newManager); - - treeDataProvider.refreshAll(); - }); - - treeGrid.addDragEndListener(e -> { - treeGrid.setDropMode(null); - draggedItem = null; - }); - // end::snippet[] - - add(treeGrid); - } - - private static TreeGrid setupTreeGrid() { - TreeGrid treeGrid = new TreeGrid<>(); - treeGrid.addHierarchyColumn(Person::getFirstName) - .setHeader("First name"); - treeGrid.addColumn(Person::getLastName).setHeader("Last name"); - treeGrid.addColumn(Person::getEmail).setHeader("Email"); - - return treeGrid; - } - - private List getStaff(Person manager) { - return staffGroupedByMangers.getOrDefault(manager.getId(), - Collections.emptyList()); - } - - public static class Exporter // hidden-source-line - extends DemoExporter { // hidden-source-line - } // hidden-source-line -} diff --git a/src/main/java/com/vaadin/demo/component/treegrid/TreeGridDragDrop.java b/src/main/java/com/vaadin/demo/component/treegrid/TreeGridDragDrop.java index da499f2d61..be2fbb1e33 100644 --- a/src/main/java/com/vaadin/demo/component/treegrid/TreeGridDragDrop.java +++ b/src/main/java/com/vaadin/demo/component/treegrid/TreeGridDragDrop.java @@ -19,63 +19,68 @@ @Route("tree-grid-drag-drop") public class TreeGridDragDrop extends Div { - private final List managers; - private final Map> staffGroupedByMangers; + private final List people = DataService.getPeople(); + private final List managers = people.stream() + .filter(Person::isManager).toList(); + private final Map> staffByManagerId = people.stream() + .filter(person -> person.getManagerId() != null) + .collect(Collectors.groupingBy(Person::getManagerId)); private Person draggedItem; public TreeGridDragDrop() { - List people = DataService.getPeople(); - managers = people.stream().filter(Person::isManager).toList(); - staffGroupedByMangers = people.stream() - .filter(person -> person.getManagerId() != null) - .collect(Collectors.groupingBy(Person::getManagerId)); - // tag::snippet[] TreeGrid treeGrid = setupTreeGrid(); - TreeData treeData = new TreeData<>(); - treeData.addItems(managers, this::getStaff); + treeData.addItems(managers, this::getStaffByManager); // To preserve scroll position after refreshAll(), configure - // TreeDataProvider to return data in HierarchyFormat.FLATTENED – - // it supports this format out of the box. For custom data providers, - // you will need to implement this format manually, see the - // HierarchyFormat enum JavaDoc for guidance. + // TreeDataProvider to return data in the flattened hierarchy format. + // More details can be found in the JavaDoc of the HierarchyFormat enum. TreeDataProvider treeDataProvider = new TreeDataProvider<>( treeData, HierarchyFormat.FLATTENED); treeGrid.setDataProvider(treeDataProvider); - // Enable drag-and-drop + // Enable drag and drop treeGrid.setRowsDraggable(true); - // Only allow dragging staff + + // Set a filter to allow dragging staff only treeGrid.setDragFilter(person -> !person.isManager()); - // Only allow dropping on managers - treeGrid.setDropFilter(Person::isManager); treeGrid.addDragStartListener(e -> { - treeGrid.setDropMode(GridDropMode.ON_TOP); draggedItem = e.getDraggedItems().get(0); + + // Allow dropping on top of rows only + treeGrid.setDropMode(GridDropMode.ON_TOP); + + // Set a filter to define valid drop targets + // @formatter:off hidden-source-line + treeGrid.setDropFilter(person -> + // Allow dropping on supervisors only + person.isManager() && + // Disallow dropping on the same manager + !person.getId().equals(draggedItem.getManagerId())); + // @formatter:on hidden-source-line }); treeGrid.addDropListener(e -> { Person newManager = e.getDropTargetItem().orElse(null); - boolean isSameManager = newManager != null - && newManager.getId().equals(draggedItem.getManagerId()); - - if (newManager == null || isSameManager) + if (newManager == null) { return; + } draggedItem.setManagerId(newManager.getId()); treeData.setParent(draggedItem, newManager); - // Reset TreeGrid's cache to trigger a re-render + // Reset TreeGrid's cache to reflect the hierarchy changes in the UI treeDataProvider.refreshAll(); }); treeGrid.addDragEndListener(e -> { - treeGrid.setDropMode(null); draggedItem = null; + + treeGrid.setDropMode(null); + treeGrid.setDropFilter(null); }); // end::snippet[] @@ -92,8 +97,8 @@ private static TreeGrid setupTreeGrid() { return treeGrid; } - private List getStaff(Person manager) { - return staffGroupedByMangers.getOrDefault(manager.getId(), + private List getStaffByManager(Person manager) { + return staffByManagerId.getOrDefault(manager.getId(), Collections.emptyList()); }