Remote Lookup Editor
Double-click a value to edit. Apply or Ctrl/Cmd+Enter saves the draft. Cancel, Escape or outside dismissal discards it. Read-only cells cannot open or commit an editor.
Source code
<template>
<p>Double-click a value to edit. Apply saves; Cancel discards the draft.</p>
<RevoGrid
:source="source"
:columns="columns"
:plugins="plugins"
:theme="isDark ? 'darkMaterial' : 'material'"
style="height: 340px"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { currentThemeVue } from '../composables/useRandomData';
import RevoGrid from '@revolist/vue3-datagrid';
import { editorColumns, editorRows } from './editor-data';
const { isDark } = currentThemeVue();
const source = ref(editorRows());
const columns = editorColumns();
const plugins = [];
</script>
import { currentTheme } from '../composables/useRandomData';
import { defineCustomElements } from '@revolist/revogrid/loader';
import { editorColumns, editorRows } from './editor-data';
defineCustomElements();
export function load(parentSelector: string, rows = editorRows()) {
const grid = document.createElement('revo-grid');
grid.theme = currentTheme().isDark() ? 'darkMaterial' : 'material';
grid.style.height = '340px';
grid.columns = editorColumns();
document.querySelector(parentSelector)?.append(grid);
grid.source = rows;
return () => grid.remove();
}
import { currentTheme } from '../composables/useRandomData';
import { useMemo } from 'react';
import { RevoGrid } from '@revolist/react-datagrid';
import { editorColumns, editorRows } from './editor-data';
export default function EditorLookup({ rows }: { rows?: any[] }) {
const columns = useMemo(editorColumns, []);
const fallback = useMemo(editorRows, []);
const plugins = useMemo(() => [], []);
return (
<>
<p>
Double-click a value to edit. Apply saves; Cancel discards the draft.
</p>
<RevoGrid
source={rows?.length ? rows : fallback}
columns={columns}
plugins={plugins}
theme={currentTheme().isDark() ? 'darkMaterial' : 'material'}
style={{ height: '340px' }}
/>
</>
);
}
import { currentTheme } from '../composables/useRandomData';
import { Component, ViewEncapsulation } from '@angular/core';
import { RevoGrid } from '@revolist/angular-datagrid';
import { editorColumns, editorRows } from './editor-data';
@Component({
selector: 'editor-lookup-grid',
standalone: true,
imports: [RevoGrid],
encapsulation: ViewEncapsulation.None,
template: `<p>
Double-click a value to edit. Apply saves; Cancel discards the draft.
</p>
<revo-grid
[source]="source"
[columns]="columns"
[plugins]="plugins"
[theme]="theme"
style="height:340px"
></revo-grid>`,
})
export class EditorLookupGridComponent {
theme = currentTheme().isDark() ? 'darkMaterial' : 'material';
source = editorRows();
columns = editorColumns();
plugins = [];
}
import type { ColumnRegular } from '@revolist/revogrid';
import { LookupEditor } from '@revolist/revogrid-pro';
const records = Array.from({ length: 160 }, (_, index) => ({
value: index + 1,
label: `Project ${String(index + 1).padStart(3, '0')}`,
}));
const dataSource = {
async search({
query,
cursor,
pageSize,
signal,
}: {
query: string;
cursor?: string | number;
pageSize: number;
signal: AbortSignal;
}) {
if (signal.aborted) throw new DOMException('Aborted', 'AbortError');
const matches = records.filter((item) =>
item.label.toLowerCase().includes(query.toLowerCase()),
);
const offset = Number(cursor ?? 0);
return {
items: matches.slice(offset, offset + pageSize),
nextCursor:
offset + pageSize < matches.length ? offset + pageSize : undefined,
};
},
async resolve({
ids,
signal,
}: {
ids: readonly (string | number)[];
signal: AbortSignal;
}) {
if (signal.aborted) throw new DOMException('Aborted', 'AbortError');
return records.filter((item) => ids.includes(item.value));
},
};
export const editorRows = () => [
{ name: 'Planning', value: 1, related: [1, 2] },
{ name: 'Implementation', value: 8, related: [8, 12] },
{ name: 'Unresolved record', value: 999, related: [] },
];
export const editorColumns = (): ColumnRegular[] => [
{ prop: 'name', name: 'Task', size: 190, readonly: true },
{
prop: 'value',
name: 'Linked project',
size: 240,
editor: LookupEditor,
lookup: { dataSource },
},
{
prop: 'related',
name: 'Multiple projects',
size: 300,
editor: LookupEditor,
lookup: { dataSource, multiple: true },
},
];
Use LookupEditor to select existing records through application-owned asynchronous search and resolve callbacks. Store stable string or number IDs; labels and display metadata stay in options. Single selection clears to null; multiple selection clears to [].
Search debounces for 200 ms and requests 50 options per page. Results retain server ordering, load more pages on scrolling, abort obsolete requests and ignore stale responses. Saved IDs resolve separately; missing records remain visible as unavailable IDs. Selected chips persist across searches and pages. Loading, failure, retry and empty-state text can be localized through lookup.text.
Configure lookup.multiple, required, placeholder, context, source seed options, and renderOption/renderValue. A context callback receives the cell, so applications can scope requests by row or account. Applications own backend access and authorization; the editor does not create records.
Localize loading, empty, retry, error and unresolved-record text through lookup.text. The same object accepts popupLabel, label, placeholder, searchPlaceholder, searchLabel and removeLabel(label) for selection controls and chips. Partial lookup.actions overrides Apply, Cancel and Clear. Backend callbacks own authorization and data access; translations do not change stable record IDs.