Skip to content

Commit 3e04727

Browse files
danilsomsikovDevtools-frontend LUCI CQ
authored andcommitted
Support creating new entries in the data grid web component.
Bug: 394287937 Change-Id: Ic402bbdefd83a370b3b414dd31ba8e30a0f72849 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6239892 Reviewed-by: Benedikt Meurer <[email protected]> Commit-Queue: Danil Somsikov <[email protected]>
1 parent 053666e commit 3e04727

File tree

5 files changed

+123
-36
lines changed

5 files changed

+123
-36
lines changed

front_end/ui/legacy/components/data_grid/DataGrid.ts

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,8 @@ const elementToIndexMap = new WeakMap<Element, number>();
131131
export class DataGridImpl<T> extends Common.ObjectWrapper.ObjectWrapper<EventTypes<T>> {
132132
element: HTMLDivElement;
133133
displayName: string;
134-
editCallback: ((arg0: any, arg1: string, arg2: any, arg3: any) => void)|undefined;
134+
editCallback:
135+
((node: any, columnId: string, valueBeforeEditing: any, newText: any, moveDirection?: string) => void)|undefined;
135136
deleteCallback: ((arg0: any) => void)|undefined;
136137
private readonly refreshCallback: (() => void)|undefined;
137138
private dataTableHeaders: {
@@ -169,7 +170,7 @@ export class DataGridImpl<T> extends Common.ObjectWrapper.ObjectWrapper<EventTyp
169170
private rootNodeInternal?: DataGridNode<T>;
170171
private editingNode?: DataGridNode<T>|null;
171172
private columnWeightsSetting?: Common.Settings.Setting<any>;
172-
creationNode?: CreationDataGridNode<any>;
173+
creationNode?: DataGridNode<any>;
173174
private currentResizer?: EventTarget|null;
174175
private dataGridWidget?: any;
175176

@@ -775,7 +776,7 @@ export class DataGridImpl<T> extends Common.ObjectWrapper.ObjectWrapper<EventTyp
775776
}
776777
// Make the callback - expects an editing node (table row), the column number that is being edited,
777778
// the text that used to be there, and the new text.
778-
this.editCallback(this.editingNode, columnId, valueBeforeEditing, newText);
779+
this.editCallback(this.editingNode, columnId, valueBeforeEditing, newText, moveDirection);
779780

780781
if (this.editingNode instanceof CreationDataGridNode && this.editingNode.isCreationNode) {
781782
this.addCreationNode(false);
@@ -1119,7 +1120,7 @@ export class DataGridImpl<T> extends Common.ObjectWrapper.ObjectWrapper<EventTyp
11191120

11201121
addCreationNode(hasChildren?: boolean): void {
11211122
if (this.creationNode) {
1122-
this.creationNode.makeNormal();
1123+
this.creationNode.isCreationNode = false;
11231124
}
11241125
const emptyData: {
11251126
[x: string]: any,
@@ -1262,8 +1263,7 @@ export class DataGridImpl<T> extends Common.ObjectWrapper.ObjectWrapper<EventTyp
12621263
while (nextSelectedNode && !nextSelectedNode.selectable) {
12631264
nextSelectedNode = nextSelectedNode.traverseNextNode(true);
12641265
}
1265-
const isCreationNode = nextSelectedNode instanceof CreationDataGridNode && nextSelectedNode.isCreationNode;
1266-
if (!nextSelectedNode || isCreationNode) {
1266+
if (!nextSelectedNode || nextSelectedNode.isCreationNode) {
12671267
if (!root) {
12681268
return;
12691269
}
@@ -2481,10 +2481,6 @@ export class CreationDataGridNode<T> extends DataGridNode<T> {
24812481
super(data, hasChildren);
24822482
this.isCreationNode = true;
24832483
}
2484-
2485-
makeNormal(): void {
2486-
this.isCreationNode = false;
2487-
}
24882484
}
24892485

24902486
export class DataGridWidget<T> extends UI.Widget.VBox {
@@ -2609,7 +2605,7 @@ customElements.define('devtools-data-grid-widget', DataGridWidgetElement);
26092605
export interface Parameters {
26102606
displayName: string;
26112607
columns: ColumnDescriptor[];
2612-
editCallback?: ((arg0: any, arg1: string, arg2: any, arg3: any) => void);
2608+
editCallback?: ((node: any, columnId: string, valueBeforeEditing: any, newText: any, moveDirection?: string) => void);
26132609
deleteCallback?: ((arg0: any) => void);
26142610
refreshCallback?: (() => void);
26152611
}

front_end/ui/legacy/components/data_grid/DataGridElement.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,4 +231,39 @@ describeWithEnvironment('DataGrid', () => {
231231
assert.strictEqual(editCallback.firstCall.args[0].detail.valueBeforeEditing, 'Value 1');
232232
assert.strictEqual(editCallback.firstCall.args[0].detail.newText, 'New Value');
233233
});
234+
235+
it('supports creation node', async () => {
236+
const createCallback = sinon.stub();
237+
const editCallback = sinon.stub();
238+
const element = await renderDataGrid(html`
239+
<devtools-data-grid striped name=${'Display Name'}
240+
@create=${createCallback as Function}
241+
@edit=${editCallback as Function}>
242+
<table>
243+
<tr>
244+
<th id="column-1" editable>Column 1</th>
245+
<th id="column-2" editable>Column 2</th>
246+
</tr>
247+
<tr>
248+
<td>Value 1</td>
249+
<td>Value 2</td>
250+
</tr>
251+
<tr placeholder>
252+
</tr>
253+
</table>
254+
</devtools-data-grid>`);
255+
sendKeydown(element, 'ArrowDown');
256+
sendKeydown(element, 'ArrowDown');
257+
sendKeydown(element, 'Enter');
258+
getFocusedElement()!.textContent = 'New Value 1';
259+
sendKeydown(element, 'Tab');
260+
assert.isFalse(editCallback.called);
261+
assert.isFalse(createCallback.called);
262+
getFocusedElement()!.textContent = 'New Value 2';
263+
sendKeydown(element, 'Tab');
264+
assert.isFalse(editCallback.called);
265+
assert.isTrue(createCallback.calledOnce);
266+
assert.deepEqual(createCallback.firstCall.args[0].detail,
267+
{'column-1': 'New Value 1', 'column-2': 'New Value 2'});
268+
});
234269
});

front_end/ui/legacy/components/data_grid/DataGridElement.ts

Lines changed: 73 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ class DataGridElement extends HTMLElement {
5454
#columnsOrder: string[] = [];
5555
#hideableColumns = new Set<string>();
5656
#hiddenColumns = new Set<string>();
57+
#usedCreationNode: DataGridElementNode|null = null;
5758

5859
constructor() {
5960
super();
@@ -230,37 +231,37 @@ class DataGridElement extends HTMLElement {
230231
}
231232
return [] as HTMLElement[];
232233
})
233-
.filter(node => node.querySelector('td'));
234+
.filter(node => node.querySelector('td') && !hasBooleanAttribute(node, 'placeholder'));
235+
}
236+
237+
#findNextExistingNode(element: Element): DataGridElementNode|null {
238+
for (let e = element.nextElementSibling; e; e = e.nextElementSibling) {
239+
const nextNode = DataGridElementNode.get(e);
240+
if (nextNode) {
241+
return nextNode;
242+
}
243+
}
244+
return null;
234245
}
235246

236247
#addNodes(nodes: NodeList): void {
237248
for (const element of this.#getDataRows(nodes)) {
238-
if (!element.querySelector('td')) {
239-
continue;
240-
}
241-
if (element instanceof HTMLTableRowElement && element.querySelector('td')) {
242-
const parentNode = this.#dataGrid.rootNode(); // TODO(dsv): support nested nodes
243-
const nextNode = element.nextElementSibling ? DataGridElementNode.get(element.nextElementSibling) : null;
244-
const index = nextNode ? parentNode.children.indexOf(nextNode) : parentNode.children.length;
245-
const node = new DataGridElementNode(element, this);
246-
parentNode.insertChild(node, index);
247-
if (hasBooleanAttribute(element, 'selected')) {
248-
node.select();
249-
}
249+
const parentNode = this.#dataGrid.rootNode(); // TODO(dsv): support nested nodes
250+
const nextNode = this.#findNextExistingNode(element);
251+
const index = nextNode ? parentNode.children.indexOf(nextNode) : parentNode.children.length;
252+
const node = new DataGridElementNode(element, this);
253+
parentNode.insertChild(node, index);
254+
if (hasBooleanAttribute(element, 'selected')) {
255+
node.select();
250256
}
251257
}
252258
}
253259

254260
#removeNodes(nodes: NodeList): void {
255261
for (const element of this.#getDataRows(nodes)) {
256-
if (!element.querySelector('td')) {
257-
continue;
258-
}
259-
if (element instanceof HTMLTableRowElement && element.querySelector('td')) {
260-
const node = DataGridElementNode.get(element);
261-
if (node) {
262-
node.remove();
263-
}
262+
const node = DataGridElementNode.get(element);
263+
if (node) {
264+
node.remove();
264265
}
265266
}
266267
}
@@ -281,10 +282,29 @@ class DataGridElement extends HTMLElement {
281282
}
282283
}
283284

285+
#updateCreationNode(): void {
286+
if (this.#usedCreationNode) {
287+
DataGridElementNode.remove(this.#usedCreationNode);
288+
this.#usedCreationNode = null;
289+
this.#dataGrid.creationNode = undefined;
290+
}
291+
const placeholder = this.querySelector('tr[placeholder]');
292+
if (!placeholder) {
293+
this.#dataGrid.creationNode?.remove();
294+
this.#dataGrid.creationNode = undefined;
295+
} else if (!DataGridElementNode.get(placeholder)) {
296+
this.#dataGrid.creationNode?.remove();
297+
const node = new DataGridElementNode(placeholder, this);
298+
this.#dataGrid.creationNode = node;
299+
this.#dataGrid.rootNode().appendChild(node);
300+
}
301+
}
302+
284303
#onChange(mutationList: MutationRecord[]): void {
285304
if (this.#needUpdateColumns(mutationList)) {
286305
this.#updateColumns();
287306
}
307+
this.#updateCreationNode();
288308

289309
for (const mutation of mutationList) {
290310
this.#removeNodes(mutation.removedNodes);
@@ -293,7 +313,21 @@ class DataGridElement extends HTMLElement {
293313
}
294314
}
295315

296-
#editCallback(node: DataGridElementNode, columnId: string, valueBeforeEditing: string, newText: string): void {
316+
#editCallback(
317+
node: DataGridElementNode, columnId: string, valueBeforeEditing: string, newText: string,
318+
moveDirection?: string): void {
319+
if (node.isCreationNode) {
320+
this.#usedCreationNode = node;
321+
if (moveDirection === 'forward') {
322+
const hasNextEditableColumn = this.columnsOrder.slice(this.columnsOrder.indexOf(columnId) + 1)
323+
.some(columnId => this.#dataGrid.columns[columnId].editable);
324+
if (!hasNextEditableColumn) {
325+
node.deselect();
326+
}
327+
}
328+
return;
329+
}
330+
297331
this.dispatchEvent(
298332
new CustomEvent('edit', {detail: {node: node.configElement, columnId, valueBeforeEditing, newText}}));
299333
}
@@ -313,6 +347,7 @@ class DataGridElementNode extends SortableDataGridNode<DataGridElementNode> {
313347
DataGridElementNode.#elementToNode.set(configElement, this);
314348
this.#dataGridElement = dataGridElement;
315349
this.#updateData();
350+
this.isCreationNode = hasBooleanAttribute(this.#configElement, 'placeholder');
316351
}
317352

318353
static get(configElement: Element|undefined): DataGridElementNode|undefined {
@@ -372,12 +407,15 @@ class DataGridElementNode extends SortableDataGridNode<DataGridElementNode> {
372407
}
373408

374409
override createCell(columnId: string): HTMLElement {
410+
const cell = this.createTD(columnId);
411+
if (this.isCreationNode) {
412+
return cell;
413+
}
375414
const index = this.#dataGridElement.columnsOrder.indexOf(columnId);
376415
const configCell = this.#configElement.querySelectorAll('td')[index];
377416
if (!configCell) {
378417
throw new Error(`Column ${columnId} not found in the data grid`);
379418
}
380-
const cell = this.createTD(columnId);
381419
for (const child of configCell.childNodes) {
382420
cell.appendChild(child.cloneNode(true));
383421
}
@@ -391,6 +429,18 @@ class DataGridElementNode extends SortableDataGridNode<DataGridElementNode> {
391429

392430
return cell;
393431
}
432+
433+
static remove(node: DataGridElementNode): void {
434+
DataGridElementNode.#elementToNode.delete(node.#configElement);
435+
node.remove();
436+
}
437+
438+
override deselect(): void {
439+
super.deselect();
440+
if (this.isCreationNode) {
441+
this.#dataGridElement.dispatchEvent(new CustomEvent('create', {detail: this.data}));
442+
}
443+
}
394444
}
395445

396446
customElements.define('devtools-data-grid', DataGridElement);

front_end/ui/legacy/components/data_grid/SortableDataGrid.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ export class SortableDataGrid<T> extends ViewportDataGrid<SortableDataGridNode<T
4343
static Comparator<T>(
4444
comparator: (arg0: SortableDataGridNode<T>, arg1: SortableDataGridNode<T>) => number, reverseMode: boolean,
4545
a: SortableDataGridNode<T>, b: SortableDataGridNode<T>): number {
46+
if (a.isCreationNode && !b.isCreationNode) {
47+
return 1;
48+
}
49+
if (!a.isCreationNode && b.isCreationNode) {
50+
return -1;
51+
}
4652
return reverseMode ? comparator(b, a) : comparator(a, b);
4753
}
4854

front_end/ui/legacy/components/data_grid/ViewportDataGrid.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -424,10 +424,10 @@ export class ViewportDataGridNode<T> extends DataGridNode<ViewportDataGridNode<T
424424
}
425425

426426
override removeChild(child: DataGridNode<ViewportDataGridNode<T>>): void {
427-
this.clearFlatNodes();
428427
if (this.dataGrid) {
429428
this.dataGrid.updateSelectionBeforeRemoval(child, false);
430429
}
430+
this.clearFlatNodes();
431431
if (child.previousSibling) {
432432
child.previousSibling.nextSibling = child.nextSibling;
433433
}
@@ -450,10 +450,10 @@ export class ViewportDataGridNode<T> extends DataGridNode<ViewportDataGridNode<T
450450
}
451451

452452
override removeChildren(): void {
453-
this.clearFlatNodes();
454453
if (this.dataGrid) {
455454
this.dataGrid.updateSelectionBeforeRemoval(this, true);
456455
}
456+
this.clearFlatNodes();
457457
for (let i = 0; i < this.children.length; ++i) {
458458
(this.children[i] as ViewportDataGridNode<T>).unlink();
459459
}

0 commit comments

Comments
 (0)