Multi-level Resource Grouping

This sample combines time and resource axis bindings in a SchedulerDefinition. Switch the grouping layout to place resource levels on rows or columns.

FlexScheduler API Reference | SchedulerDefinition API Reference

import 'bootstrap.css'; import '@mescius/wijmo.styles/wijmo.css'; import './styles.css'; // import { CollectionView } from '@mescius/wijmo'; import * as wjcScheduler from '@mescius/wijmo.scheduler'; import { getDisplayDate, getEvents, getPriorities, getRegions, getServiceTypes, getSpecialties } from './data'; document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', init) : init(); function init() { const scheduler = new wjcScheduler.FlexScheduler('#theScheduler', { itemsSource: new CollectionView(getEvents()), resources: [ { fieldExpr: 'regionId', label: 'Region', dataSource: getRegions(), valueExpr: 'id', displayExpr: 'name', backgroundColorExpr: 'color', foreColorExpr: 'textColor', }, { fieldExpr: 'specialtyId', label: 'Specialty', dataSource: getSpecialties(), valueExpr: 'id', displayExpr: 'name', }, { fieldExpr: 'serviceTypeId', label: 'Service type', dataSource: getServiceTypes(), valueExpr: 'id', displayExpr: 'name', backgroundColorExpr: 'color', foreColorExpr: 'textColor', }, { fieldExpr: 'priorityId', label: 'Priority', dataSource: getPriorities(), valueExpr: 'id', displayExpr: 'name', }, ], definition: getDefinition('columns'), displayDate: getDisplayDate(), workHours: [{ start: '08:00', end: '18:00' }], toolbarViewItems: [{ name: 'Day', key: wjcScheduler.SchedulerBuiltInViewItem.Day }], }); document.querySelectorAll('[data-grouping-mode]').forEach((button) => { button.addEventListener('click', () => { const mode = button.dataset.groupingMode; scheduler.definition = getDefinition(mode); scheduler.hostElement.classList.toggle('row-groups-mode', mode === 'rows'); document.querySelectorAll('[data-grouping-mode]').forEach((candidate) => { const selected = candidate === button; candidate.setAttribute('aria-pressed', selected.toString()); }); const description = document.querySelector('#groupingDescription'); description.textContent = getModeDescription(mode); updateBindingOrder(mode); }); }); } function updateBindingOrder(mode) { const rowBinding = document.querySelector('#rowBinding'); const columnBinding = document.querySelector('#columnBinding'); rowBinding.textContent = mode === 'columns' ? 'Rows: Time' : 'Rows: Region > Specialty'; columnBinding.textContent = mode === 'rows' ? 'Columns: Time' : 'Columns: Service type > Priority'; } function getDefinition(mode) { const timeBinding = { type: wjcScheduler.SchedulerAxisBindingType.Time, unit: wjcScheduler.SchedulerTimeUnit.Hour, visible: (args) => args.hour >= 8 && args.hour < 18, }; const rowRegionBinding = { type: wjcScheduler.SchedulerAxisBindingType.Resource, fieldExpr: 'regionId' }; const rowSpecialtyBinding = { type: wjcScheduler.SchedulerAxisBindingType.Resource, fieldExpr: 'specialtyId', }; const columnServiceTypeBinding = { type: wjcScheduler.SchedulerAxisBindingType.Resource, fieldExpr: 'serviceTypeId', }; const columnPriorityBinding = { type: wjcScheduler.SchedulerAxisBindingType.Resource, fieldExpr: 'priorityId', }; return { key: `multiLevelGrouping-${mode}`, rows: { bindings: mode === 'columns' ? [timeBinding] : mode === 'rows' ? [rowRegionBinding, rowSpecialtyBinding] : [rowRegionBinding, rowSpecialtyBinding, timeBinding], }, columns: { bindings: mode === 'rows' ? [timeBinding] : [columnServiceTypeBinding, columnPriorityBinding], }, cellSemantic: wjcScheduler.SchedulerCellSemantic.Timed, }; } function getModeDescription(mode) { if (mode === 'rows') return 'Rows expand by region, then specialty; hours run across columns.'; if (mode === 'both') return 'Rows use region > specialty; columns use service type > priority.'; return 'Columns expand by service type, then priority.'; }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>MESCIUS Wijmo Scheduler Multi-level Grouping</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons/font/bootstrap-icons.css" rel="stylesheet"> <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> <main class="container-fluid"> <div class="grouping-toolbar"> <div class="grouping-copy"> <strong><i class="bi bi-diagram-3" aria-hidden="true"></i> Regional service board</strong> <span id="groupingDescription">Columns expand by service type, then priority.</span> </div> <div class="grouping-switch" role="group" aria-label="Resource grouping mode"> <button type="button" data-grouping-mode="columns" aria-pressed="true">Column groups</button> <button type="button" data-grouping-mode="rows" aria-pressed="false">Row groups</button> <button type="button" data-grouping-mode="both" aria-pressed="false">Rows + columns</button> </div> <div class="binding-order" aria-label="Resource binding order"> <span id="rowBinding">Rows: Time</span> <span id="columnBinding">Columns: Service type &gt; Priority</span> </div> </div> <div id="theScheduler"></div> </main> </body> </html>
function date(hour, minute = 0) { return new Date(2026, 4, 13, hour, minute); } export function getDisplayDate() { return date(9); } export function getRegions() { return [ { id: 'east', name: 'East region', color: '#2563eb', textColor: '#ffffff' }, { id: 'west', name: 'West region', color: '#0f766e', textColor: '#ffffff' }, ]; } export function getSpecialties() { return [ { id: 'installation', name: 'Installation' }, { id: 'support', name: 'Support' }, ]; } export function getServiceTypes() { return [ { id: 'onsite', name: 'On-site', color: '#2563eb', textColor: '#ffffff' }, { id: 'remote', name: 'Remote', color: '#0f766e', textColor: '#ffffff' }, { id: 'escalation', name: 'Escalation', color: '#b45309', textColor: '#ffffff' }, ]; } export function getPriorities() { return [ { id: 'standard', name: 'Standard' }, { id: 'urgent', name: 'Urgent' }, ]; } export function getEvents() { return [ { id: 1, subject: 'Branch Rollout', start: date(8, 30), end: date(10), regionId: 'east', specialtyId: 'installation', serviceTypeId: 'onsite', priorityId: 'standard', }, { id: 2, subject: 'Connectivity Audit', start: date(10), end: date(11, 30), regionId: 'east', specialtyId: 'support', serviceTypeId: 'remote', priorityId: 'standard', }, { id: 3, subject: 'Site Commissioning', start: date(11), end: date(12, 30), regionId: 'west', specialtyId: 'installation', serviceTypeId: 'onsite', priorityId: 'urgent', }, { id: 4, subject: 'Priority Repair', start: date(9), end: date(10, 30), regionId: 'west', specialtyId: 'support', serviceTypeId: 'escalation', priorityId: 'urgent', }, { id: 5, subject: 'Equipment Upgrade', start: date(13), end: date(14, 30), regionId: 'east', specialtyId: 'installation', serviceTypeId: 'onsite', priorityId: 'urgent', }, { id: 6, subject: 'Customer Handoff', start: date(14), end: date(15), regionId: 'east', specialtyId: 'support', serviceTypeId: 'remote', priorityId: 'standard', }, { id: 7, subject: 'Access Point Setup', start: date(15), end: date(16, 30), regionId: 'west', specialtyId: 'installation', serviceTypeId: 'onsite', priorityId: 'standard', }, { id: 8, subject: 'Service Validation', start: date(16), end: date(17), regionId: 'west', specialtyId: 'support', serviceTypeId: 'escalation', priorityId: 'urgent', }, ]; }
body { color: #1f2937; font-size: 15px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; user-select: none; margin: 0; } .container-fluid { box-sizing: border-box; margin: 0; padding: 5px; } .grouping-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 12px; min-height: 54px; margin-bottom: 6px; padding: 6px 10px; border: 1px solid #dbe2ea; background: #f8fafc; } .grouping-copy { display: flex; flex-direction: column; gap: 2px; } .grouping-copy strong { display: flex; align-items: center; gap: 7px; } .grouping-copy span { color: #64748b; font-size: 12px; } .binding-order { display: flex; align-items: center; gap: 7px; color: #475569; font-size: 12px; } .grouping-switch { display: inline-flex; overflow: hidden; border: 1px solid #64748b; border-radius: 4px; } .grouping-switch button { min-height: 32px; padding: 4px 10px; border: 0; border-right: 1px solid #64748b; background: #ffffff; color: #334155; font: inherit; font-size: 12px; } .grouping-switch button:last-child { border-right: 0; } .grouping-switch button[aria-pressed="true"] { background: #0f6cbd; color: #ffffff; } .grouping-switch button:focus-visible { outline: 2px solid #0f6cbd; outline-offset: -3px; } .binding-order span { padding: 4px 8px; border: 1px solid #cbd5e1; border-radius: 4px; background: #ffffff; font-weight: 600; } #theScheduler { height: calc(100vh - 62px); min-height: 600px; } .row-groups-mode { width: min(900px, 100%); height: 380px !important; min-height: 380px !important; } @media (max-width: 640px) { .row-groups-mode .wj-scheduler-timed-horizontal-header-frame, .row-groups-mode .wj-scheduler-timed-section { width: 600px !important; } } @media (max-width: 640px) { .grouping-toolbar { align-items: flex-start; flex-direction: column; } .grouping-switch { width: 100%; } .grouping-switch button { flex: 1 1 0; } #theScheduler { height: 680px; min-height: 680px; } .row-groups-mode { width: 100%; height: 430px !important; min-height: 430px !important; } } input, textarea, [contenteditable="true"] { user-select: text; }
(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.20262.60-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.scheduler': 'cdn:@mescius/wijmo.scheduler/index.js', '@mescius/wijmo.styles': 'cdn:@mescius/wijmo.styles', '@mescius/wijmo.cultures': 'cdn:@mescius/wijmo.cultures', 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: { src: { defaultExtension: 'js', }, node_modules: { defaultExtension: 'js', }, }, }); })(this);