Skip to content

Commit

Permalink
introduce DataTree
Browse files Browse the repository at this point in the history
  • Loading branch information
joaomoreno committed Dec 17, 2018
1 parent 5fae692 commit 12682da
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 1 deletion.
60 changes: 60 additions & 0 deletions src/vs/base/browser/ui/tree/dataTree.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { AbstractTree, IAbstractTreeOptions } from 'vs/base/browser/ui/tree/abstractTree';
import { ISpliceable } from 'vs/base/common/sequence';
import { ITreeNode, ITreeModel, ITreeElement, ITreeRenderer, ITreeSorter, IDataSource } from 'vs/base/browser/ui/tree/tree';
import { ObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel';
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { Iterator } from 'vs/base/common/iterator';

export interface IDataTreeOptions<T, TFilterData = void> extends IAbstractTreeOptions<T, TFilterData> {
sorter?: ITreeSorter<T>;
}

export class DataTree<TInput, T, TFilterData = void> extends AbstractTree<T | null, TFilterData, TInput | T> {

protected model: ObjectTreeModel<T | null, TFilterData>;

private _input: TInput | undefined;

get input(): TInput | undefined {
return this._input;
}

set input(input: TInput | undefined) {
this._input = input;
this.refresh(input);
}

constructor(
container: HTMLElement,
delegate: IListVirtualDelegate<T>,
renderers: ITreeRenderer<any /* TODO@joao */, TFilterData, any>[],
private dataSource: IDataSource<TInput, T>,
options: IDataTreeOptions<T, TFilterData> = {}
) {
super(container, delegate, renderers, options);
}

refresh(element: TInput | T): void {
if (!this._input) {
throw new Error('Tree input not set');
}

this.model.setChildren((element === this.input ? null : element) as T, this.createIterator(element));
}

private createIterator(element: TInput | T): Iterator<ITreeElement<T>> {
return Iterator.map(Iterator.fromArray(this.dataSource.getChildren(element)), element => ({
element,
children: this.createIterator(element)
}));
}

protected createModel(view: ISpliceable<ITreeNode<T, TFilterData>>, options: IDataTreeOptions<T, TFilterData>): ITreeModel<T | null, TFilterData, T | null> {
return new ObjectTreeModel(view, options);
}
}
4 changes: 4 additions & 0 deletions src/vs/base/browser/ui/tree/tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@ export interface ITreeNavigator<T> {
next(): T | null;
}

export interface IDataSource<TInput, T> {
getChildren(element: TInput | T): T[];
}

export interface IAsyncDataSource<T extends NonNullable<any>> {
hasChildren(element: T | null): boolean;
getChildren(element: T | null): T[] | Promise<T[]>;
Expand Down
75 changes: 74 additions & 1 deletion test/tree/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@

require.config({ baseUrl: '/static' });

require(['vs/base/browser/ui/tree/indexTree', 'vs/base/browser/ui/tree/asyncDataTree', 'vs/base/browser/ui/tree/tree', 'vs/base/common/iterator'], ({ IndexTree }, { AsyncDataTree }, { TreeVisibility }, { iter }) => {
require(['vs/base/browser/ui/tree/indexTree', 'vs/base/browser/ui/tree/asyncDataTree', 'vs/base/browser/ui/tree/dataTree', 'vs/base/browser/ui/tree/tree', 'vs/base/common/iterator'], ({ IndexTree }, { AsyncDataTree }, { DataTree }, { TreeVisibility }, { iter }) => {
function createIndexTree(opts) {
opts = opts || {};

Expand Down Expand Up @@ -182,6 +182,69 @@
return { tree, treeFilter };
}

function createDataTree() {
const delegate = {
getHeight() { return 22; },
getTemplateId() { return 'template'; }
};

const renderer = {
templateId: 'template',
renderTemplate(container) { return container; },
renderElement(node, index, container) { container.textContent = node.element.name; },
disposeElement() { },
disposeTemplate() { }
};

const treeFilter = new class {
constructor() {
this.pattern = null;
let timeout;
filter.oninput = () => {
clearTimeout(timeout);
timeout = setTimeout(() => this.updatePattern(), 300);
};
}

updatePattern() {
if (!filter.value) {
this.pattern = null;
} else {
this.pattern = new RegExp(filter.value, 'i');
}

perf('refilter', () => tree.refilter());
}
filter(el) {
return (this.pattern ? this.pattern.test(el.name) : true) ? TreeVisibility.Visible : TreeVisibility.Recurse;
}
};

const dataSource = new class {
getChildren(element) {
return element.children || [];
}
};

const identityProvider = {
getId(node) {
return node.name;
}
};

const tree = new DataTree(container, delegate, [renderer], dataSource, { filter: treeFilter, identityProvider });

tree.input = {
children: [
{ name: 'A', children: [{ name: 'AA' }, { name: 'AB' }] },
{ name: 'B', children: [{ name: 'BA', children: [{ name: 'BAA' }] }, { name: 'BB' }] },
{ name: 'C' }
]
};

return { tree, treeFilter };
}

switch (location.search) {
case '?problems': {
const { tree, treeFilter } = createIndexTree();
Expand Down Expand Up @@ -216,6 +279,16 @@

break;
}
case '?objectdata': {
const { tree, treeFilter } = createDataTree();

expandall.onclick = () => perf('expand all', () => tree.expandAll());
collapseall.onclick = () => perf('collapse all', () => tree.collapseAll());
renderwidth.onclick = () => perf('renderwidth', () => tree.layoutWidth(Math.random()));
refresh.onclick = () => perf('refresh', () => tree.refresh(null, true));

break;
}
case '?height': {
const { tree, treeFilter } = createIndexTree({ supportDynamicHeights: true });

Expand Down

0 comments on commit 12682da

Please sign in to comment.