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). x is the group index in the nested row y. Returns null when the cell does not exist.
getNestedCell(x: number, y: number) => DOMElement | null
setNestedCell Update the attributes of a nested header cell. Only title, tooltip, align and colspan are applied; the others are kept. Returns false when the worksheet is not editable or the cell does not exist.
setNestedCell(x: number, y: number, properties: Object) => void | false
setNestedCell Update multiple nested header cells in one operation. Returns false when the worksheet is not editable or none of the cells exists.
setNestedCell(updates: { x: number, y: number, properties: Object }[]) => void | false
getNestedHeaders Return the nested header definitions, or undefined when there are none.
getNestedHeaders() => Object[][] | undefined
setNestedHeaders Set the nested header definitions. Returns false when the worksheet is not editable.
setNestedHeaders(config: Object[][]) => void | false
resetNestedHeaders Remove the nested headers. Triggers onchangenested with an empty array.
resetNestedHeaders() => void
splitNestedCell Split a group at a grid column: the group keeps the columns before it, a new untitled group takes the rest. Returns false when the worksheet is not editable, the column belongs to no group or it is the first column of its group.
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. Returns false when the worksheet is not editable, the column belongs to no group, there is no group to the right or to is not after the group.
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. Returns false when the worksheet is not editable, the group or the destination does not exist or they are the same.
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: 'left' | 'center' | 'right', frozen: Boolean }. A frozen group stays fixed with the frozen columns when freezeColumns is defined
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 once per setNestedCell call, with the arguments it received: the group index, nested row and properties, or the array of updates as the second argument in the batch form.
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('YTZmZjdjMWViMTM0N2NkZGE1Nzc1YjcwNDkwMTA4Zjg0NmRjMTFkNmNkMDYzZDBiMTdiZjAyNWE3NGQ5Nzk0MjM1NTU3ZGY3ZWI4MzZjN2RmMjA3ZmExYWUzMzcxYjdhNjExZmUxY2I1ZDgxMzUxYTY1OWQ5OTRhMDQ1OTU4MzcsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94Tnprek56Z3hNVFUxTENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2liRzlqWVd4b2IzTjBJbDBzSW5Cc1lXNGlPaUl6TkNJc0luTmpiM0JsSWpwYkluWTNJaXdpZGpnaUxDSjJPU0lzSW5ZeE1DSXNJbll4TVNJc0luWXhNaUlzSW5ZeE15SXNJbU5vWVhKMGN5SXNJbVp2Y20xeklpd2labTl5YlhWc1lTSXNJbkJoY25ObGNpSXNJbkpsYm1SbGNpSXNJbU52YlcxbGJuUnpJaXdpYVcxd2IzSjBaWElpTENKaVlYSWlMQ0oyWVd4cFpHRjBhVzl1Y3lJc0luTmxZWEpqYUNJc0luQnlhVzUwSWl3aWMyaGxaWFJ6SWl3aVkyeHBaVzUwSWl3aWMyVnlkbVZ5SWl3aWMyaGhjR1Z6SWl3aVptOXliV0YwSWl3aWNHbDJiM1FpWFN3aVpHVnRieUk2ZEhKMVpYMD0=');

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

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

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

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

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

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

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

// 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
                        }
                    ],
                ]
            }]
        });
    }
}