Skip to content
Ghislain B edited this page Aug 29, 2021 · 18 revisions
updated doc to 3.x version

Index

Description

Tree Data allows you to display a hierarchical (tree) dataset into the grid, it is visually very similar to Grouping but also very different in its implementation. A hierarchical dataset is commonly used for a parent/child relation and a great example is a Bill of Material (BOM), which you can't do with Grouping because parent/child relationship could be infinite tree level while Grouping is a defined and known level of Grouping.

Important Note (data mutation)

For Tree Data to work with SlickGrid we need to mutate the original dataset, it will add a couple of new properties to your data, these properties are: __treeLevel, __parentId and children (these key names could be changed via the treeDataOptions). Also note that these properties become available in every Formatter (built-in and custom) which can be quite useful (especially in the tree level) in some cases... You might be thinking, could we do it without mutating the data? Probably but that would require to do a deep copy of the original data and that can be expensive on the performance side (no one it stopping you from doing a deep copy on your side though). The last thing to note is that internally for Tree Data to work, the lib always has 2 dataset (1x flat dataset and 1x hierarchical dataset which is basically a tree structure) and the lib keeps them in sync internally.

Demo

Demo Parent/Child Relationship / Component

Hierarchial Dataset / Component

Parent/Child Relation Dataset

This is the most common Tree Data to use, we only use that one in our projects, and requires you to provide a key representing the relation between the parent and children (typically a parentId, which the default key when nothing is provided).

For example, we can see below is that we have a regular flat dataset with items that have a parentId property which defines the relation between the parent and child.

View
  <angular-slickgrid gridId="grid1"
                     [columnDefinitions]="columnDefinitions"
                     [gridOptions]="gridOptions"
                     [dataset]="dataset"
                     (onAngularGridCreated)="angularGridReady($event.detail)">
  </angular-slickgrid>
dataset sample
this.dataset = [
  { id: 0, file: 'documents', parentId: null, },
  { id: 1, file: 'vacation.txt', parentId: 0, }, 
  { id: 2, file: 'bills.txt', parentId: 0, }, 
  { id: 55: file: 'music', parentId: null, },
  { id: 60, file: 'favorite-song.mp3', parentId: 55, }, 
  { id: 61, file: 'blues.mp3', parentId: 61, }, 
];

For the full list of options, refer to the treeDataOptions interface

define your grid
initializeGrid() {
  this.columnDefinitions = [
    {
      id: 'title', name: 'Title', field: 'title', width: 220, cssClass: 'cell-title',
      filterable: true, sortable: true, 
      formatter: Formatters.tree, exportCustomFormatter: Formatters.treeExport
    },
    // ...
  };

  this.gridOptions = {
    enableFiltering: true,  // <<-- REQUIRED, it won't work without filtering enabled
    multiColumnSort: false, // <<-- REQUIRED to be Disabled since multi-column sorting is not currently supported with Tree Data

    treeDataOptions: {
      columnId: 'title',           // the column where you will have the Tree with collapse/expand icons
      parentPropName: 'parentId',  // the parent/child key relation in your dataset
      levelPropName: 'treeLevel',  // optionally, you can define the tree level property name, it nothing is provided it will use "__treeLevel"
      indentMarginLeft: 15,        // optionally provide the indent spacer width in pixel, for example if you provide 10 and your tree level is 2 then it will have 20px of indentation
      exportIndentMarginLeft: 4,   // similar to `indentMarginLeft` but represent a space instead of pixels for the Export CSV/Excel

      // you can optionally sort by a different column and/or sort direction
      // this is the RECOMMENDED approach, unless you are 100% that your original array is already sorted (in most cases it's not)
      initialSort: {
        columnId: 'title',         // which column are we using to do the initial sort? it doesn't have to be the tree column, it could be any column
        direction: 'ASC'
      },
    },
  };
}

Hierarchical (Tree) Dataset

This is when your dataset is already in hierarchical (tree) structure, for example your items array already has a tree where the parents have a children property array that contains other items.

For example, we can see below the children are in the files array and the entire dataset is already in a hierarchical (tree) structure.

For the full list of options, refer to the treeDataOptions interface

View
  <angular-slickgrid gridId="grid1"
                     [columnDefinitions]="columnDefinitions"
                     [gridOptions]="gridOptions"
                     [datasetHierarchical]="datasetHierarchical"
                     (onAngularGridCreated)="angularGridReady($event.detail)">
  </angular-slickgrid>
dataset sample
this.datasetHierarchical = [
  { id: 0, file: 'documents', files: [
      { id: 1, file: 'vacation.txt', size: 12 }, 
      { id: 2, file: 'bills.txt', size: 0.5 }
    ] 
  },
  { id: 55: file: 'music', files: [
      { id: 60, file: 'favorite-song.mp3': size: 2.3 }, 
      { id: 61, file: 'blues.mp3', size: 5.5 }
    ] 
  },
];
define your grid
initializeGrid() {
  this.columnDefinitions = [
    {
      id: 'file', name: 'Files', field: 'file',
      type: FieldType.string, width: 150, formatter: this.treeFormatter,
      filterable: true, sortable: true,
    },
    // ...
  };

  this.gridOptions = {
    enableFiltering: true,  // <<-- REQUIRED, it won't work without filtering enabled
    multiColumnSort: false, // <<-- REQUIRED to be Disabled since multi-column sorting is not currently supported with Tree Data

    treeDataOptions: {
      columnId: 'file',           // the column where you will have the Tree with collapse/expand icons
      parentPropName: 'files',  // the parent/child key relation in your dataset
      levelPropName: 'treeLevel',  // optionally, you can define the tree level property name, it nothing is provided it will use "__treeLevel"
      
      // you can optionally sort by a different column and/or sort direction
      // this is the RECOMMENDED approach, unless you are 100% that your original array is already sorted (in most cases it's not)
      initialSort: {
        columnId: 'size',         // which column are we using to do the initial sort? it doesn't have to be the tree column, it could be any column
        direction: 'DESC'
      },
    },
  };
}

Tree Custom Title Formatter

The column with the Tree already has a Formatter, so how can we add our own Formatter without impacting the Tree collapse/expand icons? You can use the titleFormatter in your treeDataOptions, it will style the text title but won't impact the collapsing icons.

grid options configurations
this.gridOptions = {
  enableFiltering: true,  // <<-- REQUIRED, it won't work without filtering enabled
  multiColumnSort: false, // <<-- REQUIRED to be Disabled since multi-column sorting is not currently supported with Tree Data

  treeDataOptions: {
    columnId: 'title',           // the column where you will have the Tree with collapse/expand icons
    // ...

    // we can also add a Custom Formatter just for the title text portion
    titleFormatter: (_row, _cell, value, _def, dataContext) => {
      let prefix = '';
      if (dataContext.treeLevel > 0) {
        prefix = `<span class="mdi mdi-subdirectory-arrow-right"></span>`;
      }
      return `${prefix}<span class="bold">${value}</span><span style="font-size:11px; margin-left: 15px;">(parentId: ${dataContext.parentId})</span>`;
    },
  },
};

Tree Formatter

You would typically use the built-in Formatters.tree to show the tree but in some cases you might want to use your own Formatter and that is fine, it's like any other Custom Formatter. Here's a demo of the Example 28 Custom Formatter which is specific for showing the collapsing icon and folder and files icons.

treeFormatter: Formatter = (row, cell, value, columnDef, dataContext, grid) => {
    const gridOptions = grid.getOptions() as GridOption;
    const treeLevelPropName = gridOptions?.treeDataOptions?.levelPropName || '__treeLevel';
    if (value === null || value === undefined || dataContext === undefined) {
      return '';
    }
    const dataView = grid.getData() as SlickDataView;
    const data = dataView.getItems();
    const identifierPropName = dataView.getIdPropertyName() || 'id';
    const idx = dataView.getIdxById(dataContext[identifierPropName]);
    const prefix = this.getFileIcon(value);

    value = value.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
    const spacer = `<span style="display:inline-block; width:${(15 * dataContext[treeLevelPropName])}px;"></span>`;

    if (data[idx + 1] && data[idx + 1][treeLevelPropName] > data[idx][treeLevelPropName]) {
      const folderPrefix = `<i class="mdi icon ${dataContext.__collapsed ? 'mdi-folder' : 'mdi-folder-open'}"></i>`;
      if (dataContext.__collapsed) {
        return `${spacer} <span class="slick-group-toggle collapsed" level="${dataContext[treeLevelPropName]}"></span>${folderPrefix} ${prefix}&nbsp;${value}`;
      } else {
        return `${spacer} <span class="slick-group-toggle expanded" level="${dataContext[treeLevelPropName]}"></span>${folderPrefix} ${prefix}&nbsp;${value}`;
      }
    } else {
      return `${spacer} <span class="slick-group-toggle" level="${dataContext[treeLevelPropName]}"></span>${prefix}&nbsp;${value}`;
    }
}

Exporting Options (data export to Excel/Text File)

Exporting the data and keeping the tree level indentation requires a few little tricks and a few options were added to configure them. First off we need a leading character on the left because Excel will trim any spaces and so if our indentation is only spaces then everything gets trimmed and so for that we reason we have the character · at the start of every text and then the indentation spaces and that won't be trimmed. Here's a few of the options available.

For the full list of options, refer to the treeDataOptions interface

/**
 * Defaults to 5, indentation spaces to add from the left (calculated by the tree level multiplied by this number).
 * For example if tree depth level is 2, the calculation will be (2 * 15 = 30), so the column will be displayed 30px from the left
 */
exportIndentMarginLeft?: number;

/**
 * Defaults to centered dot (·), we added this because Excel seems to trim spaces leading character
 * and if we add a regular character like a dot then it keeps all tree level indentation spaces
 */
exportIndentationLeadingChar?: string;

/**
 * Defaults to 3, when using a collapsing icon then we need to add some extra spaces to compensate on parent level.
 * If you don't want collapsing icon in your export then you probably want to put this option at 0.
 */
exportIndentationLeadingSpaceCount?: number;

Contents

Clone this wiki locally