Row moves
rowDrag() from @dzgrid/dom/row-drag lets the user move rows: drag the grip at the start of a cell, or press
Alt+Shift+↑ / ↓ (Option+Shift on a Mac), Alt+Shift+Home / End, and in a tree Alt+Shift+→ / ← to indent or outdent; or
use the right-click menu. Texts -row-drag, CSS row-drag.css. The grip takes mouse, pen and finger; the keys make
dragging never the only way.
The grid tells you where the rows go with a rowMove event; your data decides. moveRows from
@dzgrid/core/row-drag applies a move to your array (or tree of parent ids) and returns a new one.
Drag a chore by its grip, or focus it and press Alt+Shift+↓; right-click for « Move up », « Move down »…
import { createGrid, type GridMessages } from '@dzgrid/dom';import { moveRows } from '@dzgrid/core/row-drag';import { rowDrag } from '@dzgrid/dom/row-drag';import { contextMenu } from '@dzgrid/dom/context-menu';import { EN } from '@dzgrid/dom/locales/en';import { EN_ROW_DRAG } from '@dzgrid/dom/locales/en-row-drag';import { EN_CONTEXT_MENU } from '@dzgrid/dom/locales/en-context-menu';import '@dzgrid/theme/dzgrid.css';import '@dzgrid/theme/menu.css';import '@dzgrid/theme/row-drag.css';
type Chore = { id: string; title: string; done: boolean };
let chores: Chore[] = [ { id: 'c1', title: 'Wind the clock tower', done: false }, { id: 'c2', title: 'Oil the weather vane', done: true }, { id: 'c3', title: 'Count the pigeons', done: false },];const id = (c: Chore) => c.id;const messages: GridMessages = { ...EN, text: { ...EN.text, ...EN_ROW_DRAG.text, ...EN_CONTEXT_MENU.text } };
const moves = rowDrag<Chore>({ column: 'title', canDrag: (c) => !c.done });const grid = createGrid<Chore>(document.getElementById('chores')!, { rows: chores, columns: [{ id: 'title', header: 'Chore', flex: 1 }, { id: 'done', header: 'Done', type: 'boolean' }], getRowId: id, messages, selection: { mode: 'multi' }, features: [moves, contextMenu<Chore>({ items: (c, defaults) => [...defaults, { separator: true }, ...moves.menuItems(c)] })], onEvent: (e) => { if (e.type === 'rowMove' && !e.applied) { chores = moveRows(chores, e, id); grid.update({ rows: chores }); } },});import { Component, signal } from '@angular/core';import { DzGrid, type DzgColumnOrGroup } from '@dzgrid/angular';import type { GridEvent } from '@dzgrid/core';import { moveRows } from '@dzgrid/core/row-drag';import { rowDrag } from '@dzgrid/dom/row-drag';import { contextMenu } from '@dzgrid/dom/context-menu';import { EN } from '@dzgrid/dom/locales/en';import { EN_ROW_DRAG } from '@dzgrid/dom/locales/en-row-drag';import { EN_CONTEXT_MENU } from '@dzgrid/dom/locales/en-context-menu';
type Chore = { id: string; title: string; done: boolean };type Move = Extract<GridEvent<Chore>, { type: 'rowMove' }>;
const id = (c: Chore) => c.id;const moves = rowDrag<Chore>({ column: 'title', canDrag: (c) => !c.done });
@Component({ selector: 'app-chores', imports: [DzGrid], template: `<dz-grid [rows]="chores()" [columns]="columns" [getRowId]="rowId" [messages]="messages" [selection]="{ mode: 'multi' }" [features]="features" (rowMove)="move($event)" />`,})export class Chores { // In a store (NgRx, a signal), the move is applied where the state lives. readonly chores = signal<Chore[]>([ { id: 'c1', title: 'Wind the clock tower', done: false }, { id: 'c2', title: 'Oil the weather vane', done: true }, { id: 'c3', title: 'Count the pigeons', done: false }, ]); readonly columns: DzgColumnOrGroup<Chore>[] = [{ id: 'title', header: 'Chore', flex: 1 }, { id: 'done', header: 'Done', type: 'boolean' }]; readonly rowId = id; readonly messages = { ...EN, text: { ...EN.text, ...EN_ROW_DRAG.text, ...EN_CONTEXT_MENU.text } }; readonly features = [moves, contextMenu<Chore>({ items: (c, defaults) => [...defaults, { separator: true }, ...moves.menuItems(c)] })];
move(e: Move) { if (!e.applied) this.chores.update((rows) => moveRows(rows, e, id)); }}import '@dzgrid/element/define';import type { DzGridElement } from '@dzgrid/element';import type { GridEvent } from '@dzgrid/core';import { moveRows } from '@dzgrid/core/row-drag';import { rowDrag } from '@dzgrid/dom/row-drag';import { contextMenu } from '@dzgrid/dom/context-menu';import { EN } from '@dzgrid/dom/locales/en';import { EN_ROW_DRAG } from '@dzgrid/dom/locales/en-row-drag';import { EN_CONTEXT_MENU } from '@dzgrid/dom/locales/en-context-menu';
type Chore = { id: string; title: string; done: boolean };
let chores: Chore[] = [ { id: 'c1', title: 'Wind the clock tower', done: false }, { id: 'c2', title: 'Oil the weather vane', done: true }, { id: 'c3', title: 'Count the pigeons', done: false },];const id = (c: Chore) => c.id;const moves = rowDrag<Chore>({ column: 'title', canDrag: (c) => !c.done });const grid = document.querySelector<DzGridElement<Chore>>('dz-grid')!;grid.columns = [{ id: 'title', header: 'Chore', flex: 1 }, { id: 'done', header: 'Done', type: 'boolean' }];grid.getRowId = id;grid.messages = { ...EN, text: { ...EN.text, ...EN_ROW_DRAG.text, ...EN_CONTEXT_MENU.text } };grid.features = [moves, contextMenu<Chore>({ items: (c, defaults) => [...defaults, { separator: true }, ...moves.menuItems(c)] })];grid.settings = { selection: { mode: 'multi' } };grid.rows = chores;
grid.addEventListener('dzg-row-move', (e) => { const event = (e as CustomEvent<GridEvent<Chore>>).detail; if (event.type === 'rowMove' && !event.applied) { chores = moveRows(chores, event, id); grid.rows = chores; }});apply: truelets the grid reorder flat browser rows itself (the event then saysapplied: true); with a store (NgRx, signals) leave it off and apply the move where your state lives. The demo above usesapply: true.- The grip of a selected row drags every selected row shown.
canDrag(row)andcanDrop({ keys, rows, target, parent })refuse a move;maxRowscaps how many rows move at once.- While the rows are sorted or grouped, a move is refused and the grid says why;
whenSorted: 'allow'sends the event anyway and lets you decide. - With a server source the grid only sends the event (
applyis ignored): save the new order on the server, then callreload()(refresh()only paints). - In a tree, a row can be dropped inside another (
target.where: 'inside');moveRows(rows, move, id, { parentId, setParent })rewrites the parent ids for you. - In Angular, the
(rowMove)output carries the same event, andctrl.rowDrag()the api (move,step).
Every option of rowDrag() is on its page.
Between grids, or onto your own element
Section titled “Between grids, or onto your own element”moves.api(grid)?.dropTargets.add(element, { accept(keys), drop(keys) }) makes any element a place to drop rows: a
bin, a folder list, another grid’s box. Moving rows from one grid to another is two updates of your own data: take
them out of one array and add them to the other.