Hit Testing

This sample uses the FlexScheduler hitTest method and SchedulerHitTestInfo to identify scheduler headers, cells, and event items under the pointer.

FlexScheduler API Reference | SchedulerDefinition API Reference

import 'bootstrap.css'; import '@mescius/wijmo.styles/wijmo.css'; import './styles.css'; // import * as wjcCore from '@mescius/wijmo'; import * as wjcScheduler from '@mescius/wijmo.scheduler'; import { getDisplayDate, getMeetings, getRooms } from './data'; const dateTimeFormat = new Intl.DateTimeFormat(undefined, { weekday: 'short', month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit', }); document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', init) : init(); function init() { const scheduler = new wjcScheduler.FlexScheduler('#theScheduler', { itemsSource: new wjcCore.CollectionView(getMeetings()), resources: [ { fieldExpr: 'roomId', label: 'Room', dataSource: getRooms(), valueExpr: 'id', displayExpr: 'name', backgroundColorExpr: 'backgroundColor', foreColorExpr: 'foreColor', }, ], definition: getDefinition(), displayDate: getDisplayDate(), workHours: [{ start: '08:00', end: '17:00' }], toolbarViewItems: [{ name: 'Day', key: wjcScheduler.SchedulerBuiltInViewItem.Day }], }); scheduler.hostElement.addEventListener('pointermove', (event) => renderHit(scheduler.hitTest(event))); document.addEventListener('click', (event) => renderHit(scheduler.hitTest(event))); } function getDefinition() { return { key: 'hitTesting', rows: { bindings: [ { type: wjcScheduler.SchedulerAxisBindingType.Time, unit: wjcScheduler.SchedulerTimeUnit.Hour, visible: (args) => args.hour >= 8 && args.hour < 17, }, ], }, columns: { bindings: [ { type: wjcScheduler.SchedulerAxisBindingType.Resource, fieldExpr: 'roomId', }, ], }, cellSemantic: wjcScheduler.SchedulerCellSemantic.Timed, }; } function renderHit(hit) { const type = hit.hitTestType; const model = hit.getHitInfo(); const hitType = document.getElementById('hitType'); hitType.textContent = getHitTypeName(type); hitType.className = `is-${getHitTypeName(type).toLowerCase()}`; renderDetails(getDetails(type, model)); } function getDetails(type, model) { switch (type) { case wjcScheduler.SchedulerHitTestType.Cell: return getCellDetails(model); case wjcScheduler.SchedulerHitTestType.Item: return getItemDetails(model); case wjcScheduler.SchedulerHitTestType.ColumnHeader: case wjcScheduler.SchedulerHitTestType.RowHeader: return getHeaderDetails(model); case wjcScheduler.SchedulerHitTestType.Corner: return [{ label: 'Target', value: 'Header corner' }]; default: return [{ label: 'Target', value: 'Outside the Scheduler' }]; } } function getCellDetails(cell) { return [ { label: 'Start', value: formatDate(cell.start) }, { label: 'End', value: formatDate(cell.end) }, { label: 'Resources', value: formatResources(cell.resources) }, ]; } function getItemDetails(item) { const sourceItem = item.item; return [ { label: 'Subject', value: (sourceItem === null || sourceItem === void 0 ? void 0 : sourceItem.subject) || 'None' }, { label: 'Start', value: formatDate(sourceItem === null || sourceItem === void 0 ? void 0 : sourceItem.start) }, { label: 'End', value: formatDate(sourceItem === null || sourceItem === void 0 ? void 0 : sourceItem.end) }, { label: 'Room ID', value: (sourceItem === null || sourceItem === void 0 ? void 0 : sourceItem.roomId) || 'None' }, ]; } function getHeaderDetails(header) { return [ { label: 'Axis', value: header.axis }, { label: 'Text', value: header.text || 'None' }, { label: 'Level', value: String(header.level) }, ]; } function renderDetails(details) { const list = document.getElementById('hitDetails'); const content = []; details.forEach((detail) => { const term = document.createElement('dt'); const description = document.createElement('dd'); term.textContent = detail.label; description.textContent = detail.value; content.push(term, description); }); list.replaceChildren(...content); } function getHitTypeName(type) { const names = { [wjcScheduler.SchedulerHitTestType.None]: 'None', [wjcScheduler.SchedulerHitTestType.Cell]: 'Cell', [wjcScheduler.SchedulerHitTestType.Item]: 'Item', [wjcScheduler.SchedulerHitTestType.ColumnHeader]: 'ColumnHeader', [wjcScheduler.SchedulerHitTestType.RowHeader]: 'RowHeader', [wjcScheduler.SchedulerHitTestType.Corner]: 'Corner', }; return names[type] || 'None'; } function formatDate(value) { return value instanceof Date ? dateTimeFormat.format(value) : 'None'; } function formatResources(resources) { const entries = Object.keys(resources || {}).map((field) => `${field}: ${String(resources[field])}`); return entries.join(', ') || 'None'; }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>MESCIUS Wijmo Scheduler Hit Testing</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="icon" href="data:,"> <!-- 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> <main class="container-fluid"> <section class="hit-result" aria-live="polite"> <div class="result-heading"> <span>Hit type</span> <strong id="hitType">None</strong> </div> <dl id="hitDetails" class="result-details"> <dt>Target</dt> <dd>Move over a cell, header, or event.</dd> </dl> </section> <div id="theScheduler"></div> </main> </body> </html>
function createDate(hour, minute = 0) { return new Date(2026, 4, 13, hour, minute); } export function getDisplayDate() { return createDate(9); } export function getRooms() { return [ { id: 'atlas', name: 'Atlas Room', backgroundColor: '#2563eb', foreColor: '#ffffff' }, { id: 'cedar', name: 'Cedar Room', backgroundColor: '#0f766e', foreColor: '#ffffff' }, { id: 'studio', name: 'Design Studio', backgroundColor: '#a21caf', foreColor: '#ffffff' }, ]; } export function getMeetings() { return [ { id: 1, subject: 'Product Sync', start: createDate(9), end: createDate(10), roomId: 'atlas' }, { id: 2, subject: 'Design Review', start: createDate(10, 30), end: createDate(12), roomId: 'studio' }, { id: 3, subject: 'Hiring Panel', start: createDate(13), end: createDate(14, 30), roomId: 'cedar' }, { id: 4, subject: 'Release Readiness', start: createDate(15), end: createDate(16), roomId: 'atlas' }, ]; }
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; } .hit-result { display: flex; align-items: stretch; min-height: 64px; margin-bottom: 6px; border: 1px solid #cbd5e1; background: #f8fafc; } .result-heading { display: flex; flex: 0 0 160px; flex-direction: column; justify-content: center; padding: 9px 14px; border-right: 1px solid #cbd5e1; color: #475569; font-size: 12px; } .result-heading strong { color: #0f766e; font-size: 18px; font-weight: 600; } .result-heading strong.is-item { color: #0f6cbd; } .result-heading strong.is-cell { color: #237b4b; } .result-heading strong.is-columnheader, .result-heading strong.is-rowheader, .result-heading strong.is-corner { color: #76520e; } .result-details { display: grid; flex: 1 1 auto; grid-template-columns: repeat(4, max-content minmax(80px, 1fr)); align-content: center; min-width: 0; margin: 0; padding: 9px 14px; column-gap: 8px; row-gap: 2px; } .result-details dt { color: #64748b; font-size: 12px; font-weight: 600; } .result-details dd { overflow: hidden; min-width: 0; margin: 0; color: #1f2937; font-size: 13px; text-overflow: ellipsis; white-space: nowrap; } #theScheduler { height: 720px; min-height: 560px; } @media (max-width: 768px) { .hit-result { display: block; min-height: 138px; } .result-heading { flex-direction: row; align-items: baseline; justify-content: space-between; min-height: 42px; padding: 6px 10px; border-right: 0; border-bottom: 1px solid #cbd5e1; } .result-heading strong { font-size: 16px; } .result-details { grid-template-columns: max-content minmax(0, 1fr); min-height: 96px; padding: 7px 10px; } #theScheduler { height: 640px; min-height: 620px; } } 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);