blog
blog copied to clipboard
VS Code Extension API: Implementing paging in a Tree View
I found myself needing paging in a TreeView. Here's how I did it.
- I'm using JavaScript - sorry about that!
- My TreeView data is SQL driven.
Video: https://user-images.githubusercontent.com/3708366/137364458-bc9ad98e-b65d-46c9-9b01-7c32e90c44c6.mov
Base
Here's the base for the TreeView class (extending TreeDataProvider
). PAGE_SIZE
is how much to load in each fetch.
const PAGE_SIZE = 100;
module.exports = class UserList {
/**
* @param {vscode.ExtensionContext} context
*/
constructor(context) {
this.emitter = new vscode.EventEmitter();
this.onDidChangeTreeData = this.emitter.event;
/** @type {{[key: string]: object[]}} */
this.cache = {};
}
refresh() {
this.emitter.fire();
}
}
- The
cache
field is used to store TreeView data. - We use
refresh
to reload the table from thecache
- We pass in our extension context so we can register commands in the contructor specific to this TreeView
Fetching the data
/**
* @param {vscode.TreeItem|UserType} [element]
* @returns {Promise<vscode.TreeItem[]>};
*/
async getChildren(element) {
let items = [], item;
// UserType extends TreeView and adds `type`
if (element) {
items = await this.fetchUsers(element.type, false);
items.push(moreButton(element.type));
} else {
items = [
new UserType({title: `Admins`, type: `admin`}),
new UserType({title: `Regular`, type: `regular`})
]
}
return items;
}
-
getChildren
is a method on our class -
fetchUsers
returnsTreeItem[]
. We will define this soon -
moreButton
returnsTreeItem
More button
const moreButton = (type) => {
const item = new vscode.TreeItem(`More...`, vscode.TreeItemCollapsibleState.None);
item.iconPath = new vscode.ThemeIcon(`add`);
item.command = {
command: `myext.loadMoreUsers`,
title: `Load more`,
// @ts-ignore
arguments: [type]
};
return item;
}
- We pass the type in so we know what type of users we need to fetch
- We use the type for the
this.cache
key - We implement our command
myext.loadMoreUsers
later on
Fetching the data
/**
* @param {"admins"|"regular"} type The type of users we want to fetch
* @param {boolean} [addRows] Passing false/null will returning the existing cache, but if it is empty will fetch the first page
*/
async fetchUsers(type, addRows) {
const key = type;
let offset;
// Only fetch the rows if we have none or are looking for the next page
if (addRows || this.cache[key] === undefined) {
// The offset is basically the lenfth of the cache
offset = (this.cache[key] ? this.cache[key].length : 0);
// Fetch the data from our source
const data = await Users.get(type, {
limit: PAGE_SIZE,
offset
});
if (data.length > 0) {
// Here, I am mapping to UserItem, which is type of `TreeView`
const items = data.map(item => new UserItem(item));
if (this.cache[key]) {
this.cache[key].push(...items);
} else {
this.cache[key] = items;
}
} else {
vscode.window.showInformationMessage(`No more items to load.`);
}
}
// Return a copy
return this.cache[key].slice(0);
}
fetchUsers
can do two things:
- Return just the existing cache if there is one, otherwise fetch the first page, add it to the cache and return that.
- Return the next page, add it to the cache and then return it
Fetching the next page
Our 'More' button calls the following command, which is defined in the constructor
to make use of the extension context.
vscode.commands.registerCommand(`myext.loadMoreUsers`, async (type) => {
if (type) {
// Fetch the next page of data from the source
await this.fetchUsers(type, true);
// Refresh the TreeView, which collects data from the cache
this.refresh();
}
})
What is important to note here is that because refresh
invokes getChildren
, which in return calls fetchUsers
again. Luckily, the second time getChildren
calls fetchUsers
it won't reach out to the database. This is thanks to the addRows
parameter on fetchUsers
.