Sticky Group Headers

This sample shows how to keep the active grouped path visible while users scroll a grouped FlexGrid. Enable the feature by setting the grid's stickyGroupHeaders property to true. You can also combine it with stickyHeaders to keep the column headers visible above the sticky group path.

The demo uses regional sales records grouped by Region, Country, and Channel, then hides those grouped columns so the sticky path is easier to follow while scrolling.

Learn about FlexGrid | Grouping Documentation | FlexGrid API Reference

import 'bootstrap.css'; import '@mescius/wijmo.styles/wijmo.css'; import './styles.css'; import * as wjGrid from '@mescius/wijmo.grid'; import * as wjCore from '@mescius/wijmo'; // document.readyState === 'complete' ? init() : window.onload = init; // function init() { // Use a realistic sales dataset so the sticky path reflects a common reporting workflow. var data = createSalesData(); function createSalesView() { return new wjCore.CollectionView(data, { sortDescriptions: ['region', 'country', 'channel', 'quarter', 'account'], groupDescriptions: ['region', 'country', 'channel'] }); } function createColumns() { return [ { binding: 'region', header: 'Region', visible: false }, { binding: 'country', header: 'Country', visible: false }, { binding: 'channel', header: 'Channel', visible: false }, { binding: 'quarter', header: 'Quarter', width: 110 }, { binding: 'segment', header: 'Segment', width: 120 }, { binding: 'account', header: 'Account', width: 200 }, { binding: 'product', header: 'Product', width: 140 }, { binding: 'status', header: 'Status', width: 120 }, { binding: 'orders', header: 'Orders', width: 90, format: 'n0' }, { binding: 'revenue', header: 'Revenue', width: 120, format: 'c0' }, { binding: 'margin', header: 'Margin', width: 120, format: 'c0' }, ]; } // Create a grouped grid and enable sticky group headers. var theGrid = new wjGrid.FlexGrid('#theGrid', { autoGenerateColumns: false, columns: createColumns(), stickyGroupHeaders: true, itemsSource: createSalesView() }); setTimeout(function () { theGrid.select(new wjGrid.CellRange(0, 3), true); theGrid.scrollIntoView(24, 3, true); }); var stickyGroupHeaders = document.getElementById('stickyGroupHeaders'); stickyGroupHeaders.checked = theGrid.stickyGroupHeaders; stickyGroupHeaders.addEventListener('change', function () { theGrid.stickyGroupHeaders = stickyGroupHeaders.checked; }); } function createSalesData() { var regionGroups = [ { region: 'North America', countries: ['US', 'Canada'] }, { region: 'Europe', countries: ['Germany', 'UK'] }, { region: 'Asia Pacific', countries: ['Japan', 'Australia'] } ], channels = ['Online', 'Retail', 'Distributor'], quarters = ['Q1', 'Q2', 'Q3', 'Q4'], segments = ['Enterprise', 'SMB'], products = ['Phones', 'Laptops', 'Displays', 'Services'], statuses = ['Active', 'Negotiation', 'Closed Won'], accountsByCountry = { US: ['Northwind Stores', 'Adventure Works'], Canada: ['Maple Retail', 'Northern Telecom'], Germany: ['Contoso GmbH', 'Alpine House'], UK: ['Blue Yonder UK', 'Fabrikam Retail'], Japan: ['Sakura Systems', 'Apex K.K.'], Australia: ['Southern Cross Stores', 'Harbor Retail'] }, data = [], id = 0; regionGroups.forEach(function (group, regionIndex) { group.countries.forEach(function (country, countryIndex) { channels.forEach(function (channel, channelIndex) { quarters.forEach(function (quarter, quarterIndex) { segments.forEach(function (segment, segmentIndex) { var accounts = accountsByCountry[country], product = products[(quarterIndex + channelIndex + segmentIndex) % products.length], account = accounts[(channelIndex + segmentIndex) % accounts.length], revenue = 85000 + regionIndex * 18000 + countryIndex * 9000 + channelIndex * 7000 + quarterIndex * 4000 + segmentIndex * 2500, marginRate = 0.18 + quarterIndex * 0.01 + channelIndex * 0.015, orders = 120 + regionIndex * 24 + countryIndex * 16 + channelIndex * 11 + quarterIndex * 9 + segmentIndex * 6; data.push({ id: id++, region: group.region, country: country, channel: channel, quarter: '2025 ' + quarter, segment: segment, account: account, product: product, status: statuses[(regionIndex + quarterIndex + segmentIndex) % statuses.length], orders: orders, revenue: revenue, margin: Math.round(revenue * marginRate) }); }); }); }); }); }); return data; }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>MESCIUS Wijmo FlexGrid Sticky Group Headers</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- SystemJS --> <script src="https://cdn.jsdelivr.net/npm/systemjs@0.21.5/dist/system.src.js" integrity="sha512-skZbMyvYdNoZfLmiGn5ii6KmklM82rYX2uWctBhzaXPxJgiv4XBwJnFGr5k8s+6tE1pcR1nuTKghozJHyzMcoA==" crossorigin="anonymous"></script> <script src="systemjs.config.js"></script> <script> System.import('./src/app'); </script> </head> <body> <div class="container-fluid"> <p> This example groups regional sales records by region, country, and channel. The grouped columns are hidden so the sticky group path is easier to follow while you scroll: </p> <div class="demo-options"> <label class="checkbox-inline"> <input id="stickyGroupHeaders" type="checkbox" checked> Sticky group headers </label> </div> <p class="demo-note"> Basic usage: set <code>stickyGroupHeaders</code> to <code>true</code> on a grouped FlexGrid to keep the active group path visible while you scroll through the rows. </p> <div id="theGrid"></div> </div> </body> </html>
.wj-flexgrid { max-height: 600px; margin: 12px 0; } body { margin-bottom: 20px; } .demo-options { margin: 8px 0 6px; } .demo-options .checkbox-inline + .checkbox-inline { margin-left: 18px; } .demo-note { color: #666; margin-bottom: 10px; }
(function (global) { System.config({ transpiler: 'plugin-babel', babelOptions: { es2015: true, }, meta: { '*.css': { loader: 'css' }, }, paths: { // paths serve as alias 'npm:': 'node_modules/', 'cdn:': 'https://cdn.mescius.io/demoapps/packages/wijmojs/5.20261.52-rc/', 'pcdn:': 'https://cdn.jsdelivr.net/npm/' }, // map tells the System loader where to look for things map: { '@mescius/wijmo': 'cdn:@mescius/wijmo/index.js', '@mescius/wijmo.input': 'cdn:@mescius/wijmo.input/index.js', '@mescius/wijmo.styles': 'cdn:@mescius/wijmo.styles', '@mescius/wijmo.cultures': 'cdn:@mescius/wijmo.cultures', '@mescius/wijmo.chart': 'cdn:@mescius/wijmo.chart/index.js', '@mescius/wijmo.chart.analytics': 'cdn:@mescius/wijmo.chart.analytics/index.js', '@mescius/wijmo.chart.animation': 'cdn:@mescius/wijmo.chart.animation/index.js', '@mescius/wijmo.chart.annotation': 'cdn:@mescius/wijmo.chart.annotation/index.js', '@mescius/wijmo.chart.finance': 'cdn:@mescius/wijmo.chart.finance/index.js', '@mescius/wijmo.chart.finance.analytics': 'cdn:@mescius/wijmo.chart.finance.analytics/index.js', '@mescius/wijmo.chart.hierarchical': 'cdn:@mescius/wijmo.chart.hierarchical/index.js', '@mescius/wijmo.chart.interaction': 'cdn:@mescius/wijmo.chart.interaction/index.js', '@mescius/wijmo.chart.radar': 'cdn:@mescius/wijmo.chart.radar/index.js', '@mescius/wijmo.chart.render': 'cdn:@mescius/wijmo.chart.render/index.js', '@mescius/wijmo.chart.webgl': 'cdn:@mescius/wijmo.chart.webgl/index.js', '@mescius/wijmo.chart.map': 'cdn:@mescius/wijmo.chart.map/index.js', '@mescius/wijmo.gauge': 'cdn:@mescius/wijmo.gauge/index.js', '@mescius/wijmo.grid': 'cdn:@mescius/wijmo.grid/index.js', '@mescius/wijmo.grid.detail': 'cdn:@mescius/wijmo.grid.detail/index.js', '@mescius/wijmo.grid.filter': 'cdn:@mescius/wijmo.grid.filter/index.js', '@mescius/wijmo.grid.search': 'cdn:@mescius/wijmo.grid.search/index.js', '@mescius/wijmo.grid.style': 'cdn:@mescius/wijmo.grid.style/index.js', '@mescius/wijmo.grid.grouppanel': 'cdn:@mescius/wijmo.grid.grouppanel/index.js', '@mescius/wijmo.grid.multirow': 'cdn:@mescius/wijmo.grid.multirow/index.js', '@mescius/wijmo.grid.transposed': 'cdn:@mescius/wijmo.grid.transposed/index.js', '@mescius/wijmo.grid.transposedmultirow': 'cdn:@mescius/wijmo.grid.transposedmultirow/index.js', '@mescius/wijmo.grid.pdf': 'cdn:@mescius/wijmo.grid.pdf/index.js', '@mescius/wijmo.grid.sheet': 'cdn:@mescius/wijmo.grid.sheet/index.js', '@mescius/wijmo.grid.xlsx': 'cdn:@mescius/wijmo.grid.xlsx/index.js', '@mescius/wijmo.grid.selector': 'cdn:@mescius/wijmo.grid.selector/index.js', '@mescius/wijmo.grid.cellmaker': 'cdn:@mescius/wijmo.grid.cellmaker/index.js', '@mescius/wijmo.nav': 'cdn:@mescius/wijmo.nav/index.js', '@mescius/wijmo.odata': 'cdn:@mescius/wijmo.odata/index.js', '@mescius/wijmo.olap': 'cdn:@mescius/wijmo.olap/index.js', '@mescius/wijmo.rest': 'cdn:@mescius/wijmo.rest/index.js', '@mescius/wijmo.pdf': 'cdn:@mescius/wijmo.pdf/index.js', '@mescius/wijmo.pdf.security': 'cdn:@mescius/wijmo.pdf.security/index.js', '@mescius/wijmo.viewer': 'cdn:@mescius/wijmo.viewer/index.js', '@mescius/wijmo.xlsx': 'cdn:@mescius/wijmo.xlsx/index.js', '@mescius/wijmo.undo': 'cdn:@mescius/wijmo.undo/index.js', '@mescius/wijmo.interop.grid': 'cdn:@mescius/wijmo.interop.grid/index.js', '@mescius/wijmo.touch': 'cdn:@mescius/wijmo.touch/index.js', '@mescius/wijmo.cloud': 'cdn:@mescius/wijmo.cloud/index.js', '@mescius/wijmo.barcode': 'cdn:@mescius/wijmo.barcode/index.js', '@mescius/wijmo.barcode.common': 'cdn:@mescius/wijmo.barcode.common/index.js', '@mescius/wijmo.barcode.composite': 'cdn:@mescius/wijmo.barcode.composite/index.js', '@mescius/wijmo.barcode.specialized': 'cdn:@mescius/wijmo.barcode.specialized/index.js', jszip: 'pcdn:jszip@3.8.0/dist/jszip.js', 'bootstrap.css': 'pcdn:bootstrap@5.3.8/dist/css/bootstrap.min.css', css: 'pcdn:systemjs-plugin-css@0.1.37/css.js', 'plugin-babel': 'pcdn:systemjs-plugin-babel@0.0.25/plugin-babel.js', 'systemjs-babel-build': 'pcdn:systemjs-plugin-babel@0.0.25/systemjs-babel-browser.js', }, // packages tells the System loader how to load when no filename and/or no extension packages: { src: { defaultExtension: 'js', }, node_modules: { defaultExtension: 'js', }, }, }); })(this);