Nested Headers

Create hierarchical column headers with multiple levels to organize and structure your spreadsheet data presentation.

What's new with Version 13

  • Editing: splitNestedCell, mergeNestedCell and moveNestedHeader change the structure programmatically. Every change is one undoable operation and travels to connected users
  • Interactive editing: rename, split and merge groups from the context menu, drag the boundary between two groups to move columns from one to the other, and select a group then drag it to move the group with all its columns. Disable it with allowEditNestedHeaders: false
  • Selection: a click on a group selects its columns. Shift-click extends the selection over several groups, Ctrl-click adds a group, and a right-click on a selected group keeps the selection, so a context menu action applies to every selected group
  • Events: onchangenested now fires after every change of the definitions, including cell updates, so a single handler can save the whole nested header configuration

Documentation

Methods

The following methods provide programmatic control over nested headers:

Method Description
getNestedCell Get a nested header cell (DOM element).
getNestedCell(x: number, y: number) => DOMElement
setNestedCell Define the nested header cell attributes.
setNestedCell(x: number, y: number, properties: Object) => void
setNestedCell Define multiple nested header cell attributes.
setNestedCell(updates: Object[]) => void
getNestedHeaders Return the nested header definitions.
getNestedHeaders() => Object[]
setNestedHeaders Set the nested header definitions.
setNestedHeaders(config: Object[][]) => void
resetNestedHeaders Reset the nested header definitions.
resetNestedHeaders() => void
splitNestedCell Split a group at a grid column: the group keeps the columns before it, a new untitled group takes the rest.
splitNestedCell(x: number, y: number) => boolean
mergeNestedCell Merge the group owning a grid column with its right neighbour, or with every group up to the one owning the end column to; the combined group keeps the title. The context menu merges the whole selected range.
mergeNestedCell(x: number, y: number, to?: number) => boolean
moveNestedHeader Move a group to another position of its row, with all the columns it covers. x and destination are group indexes in the row.
moveNestedHeader(x: number, y: number, destination: number) => boolean

Every editing method is one undoable operation and travels to connected users. The x argument of splitNestedCell and mergeNestedCell is a grid column; the nested cell arguments of setNestedCell and moveNestedHeader are group indexes inside the nested row.

Interactive editing

Users can edit the nested headers directly:

  • Context menu: right-click a group to rename it, split it at the clicked column, or merge it with the group to its right.
  • Boundary drag: drag the right edge of a group to move columns from one group to its neighbour; both groups keep at least one column.
  • Group drag: click a group to select its columns, then press the selected group and drag it. The drop snaps to the group boundaries of that row and the group moves with all its columns.
  • Selection: Shift-click extends the selection over several groups, Ctrl-click adds a group as a second range, and a right-click on a group inside the selection keeps the selection.

Set allowEditNestedHeaders: false to disable the interactive editing; the programmatic methods stay available.

Initial Settings

Configure nested headers during spreadsheet initialization:

Property Description
nestedHeaders?: Object[][] Worksheet nested header definitions. Possible attributes: { title: String, colspan: Number, tooltip: String, align: String, frozen: Boolean }
allowEditNestedHeaders?: boolean Allow the user to rename, split, merge and drag the groups. The programmatic methods are not affected. Default: true

Available Events

Event Description
onchangenested Triggered after every change of the nested header definitions: a new definition, a reset, a cell update, a split, a merge or a move. The second argument is the complete definition, ready to be saved.
onchangenested(worksheet: Object, config: Object[][]) => void
onchangenestedcell Triggered when nested header cell properties are updated.
onchangenestedcell(worksheet: Object, positionXOrArrayOfUpdates: number|Object[], positionY?: number, properties?: Object) => void

Every change also reaches the persistence handler and, with the Jspreadsheet Server, is replayed on the other connected users.

Examples

Nested header example

This example demonstrates basic nested header configuration with programmatic updates. See a working example of a spreadsheet with nested headers on JSFiddle.

<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='Update' id="btn1"></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('ODE1MWFhYTRjNGQ4MzE1YzE0ZGJhYTNhNzYxZGRkNTkxMjAyNzdjYWEwZGRlYTBmODE0NmNhZGU0YWIxM2EzMDVkZTViYmVhMjY3MzA3MDc3NTEwNDU0ZjMxOGExMDBiNzMxMDNjNTE0NDU5MjVhOTA0ZDNhMjVmOWIzODI2NzgsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94Tnprd01qRXlOek16TENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2liRzlqWVd4b2IzTjBJbDBzSW5Cc1lXNGlPaUl6TkNJc0luTmpiM0JsSWpwYkluWTNJaXdpZGpnaUxDSjJPU0lzSW5ZeE1DSXNJbll4TVNJc0luWXhNaUlzSW5ZeE15SXNJbU5vWVhKMGN5SXNJbVp2Y20xeklpd2labTl5YlhWc1lTSXNJbkJoY25ObGNpSXNJbkpsYm1SbGNpSXNJbU52YlcxbGJuUnpJaXdpYVcxd2IzSjBaWElpTENKaVlYSWlMQ0oyWVd4cFpHRjBhVzl1Y3lJc0luTmxZWEpqYUNJc0luQnlhVzUwSWl3aWMyaGxaWFJ6SWl3aVkyeHBaVzUwSWl3aWMyVnlkbVZ5SWl3aWMyaGhjR1Z6SWl3aVptOXliV0YwSWl3aWNHbDJiM1FpWFN3aVpHVnRieUk2ZEhKMVpYMD0=');

// Create the spreadsheet
let table = jspreadsheet(document.getElementById('spreadsheet'), {
    worksheets: [{
        data: [
            ['BR', 'Cheese', 1],
            ['CA', 'Apples', 0],
            ['US', 'Carrots', 1],
            ['GB', 'Oranges', 0],
        ],
        columns: [
            {
                type: 'autocomplete',
                title: 'Country',
                width: '200px'
            },
            {
                type: 'dropdown',
                title: 'Food',
                width: '100px',
                source: ['Apples','Bananas','Carrots','Oranges','Cheese']
            },
            {
                type: 'checkbox',
                title: 'Stock',
                width: '100px'
            },
            {
                type: 'number',
                title: 'Price',
                width: '100px'
            },
        ],
        minDimensions: [6,4],
        nestedHeaders:[
            [
                {
                    title: 'Supermarket information',
                    colspan: 6,
                },
            ],
            [
                {
                    title: 'Location',
                    colspan: 1,
                },
                {
                    title: 'Other Information',
                    colspan: 2
                },
                {
                    title: 'Costs',
                    colspan: 3
                }
            ],
        ]
    }]
});

document.getElementById("btn1").onclick = () => table[0].setNestedCell(0, 0, { title:'New title',tooltip:'New tooltip' })
</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('ODE1MWFhYTRjNGQ4MzE1YzE0ZGJhYTNhNzYxZGRkNTkxMjAyNzdjYWEwZGRlYTBmODE0NmNhZGU0YWIxM2EzMDVkZTViYmVhMjY3MzA3MDc3NTEwNDU0ZjMxOGExMDBiNzMxMDNjNTE0NDU5MjVhOTA0ZDNhMjVmOWIzODI2NzgsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94Tnprd01qRXlOek16TENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2liRzlqWVd4b2IzTjBJbDBzSW5Cc1lXNGlPaUl6TkNJc0luTmpiM0JsSWpwYkluWTNJaXdpZGpnaUxDSjJPU0lzSW5ZeE1DSXNJbll4TVNJc0luWXhNaUlzSW5ZeE15SXNJbU5vWVhKMGN5SXNJbVp2Y20xeklpd2labTl5YlhWc1lTSXNJbkJoY25ObGNpSXNJbkpsYm1SbGNpSXNJbU52YlcxbGJuUnpJaXdpYVcxd2IzSjBaWElpTENKaVlYSWlMQ0oyWVd4cFpHRjBhVzl1Y3lJc0luTmxZWEpqYUNJc0luQnlhVzUwSWl3aWMyaGxaWFJ6SWl3aVkyeHBaVzUwSWl3aWMyVnlkbVZ5SWl3aWMyaGhjR1Z6SWl3aVptOXliV0YwSWl3aWNHbDJiM1FpWFN3aVpHVnRieUk2ZEhKMVpYMD0=');

export default function App() {
    // Spreadsheet array of worksheets
    const spreadsheet = useRef();
    // Data
    const data = [
        ['BR', 'Cheese', 1],
        ['CA', 'Apples', 0],
        ['US', 'Carrots', 1],
        ['GB', 'Oranges', 0],
    ];
    // Columns
    const columns = [
        {
            type: 'autocomplete',
            title: 'Country',
            width: '200px'
        },
        {
            type: 'dropdown',
            title: 'Food',
            width: '100px',
            source: ['Apples', 'Bananas', 'Carrots', 'Oranges', 'Cheese']
        },
        {
            type: 'checkbox',
            title: 'Stock',
            width: '100px'
        },
        {
            type: 'number',
            title: 'Price',
            width: '100px'
        },
    ];
    // Nested headers
    const nestedHeaders = [
        [
            {
                title: 'Supermarket information',
                colspan: 8,
            },
        ],
        [
            {
                title: 'Location',
                colspan: 1,
            },
            {
                title: 'Other Information',
                colspan: 2
            },
            {
                title: 'Costs',
                colspan: 5
            }
        ],
    ];
    // Render component
    return (
        <>
            <Spreadsheet ref={spreadsheet}>
                <Worksheet data={data} columns={columns} nestedHeaders={nestedHeaders} minDimensions={[6, 4]}/>
            </Spreadsheet>
            <input type='button' value='Update'
                   onClick={() => spreadsheet.current[0].setNestedCell(0, 0, {title: 'New', tooltip: 'Tooltip'})}/>
        </>
    );
}
<template>
    <Spreadsheet ref="spreadsheet">
        <Worksheet :data="data" :columns="columns" :nestedHeaders="nestedHeaders" :minDimensions="[6,4]" />
    </Spreadsheet>
    <input type='button' value='Update' @click="update" />
</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('ODE1MWFhYTRjNGQ4MzE1YzE0ZGJhYTNhNzYxZGRkNTkxMjAyNzdjYWEwZGRlYTBmODE0NmNhZGU0YWIxM2EzMDVkZTViYmVhMjY3MzA3MDc3NTEwNDU0ZjMxOGExMDBiNzMxMDNjNTE0NDU5MjVhOTA0ZDNhMjVmOWIzODI2NzgsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94Tnprd01qRXlOek16TENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2liRzlqWVd4b2IzTjBJbDBzSW5Cc1lXNGlPaUl6TkNJc0luTmpiM0JsSWpwYkluWTNJaXdpZGpnaUxDSjJPU0lzSW5ZeE1DSXNJbll4TVNJc0luWXhNaUlzSW5ZeE15SXNJbU5vWVhKMGN5SXNJbVp2Y20xeklpd2labTl5YlhWc1lTSXNJbkJoY25ObGNpSXNJbkpsYm1SbGNpSXNJbU52YlcxbGJuUnpJaXdpYVcxd2IzSjBaWElpTENKaVlYSWlMQ0oyWVd4cFpHRjBhVzl1Y3lJc0luTmxZWEpqYUNJc0luQnlhVzUwSWl3aWMyaGxaWFJ6SWl3aVkyeHBaVzUwSWl3aWMyVnlkbVZ5SWl3aWMyaGhjR1Z6SWl3aVptOXliV0YwSWl3aWNHbDJiM1FpWFN3aVpHVnRieUk2ZEhKMVpYMD0=');

export default {
    components: {
        Spreadsheet,
        Worksheet,
    },
    data() {
        // Data
        const data = [
            ['BR', 'Cheese', 1],
            ['CA', 'Apples', 0],
            ['US', 'Carrots', 1],
            ['GB', 'Oranges', 0],
        ];
        // Columns
        const columns = [
            {
                type: 'autocomplete',
                title: 'Country',
                width: '200px'
            },
            {
                type: 'dropdown',
                title: 'Food',
                width: '100px',
                source: ['Apples','Bananas','Carrots','Oranges','Cheese']
            },
            {
                type: 'checkbox',
                title: 'Stock',
                width: '100px'
            },
            {
                type: 'number',
                title: 'Price',
                width: '100px'
            },
        ];
        // Nested headers
        const nestedHeaders = [
            [
                {
                    title: 'Supermarket information',
                    colspan: 8,
                },
            ],
            [
                {
                    title: 'Location',
                    colspan: 1,
                },
                {
                    title: 'Other Information',
                    colspan: 2
                },
                {
                    title: 'Costs',
                    colspan: 5
                }
            ],
        ];

        return {
            data,
            columns,
            nestedHeaders,
        };
    },
    methods: {
        update() {
            this.$refs.spreadsheet.current[0].setNestedCell(0, 0, { title:'New', tooltip:'Tooltip' })
        }
    }
}
</script>
import { Component, ViewChild, ElementRef } from "@angular/core";
import jspreadsheet from "jspreadsheet";
import "jspreadsheet/dist/jspreadsheet.css";
import "jsuites/dist/jsuites.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('ODE1MWFhYTRjNGQ4MzE1YzE0ZGJhYTNhNzYxZGRkNTkxMjAyNzdjYWEwZGRlYTBmODE0NmNhZGU0YWIxM2EzMDVkZTViYmVhMjY3MzA3MDc3NTEwNDU0ZjMxOGExMDBiNzMxMDNjNTE0NDU5MjVhOTA0ZDNhMjVmOWIzODI2NzgsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94Tnprd01qRXlOek16TENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2liRzlqWVd4b2IzTjBJbDBzSW5Cc1lXNGlPaUl6TkNJc0luTmpiM0JsSWpwYkluWTNJaXdpZGpnaUxDSjJPU0lzSW5ZeE1DSXNJbll4TVNJc0luWXhNaUlzSW5ZeE15SXNJbU5vWVhKMGN5SXNJbVp2Y20xeklpd2labTl5YlhWc1lTSXNJbkJoY25ObGNpSXNJbkpsYm1SbGNpSXNJbU52YlcxbGJuUnpJaXdpYVcxd2IzSjBaWElpTENKaVlYSWlMQ0oyWVd4cFpHRjBhVzl1Y3lJc0luTmxZWEpqYUNJc0luQnlhVzUwSWl3aWMyaGxaWFJ6SWl3aVkyeHBaVzUwSWl3aWMyVnlkbVZ5SWl3aWMyaGhjR1Z6SWl3aVptOXliV0YwSWl3aWNHbDJiM1FpWFN3aVpHVnRieUk2ZEhKMVpYMD0=');

// Create component
@Component({
    standalone: true,
    selector: "app-root",
    template: `
        <div #spreadsheet></div>
        <input type='button' value='Update' (click)="this.worksheets[0].setNestedCell(0, 0, { title:'New title',tooltip:'New tooltip' })">
    `,
})
export class AppComponent {
    @ViewChild("spreadsheet") spreadsheet: ElementRef;
    // Worksheets
    worksheets: jspreadsheet.worksheetInstance[];
    // Create a new data grid
    ngAfterViewInit() {
        // Create spreadsheet
        this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, {
            worksheets: [{
                data: [
                    ['BR', 'Cheese', 1],
                    ['CA', 'Apples', 0],
                    ['US', 'Carrots', 1],
                    ['GB', 'Oranges', 0],
                ],
                columns: [
                    {
                        type: 'autocomplete',
                        title: 'Country',
                    },
                    {
                        type: 'dropdown',
                        title: 'Food',
                        source: ['Apples','Bananas','Carrots','Oranges','Cheese']
                    },
                    {
                        type: 'checkbox',
                        title: 'Stock',
                    },
                    {
                        type: 'number',
                        title: 'Price',
                    },
                ],
                minDimensions: [6,4],
                nestedHeaders:[
                    [
                        {
                            title: 'Supermarket information',
                            colspan: 6,
                        },
                    ],
                    [
                        {
                            title: 'Location',
                            colspan: 1,
                        },
                        {
                            title: 'Other Information',
                            colspan: 2
                        },
                        {
                            title: 'Costs',
                            colspan: 3
                        }
                    ],
                ]
            }]
        });
    }
}

Frozen nested headers

This example demonstrates frozen nested headers functionality with multi-level header structure.

<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>

<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('ODE1MWFhYTRjNGQ4MzE1YzE0ZGJhYTNhNzYxZGRkNTkxMjAyNzdjYWEwZGRlYTBmODE0NmNhZGU0YWIxM2EzMDVkZTViYmVhMjY3MzA3MDc3NTEwNDU0ZjMxOGExMDBiNzMxMDNjNTE0NDU5MjVhOTA0ZDNhMjVmOWIzODI2NzgsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94Tnprd01qRXlOek16TENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2liRzlqWVd4b2IzTjBJbDBzSW5Cc1lXNGlPaUl6TkNJc0luTmpiM0JsSWpwYkluWTNJaXdpZGpnaUxDSjJPU0lzSW5ZeE1DSXNJbll4TVNJc0luWXhNaUlzSW5ZeE15SXNJbU5vWVhKMGN5SXNJbVp2Y20xeklpd2labTl5YlhWc1lTSXNJbkJoY25ObGNpSXNJbkpsYm1SbGNpSXNJbU52YlcxbGJuUnpJaXdpYVcxd2IzSjBaWElpTENKaVlYSWlMQ0oyWVd4cFpHRjBhVzl1Y3lJc0luTmxZWEpqYUNJc0luQnlhVzUwSWl3aWMyaGxaWFJ6SWl3aVkyeHBaVzUwSWl3aWMyVnlkbVZ5SWl3aWMyaGhjR1Z6SWl3aVptOXliV0YwSWl3aWNHbDJiM1FpWFN3aVpHVnRieUk2ZEhKMVpYMD0=');

// Create the data grid
let table = jspreadsheet(document.getElementById('spreadsheet'), {
    tabs: true,
    toolbar: true,
    tableOverflow: true,
    tableWidth: 600,
    worksheets: [{
        minDimensions: [22,10],
        freezeColumns: 2,
        nestedHeaders:[
            [
                {
                    title: 'Location',
                    colspan: 2,
                    frozen: true,
                },
                {
                    title: 'Information',
                    colspan: 10
                },
                {
                    title: 'Another information',
                    colspan: 10
                }
            ],
        ]
    }]
});
</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('ODE1MWFhYTRjNGQ4MzE1YzE0ZGJhYTNhNzYxZGRkNTkxMjAyNzdjYWEwZGRlYTBmODE0NmNhZGU0YWIxM2EzMDVkZTViYmVhMjY3MzA3MDc3NTEwNDU0ZjMxOGExMDBiNzMxMDNjNTE0NDU5MjVhOTA0ZDNhMjVmOWIzODI2NzgsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94Tnprd01qRXlOek16TENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2liRzlqWVd4b2IzTjBJbDBzSW5Cc1lXNGlPaUl6TkNJc0luTmpiM0JsSWpwYkluWTNJaXdpZGpnaUxDSjJPU0lzSW5ZeE1DSXNJbll4TVNJc0luWXhNaUlzSW5ZeE15SXNJbU5vWVhKMGN5SXNJbVp2Y20xeklpd2labTl5YlhWc1lTSXNJbkJoY25ObGNpSXNJbkpsYm1SbGNpSXNJbU52YlcxbGJuUnpJaXdpYVcxd2IzSjBaWElpTENKaVlYSWlMQ0oyWVd4cFpHRjBhVzl1Y3lJc0luTmxZWEpqYUNJc0luQnlhVzUwSWl3aWMyaGxaWFJ6SWl3aVkyeHBaVzUwSWl3aWMyVnlkbVZ5SWl3aWMyaGhjR1Z6SWl3aVptOXliV0YwSWl3aWNHbDJiM1FpWFN3aVpHVnRieUk2ZEhKMVpYMD0=');

export default function App() {
    // Spreadsheet array of worksheets
    const spreadsheet = useRef();
    // Nested headers
    const nestedHeaders = [
        [
            {
                title: 'Location',
                colspan: 2,
                frozen: true,
            },
            {
                title: 'Information',
                colspan: 10
            },
            {
                title: 'Another information',
                colspan: 10
            }
        ],
    ];
    // Render component
    return (
        <Spreadsheet ref={spreadsheet} toolbar tabs tableOverflow tableWidth="600">
            <Worksheet nestedHeaders={nestedHeaders} minDimensions={[22,10]} freezeColumns="2" />
        </Spreadsheet>
    );
}
<template>
    <Spreadsheet ref="spreadsheet" toolbar tabs tableOverflow tableWidth="600">
        <Worksheet :nestedHeaders="nestedHeaders" :minDimensions="[22,10]" freezeColumns="2" />
    </Spreadsheet>
</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('ODE1MWFhYTRjNGQ4MzE1YzE0ZGJhYTNhNzYxZGRkNTkxMjAyNzdjYWEwZGRlYTBmODE0NmNhZGU0YWIxM2EzMDVkZTViYmVhMjY3MzA3MDc3NTEwNDU0ZjMxOGExMDBiNzMxMDNjNTE0NDU5MjVhOTA0ZDNhMjVmOWIzODI2NzgsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94Tnprd01qRXlOek16TENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2liRzlqWVd4b2IzTjBJbDBzSW5Cc1lXNGlPaUl6TkNJc0luTmpiM0JsSWpwYkluWTNJaXdpZGpnaUxDSjJPU0lzSW5ZeE1DSXNJbll4TVNJc0luWXhNaUlzSW5ZeE15SXNJbU5vWVhKMGN5SXNJbVp2Y20xeklpd2labTl5YlhWc1lTSXNJbkJoY25ObGNpSXNJbkpsYm1SbGNpSXNJbU52YlcxbGJuUnpJaXdpYVcxd2IzSjBaWElpTENKaVlYSWlMQ0oyWVd4cFpHRjBhVzl1Y3lJc0luTmxZWEpqYUNJc0luQnlhVzUwSWl3aWMyaGxaWFJ6SWl3aVkyeHBaVzUwSWl3aWMyVnlkbVZ5SWl3aWMyaGhjR1Z6SWl3aVptOXliV0YwSWl3aWNHbDJiM1FpWFN3aVpHVnRieUk2ZEhKMVpYMD0=');

export default {
    components: {
        Spreadsheet,
        Worksheet,
    },
    data() {
        // Nested headers
        const nestedHeaders = [
            [
                {
                    title: 'Location',
                    colspan: 2,
                    frozen: true,
                },
                {
                    title: 'Information',
                    colspan: 10
                },
                {
                    title: 'Another information',
                    colspan: 10
                }
            ],
        ];

        return {
            nestedHeaders,
        };
    }
}
</script>
import { Component, ViewChild, ElementRef } from "@angular/core";
import jspreadsheet from "jspreadsheet";

// 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('ODE1MWFhYTRjNGQ4MzE1YzE0ZGJhYTNhNzYxZGRkNTkxMjAyNzdjYWEwZGRlYTBmODE0NmNhZGU0YWIxM2EzMDVkZTViYmVhMjY3MzA3MDc3NTEwNDU0ZjMxOGExMDBiNzMxMDNjNTE0NDU5MjVhOTA0ZDNhMjVmOWIzODI2NzgsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94Tnprd01qRXlOek16TENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2liRzlqWVd4b2IzTjBJbDBzSW5Cc1lXNGlPaUl6TkNJc0luTmpiM0JsSWpwYkluWTNJaXdpZGpnaUxDSjJPU0lzSW5ZeE1DSXNJbll4TVNJc0luWXhNaUlzSW5ZeE15SXNJbU5vWVhKMGN5SXNJbVp2Y20xeklpd2labTl5YlhWc1lTSXNJbkJoY25ObGNpSXNJbkpsYm1SbGNpSXNJbU52YlcxbGJuUnpJaXdpYVcxd2IzSjBaWElpTENKaVlYSWlMQ0oyWVd4cFpHRjBhVzl1Y3lJc0luTmxZWEpqYUNJc0luQnlhVzUwSWl3aWMyaGxaWFJ6SWl3aVkyeHBaVzUwSWl3aWMyVnlkbVZ5SWl3aWMyaGhjR1Z6SWl3aVptOXliV0YwSWl3aWNHbDJiM1FpWFN3aVpHVnRieUk2ZEhKMVpYMD0=');

// Create component
@Component({
    standalone: true,
    selector: "app-root",
    template: `<div #spreadsheet></div>`,
})
export class AppComponent {
    @ViewChild("spreadsheet") spreadsheet: ElementRef;
    // Worksheets
    worksheets: jspreadsheet.worksheetInstance[];
    // Create a new data grid
    ngAfterViewInit() {
        // Create spreadsheet
        this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, {
            tabs: true,
            toolbar: true,
            tableOverflow: true,
            tableWidth: 600,
            worksheets: [{
                minDimensions: [22,10],
                freezeColumns: 2,
                nestedHeaders:[
                    [
                        {
                            title: 'Location',
                            colspan: 2,
                            frozen: true,
                        },
                        {
                            title: 'Information',
                            colspan: 10
                        },
                        {
                            title: 'Another information',
                            colspan: 10
                        }
                    ],
                ]
            }]
        });
    }
}