Row Groups

Row groups allow you to organize large spreadsheets by combining related rows into collapsible sections. This section explains how to define and configure groups, the programmatic methods available to manage them, and the events triggered when groups are expanded or collapsed.

What's new with Version 13

  • Outline levels: nested groups render the Excel outline model, with jss_group_header on the owner rows and jss_group_level_1 to jss_group_level_8 for the nesting depth. The default visuals are enabled with the groupIndentation option
  • Visibility: opening a parent never reveals the content of a closed nested group, and nested owners keep their own state
  • Validation: setRowGroup refuses invalid positions or a negative size with false

Documentation

Methods

The following methods manage row groups programmatically.

Method Description
getRowGroup Get all row groups.
getRowGroup() : object
setRowGroup Create a new collapsible group of rows.
setRowGroup(rowNumber: Number, numOfItems: Number) : void
resetRowGroup Reset the row group.
resetRowGroup(rowNumber: Number) : void
openRowGroup Open a group of rows.
openRowGroup(rowNumber?: Number|Number[]) : void
closeRowGroup Close a group of rows.
closeRowGroup(rowNumber?: Number|Number[]) : void

Events

Events triggered by row group actions.

Property Description
ongrouprow When the user creates, updates or resets a group of rows
ongrouprow?: (worksheet: Object, row: Number, numOfItems: Number) => void
onopenrowgroup When the user opens a group of rows
onopenrowgroup?: (worksheet: Object, row: Number) => void
oncloserowgroup When the user closes a group of rows
oncloserowgroup?: (worksheet: Object, row: Number) => void

Initial Settings

Configure row groups during initialization using these row properties.

Property Description
group?: number Number of rows to include in this group
state?: boolean Whether the group starts expanded. Default: false (collapsed)

Worksheet Level

Property Description
groupIndentation?: boolean | number Enable the default outline visuals: the owner rows are highlighted and the first data cell is indented per nesting level. true indents by 16px; a number sets the pixel step. Default: off

Outline levels

Nested row groups render the Excel outline model. The owner row of a group carries the jss_group_header class, and every row inside a group carries jss_group_level_1 to jss_group_level_8, the nesting depth, with eight levels as in Excel. The classes are always emitted, so a stylesheet can style the outline; the default visuals are opt-in through groupIndentation. A row stays hidden while any enclosing group is closed: opening a parent never reveals the content of a closed nested group, and nested owners keep their own open state.

The open and close state of a group is a personal view preference: it is never persisted or synchronized to other connected users.

Examples

Basic Collapsible Row Group

Create collapsible row groups in Jspreadsheet.

<html>
<script src="https://jspreadsheet.com/v13/jspreadsheet.js"></script>
<script src="https://jsuites.net/v6/jsuites.js"></script>
<link rel="stylesheet" href="https://jsuites.net/v6/jsuites.css" type="text/css" />
<link rel="stylesheet" href="https://jspreadsheet.com/v13/jspreadsheet.css" type="text/css" />

<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Material+Icons" />

<div id="spreadsheet"></div>

<p><input type="button" value="setRowGroup(0,3)" id="btn1" />
<input type="button" value="resetRowGroup(0)" id="btn2" /></p>

<script>
// You can use the following license for quick testing on localhost, StackBlitz, or CodeSandbox.
// The license is valid for one day, after which the spreadsheet will become read-only.
// For a longer trial period, you can create a free account and generate a demo license with an extended expiration date.
jspreadsheet.setLicense('MDNkMjE4NjNmMDc0YTkwZDQ1MGViZmQ0ZDc4ZWNjMzc0MTMxZThlMzNhYTQ0OGYzYTNhMTI0NDYwMzg1NGIzMTcyNjk1YTA0MDM4ZjdkZDczMWFkYTM0ODZmZmQyMGYyMDVmY2JhYTc2N2MzN2QzMjgxZjQ1MzJjZmI2ZDI2NjQsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94Tnprd01qRXlOemszTENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2liRzlqWVd4b2IzTjBJbDBzSW5Cc1lXNGlPaUl6TkNJc0luTmpiM0JsSWpwYkluWTNJaXdpZGpnaUxDSjJPU0lzSW5ZeE1DSXNJbll4TVNJc0luWXhNaUlzSW5ZeE15SXNJbU5vWVhKMGN5SXNJbVp2Y20xeklpd2labTl5YlhWc1lTSXNJbkJoY25ObGNpSXNJbkpsYm1SbGNpSXNJbU52YlcxbGJuUnpJaXdpYVcxd2IzSjBaWElpTENKaVlYSWlMQ0oyWVd4cFpHRjBhVzl1Y3lJc0luTmxZWEpqYUNJc0luQnlhVzUwSWl3aWMyaGxaWFJ6SWl3aVkyeHBaVzUwSWl3aWMyVnlkbVZ5SWl3aWMyaGhjR1Z6SWl3aVptOXliV0YwSWl3aWNHbDJiM1FpWFN3aVpHVnRieUk2ZEhKMVpYMD0=');

let data = [];

for (let j = 0; j < 50; j++) {
    data[j] = [];
    for (let i = 0; i < 50; i++) {
        data[j][i] = jspreadsheet.helpers.getColumnNameFromCoords(i, j);
    }
}

// Create a new spreadsheet
let spreadsheet = jspreadsheet(document.getElementById('spreadsheet'), {
    tableOverflow: true,
    tableWidth: '700px',
    tableHeight: '400px',
    worksheets: [{
        data: data,
        rows: { 6: { group: 3 }},
    }]
});

document.getElementById("btn1").onclick = () => spreadsheet[0].setRowGroup(0,3);
document.getElementById("btn2").onclick = () => spreadsheet[0].resetRowGroup(0);
</script>
</html>
import React, {useRef} from "react";
import {Spreadsheet, Worksheet, jspreadsheet} from "@jspreadsheet/react";
import "jsuites/dist/jsuites.css";
import "jspreadsheet/dist/jspreadsheet.css";

// You can use the following license for quick testing on localhost, StackBlitz, or CodeSandbox.
// The license is valid for one day, after which the spreadsheet will become read-only.
// For a longer trial period, you can create a free account and generate a demo license with an extended expiration date.
jspreadsheet.setLicense('MDNkMjE4NjNmMDc0YTkwZDQ1MGViZmQ0ZDc4ZWNjMzc0MTMxZThlMzNhYTQ0OGYzYTNhMTI0NDYwMzg1NGIzMTcyNjk1YTA0MDM4ZjdkZDczMWFkYTM0ODZmZmQyMGYyMDVmY2JhYTc2N2MzN2QzMjgxZjQ1MzJjZmI2ZDI2NjQsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94Tnprd01qRXlOemszTENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2liRzlqWVd4b2IzTjBJbDBzSW5Cc1lXNGlPaUl6TkNJc0luTmpiM0JsSWpwYkluWTNJaXdpZGpnaUxDSjJPU0lzSW5ZeE1DSXNJbll4TVNJc0luWXhNaUlzSW5ZeE15SXNJbU5vWVhKMGN5SXNJbVp2Y20xeklpd2labTl5YlhWc1lTSXNJbkJoY25ObGNpSXNJbkpsYm1SbGNpSXNJbU52YlcxbGJuUnpJaXdpYVcxd2IzSjBaWElpTENKaVlYSWlMQ0oyWVd4cFpHRjBhVzl1Y3lJc0luTmxZWEpqYUNJc0luQnlhVzUwSWl3aWMyaGxaWFJ6SWl3aVkyeHBaVzUwSWl3aWMyVnlkbVZ5SWl3aWMyaGhjR1Z6SWl3aVptOXliV0YwSWl3aWNHbDJiM1FpWFN3aVpHVnRieUk2ZEhKMVpYMD0=');

export default function App() {
    // Spreadsheet array of worksheets
    const spreadsheet = useRef();
    // Data
    let data = [];
    for (let j = 0; j < 50; j++) {
        data[j] = [];
        for (let i = 0; i < 50; i++) {
            data[j][i] = jspreadsheet.helpers.getColumnNameFromCoords(i, j);
        }
    }
    // Rows settings
    const rows = {6: {group: 3}}

    // Render component
    return (
        <>
            <Spreadsheet ref={spreadsheet} tableOverflow tableWidth="700px" tableHeight="400px">
                <Worksheet data={data} rows={rows} />
            </Spreadsheet>
            <input type="button" value="setRowGroup(0,3)" onClick={() => spreadsheet.current[0].setRowGroup(0, 3)} />
            <input type="button" value="resetRowGroup(0)" onClick={() => spreadsheet.current[0].resetRowGroup(0)} />
        </>
    );
}
<template>
    <Spreadsheet ref="spreadsheet" tableOverflow tableWidth="700px" tableHeight="400px">
        <Worksheet :data="data" :rows="rows" />
    </Spreadsheet>
    <input type="button" value="setRowGroup(0,3)" @click="setRowGroup(0,3)" />
    <input type="button" value="resetRowGroup(0)" @click="resetRowGroup(0);" />
</template>

<script>
import { Spreadsheet, Worksheet, jspreadsheet } from "@jspreadsheet/vue";
import "jsuites/dist/jsuites.css";
import "jspreadsheet/dist/jspreadsheet.css";

// You can use the following license for quick testing on localhost, StackBlitz, or CodeSandbox.
// The license is valid for one day, after which the spreadsheet will become read-only.
// For a longer trial period, you can create a free account and generate a demo license with an extended expiration date.
jspreadsheet.setLicense('MDNkMjE4NjNmMDc0YTkwZDQ1MGViZmQ0ZDc4ZWNjMzc0MTMxZThlMzNhYTQ0OGYzYTNhMTI0NDYwMzg1NGIzMTcyNjk1YTA0MDM4ZjdkZDczMWFkYTM0ODZmZmQyMGYyMDVmY2JhYTc2N2MzN2QzMjgxZjQ1MzJjZmI2ZDI2NjQsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94Tnprd01qRXlOemszTENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2liRzlqWVd4b2IzTjBJbDBzSW5Cc1lXNGlPaUl6TkNJc0luTmpiM0JsSWpwYkluWTNJaXdpZGpnaUxDSjJPU0lzSW5ZeE1DSXNJbll4TVNJc0luWXhNaUlzSW5ZeE15SXNJbU5vWVhKMGN5SXNJbVp2Y20xeklpd2labTl5YlhWc1lTSXNJbkJoY25ObGNpSXNJbkpsYm1SbGNpSXNJbU52YlcxbGJuUnpJaXdpYVcxd2IzSjBaWElpTENKaVlYSWlMQ0oyWVd4cFpHRjBhVzl1Y3lJc0luTmxZWEpqYUNJc0luQnlhVzUwSWl3aWMyaGxaWFJ6SWl3aVkyeHBaVzUwSWl3aWMyVnlkbVZ5SWl3aWMyaGhjR1Z6SWl3aVptOXliV0YwSWl3aWNHbDJiM1FpWFN3aVpHVnRieUk2ZEhKMVpYMD0=');

// Data
let data = [];
for (let j = 0; j < 50; j++) {
    data[j] = [];
    for (let i = 0; i < 50; i++) {
        data[j][i] = jspreadsheet.helpers.getColumnNameFromCoords(i, j);
    }
}

export default {
    components: {
        Spreadsheet,
        Worksheet,
    },
    methods: {
        setRowGroup(row, numOfRows) {
            this.$refs.spreadsheet.current[0].setRowGroup(row, numOfRows);
        },
        resetRowGroup(row) {
            this.$refs.spreadsheet.current[0].resetRowGroup(row);
        },
    },
    data() {
        return {
            // Worksheet data
            data: data,
            // Rows
            rows: { 6: { group: 3 }}
        };
    }
}
</script>
import { Component, ViewChild, ElementRef, AfterViewInit } from "@angular/core";
import jspreadsheet from "jspreadsheet";
import "jsuites/dist/jsuites.css";
import "jspreadsheet/dist/jspreadsheet.css";

// You can use the following license for quick testing on localhost, StackBlitz, or CodeSandbox.
// The license is valid for one day, after which the spreadsheet will become read-only.
// For a longer trial period, you can create a free account and generate a demo license with an extended expiration date.
jspreadsheet.setLicense('MDNkMjE4NjNmMDc0YTkwZDQ1MGViZmQ0ZDc4ZWNjMzc0MTMxZThlMzNhYTQ0OGYzYTNhMTI0NDYwMzg1NGIzMTcyNjk1YTA0MDM4ZjdkZDczMWFkYTM0ODZmZmQyMGYyMDVmY2JhYTc2N2MzN2QzMjgxZjQ1MzJjZmI2ZDI2NjQsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94Tnprd01qRXlOemszTENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2liRzlqWVd4b2IzTjBJbDBzSW5Cc1lXNGlPaUl6TkNJc0luTmpiM0JsSWpwYkluWTNJaXdpZGpnaUxDSjJPU0lzSW5ZeE1DSXNJbll4TVNJc0luWXhNaUlzSW5ZeE15SXNJbU5vWVhKMGN5SXNJbVp2Y20xeklpd2labTl5YlhWc1lTSXNJbkJoY25ObGNpSXNJbkpsYm1SbGNpSXNJbU52YlcxbGJuUnpJaXdpYVcxd2IzSjBaWElpTENKaVlYSWlMQ0oyWVd4cFpHRjBhVzl1Y3lJc0luTmxZWEpqYUNJc0luQnlhVzUwSWl3aWMyaGxaWFJ6SWl3aVkyeHBaVzUwSWl3aWMyVnlkbVZ5SWl3aWMyaGhjR1Z6SWl3aVptOXliV0YwSWl3aWNHbDJiM1FpWFN3aVpHVnRieUk2ZEhKMVpYMD0=');

// Data
let data: any = [];
for (let j = 0; j < 50; j++) {
    data[j] = [];
    for (let i = 0; i < 50; i++) {
        data[j][i] = jspreadsheet.helpers.getColumnNameFromCoords(i, j);
    }
}

@Component({
    standalone: true,
    selector: "app-root",
    template: `
        <div #spreadsheet></div>
        <input type="button" value="setRowGroup(0,3)" (click)="this.worksheets[0].setRowGroup(0,3)" />
        <input type="button" value="resetRowGroup(0)" (click)="this.worksheets[0].resetRowGroup(0);" />`
})
export class AppComponent implements AfterViewInit {
    @ViewChild("spreadsheet") spreadsheet: ElementRef;
    // Worksheets
    worksheets: jspreadsheet.worksheetInstance[];
    // Create a new data grid
    ngAfterViewInit() {
        // Create spreadsheet
        this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, {
            tableOverflow: true,
            tableWidth: '700px',
            tableHeight: '400px',
            worksheets: [
                {
                    data: data,
                    rows: { 6: { group: 3 }},
                }
            ]
        });
    }
}