Skip to content

Instantly share code, notes, and snippets.

@worksofliam
Last active October 14, 2021 17:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save worksofliam/2566c4e72e5a7d2e307cf22a56255628 to your computer and use it in GitHub Desktop.
Save worksofliam/2566c4e72e5a7d2e307cf22a56255628 to your computer and use it in GitHub Desktop.
VS Code Extension: 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 the cache
  • 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 returns TreeItem[]. We will define this soon
  • moreButton returns TreeItem

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:

  1. Return just the existing cache if there is one, otherwise fetch the first page, add it to the cache and return that.
  2. 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment