Context Menu

This sample uses the FlexScheduler contextMenu property and SchedulerMenu class to provide commands for scheduler cells and event items.

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, getEvents, getTeams } from './data'; // const CustomMenuAction = { FocusBlock: 100, DuplicateNextDay: 101, TogglePriority: 102, }; class BusinessSchedulerMenu extends wjcScheduler.SchedulerMenu { constructor(element, scheduler) { super(element, scheduler); this.targetCell = null; this.targetItem = null; this.control = scheduler; this.itemsSource = [ { text: 'Add event...', action: wjcScheduler.SchedulerMenuAction.Add, icon: 'bi bi-calendar-plus' }, { text: 'Create focus block', action: CustomMenuAction.FocusBlock, icon: 'bi bi-bullseye' }, { text: 'Edit event...', action: wjcScheduler.SchedulerMenuAction.Edit, icon: 'bi bi-pencil-square' }, { text: 'Duplicate to next day', action: CustomMenuAction.DuplicateNextDay, icon: 'bi bi-copy' }, { text: 'Toggle priority', action: CustomMenuAction.TogglePriority, icon: 'bi bi-star' }, { text: 'Delete event', action: wjcScheduler.SchedulerMenuAction.Delete, icon: 'bi bi-trash3' }, { text: 'Go to today', action: wjcScheduler.SchedulerMenuAction.Today, icon: 'bi bi-calendar2-day' }, ]; } show(position) { const hit = this.control.hitTest(position); this.targetCell = hit.hitTestType === wjcScheduler.SchedulerHitTestType.Cell ? hit.getHitInfo() : null; this.targetItem = hit.hitTestType === wjcScheduler.SchedulerHitTestType.Item ? hit.getHitInfo() : null; super.show(position); } canExecute(action) { const actionValue = action; if (actionValue === CustomMenuAction.FocusBlock) return !!this.targetCell; if (actionValue === CustomMenuAction.DuplicateNextDay || actionValue === CustomMenuAction.TogglePriority) return !!this.targetItem; return super.canExecute(action); } executeContextMenuAction(action) { var _a; const actionValue = action; const sourceItem = (_a = this.targetItem) === null || _a === void 0 ? void 0 : _a.item; if (actionValue === CustomMenuAction.FocusBlock && this.targetCell) { const start = this.targetCell.start; this.control.create({ id: getNextId(this.control), subject: 'Focus block', start, end: new Date(start.getTime() + 30 * 60000), teamId: this.targetCell.resources.teamId || 'product', priority: true, }); return; } if (actionValue === CustomMenuAction.DuplicateNextDay && sourceItem) { this.control.create(Object.assign(Object.assign({}, sourceItem), { id: getNextId(this.control), subject: `${sourceItem.subject} copy`, start: addDays(sourceItem.start, 1), end: addDays(sourceItem.end, 1) })); return; } if (actionValue === CustomMenuAction.TogglePriority && sourceItem) { this.control.update(sourceItem, { priority: !sourceItem.priority }); return; } super.executeContextMenuAction(action); } } document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', init) : init(); function init() { const displayDate = getDisplayDate(); const events = new wjcCore.CollectionView(getEvents()); const scheduler = new wjcScheduler.FlexScheduler('#theScheduler', { itemsSource: events, resources: [ { fieldExpr: 'teamId', label: 'Team', dataSource: getTeams(), allowMultiple: false, valueExpr: 'id', displayExpr: 'name', backgroundColorExpr: 'backgroundColor', foreColorExpr: 'foreColor', }, ], definition: getWorkWeekDefinition(renderEvent), displayDate, workHours: [{ start: '08:00', end: '18:00' }], toolbarViewItems: [{ name: 'Work Week', key: wjcScheduler.SchedulerBuiltInViewItem.WorkWeek }], }); const contextMenu = new BusinessSchedulerMenu('#schedulerMenu', scheduler); contextMenu.dropDownCssClass += ' sample-context-menu'; scheduler.contextMenu = contextMenu; scheduler.itemChanged.addHandler((_sender, args) => { const item = args.result && args.result.item; setStatus(`${getActionText(args.request.action)}${(item === null || item === void 0 ? void 0 : item.subject) ? ` "${item.subject}"` : ''}. ${events.sourceCollection.length} events.`); }); scheduler.displayDateChanged.addHandler(() => setStatus(`Viewing ${wjcCore.Globalize.format(scheduler.displayDate, 'MMMM d, yyyy')}.`)); document.querySelector('#sampleWeekButton').addEventListener('click', () => { scheduler.displayDate = displayDate; scheduler.focus(); }); } function renderEvent(ctx, element) { const item = ctx.item; const icon = document.createElement('i'); icon.className = `bi ${item.priority ? 'bi-star-fill' : 'bi-calendar-event'}`; icon.setAttribute('aria-hidden', 'true'); element.textContent = ''; element.append(icon, document.createTextNode(item.subject || 'Untitled event')); element.classList.toggle('priority-event', !!item.priority); return null; } function getNextId(scheduler) { return scheduler.queryEvents(() => true).reduce((max, item) => Math.max(max, item.id), 0) + 1; } function addDays(value, days) { const result = new Date(value); result.setDate(result.getDate() + days); return result; } function getWorkWeekDefinition(itemTemplate) { const definition = wjcScheduler.createSchedulerDefinitionPreset(wjcScheduler.SchedulerBuiltInViewItem.WorkWeek); return Object.assign(Object.assign({}, definition), { itemTemplate, rows: { bindings: [ { type: wjcScheduler.SchedulerAxisBindingType.Time, unit: wjcScheduler.SchedulerTimeUnit.Hour, visible: (args) => args.hour >= 8 && args.hour < 18, }, ], } }); } function getActionText(action) { switch (action) { case wjcScheduler.SchedulerItemChangeAction.Create: return 'Created'; case wjcScheduler.SchedulerItemChangeAction.Update: return 'Updated'; case wjcScheduler.SchedulerItemChangeAction.Remove: return 'Deleted'; default: return 'Changed'; } } function setStatus(message) { document.querySelector('#menuStatus').textContent = message; }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>MESCIUS Wijmo Scheduler Context Menu</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"> <!-- 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"> <div class="sample-bar"> <p id="menuStatus" class="menu-status" role="status" aria-live="polite"><i class="bi bi-mouse2" aria-hidden="true"></i> Right-click a slot or event for built-in and business-specific actions.</p> <button id="sampleWeekButton" class="btn btn-sm btn-outline-secondary" type="button"><i class="bi bi-calendar-week" aria-hidden="true"></i> Sample week</button> </div> <div id="theScheduler"></div> <div id="schedulerMenu" aria-label="Scheduler context menu"></div> </main> </body> </html>
function createDate(day, hour, minute = 0) { return new Date(2026, 4, day, hour, minute); } export function getDisplayDate() { return createDate(11, 9); } export function getTeams() { return [ { id: 'product', name: 'Product', backgroundColor: '#1769aa', foreColor: '#ffffff' }, { id: 'design', name: 'Design', backgroundColor: '#7b3f98', foreColor: '#ffffff' }, { id: 'success', name: 'Customer Success', backgroundColor: '#087f5b', foreColor: '#ffffff' }, { id: 'operations', name: 'Operations', backgroundColor: '#a44a19', foreColor: '#ffffff' }, ]; } export function getEvents() { return [ { id: 1, subject: 'Sprint Planning', start: createDate(11, 9), end: createDate(11, 10, 30), teamId: 'product', priority: true, }, { id: 2, subject: 'Design Critique', start: createDate(11, 13), end: createDate(11, 14), teamId: 'design' }, { id: 3, subject: 'Customer Workshop', start: createDate(12, 10), end: createDate(12, 11, 30), teamId: 'success', }, { id: 4, subject: 'Capacity Review', start: createDate(12, 14), end: createDate(12, 15), teamId: 'operations' }, { id: 5, subject: 'Roadmap Checkpoint', start: createDate(13, 11), end: createDate(13, 12), teamId: 'product' }, { id: 6, subject: 'Research Playback', start: createDate(14, 9, 30), end: createDate(14, 10, 30), teamId: 'design', }, { id: 7, subject: 'Renewal Review', start: createDate(14, 14), end: createDate(14, 15), teamId: 'success' }, { id: 8, subject: 'Release Readiness', start: createDate(15, 10), end: createDate(15, 11, 30), teamId: 'operations', }, ]; }
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; } .sample-bar { display: flex; min-height: 44px; margin-bottom: 6px; padding: 5px 9px; border: 1px solid #dbe2ea; border-left: 3px solid #0f6cbd; background: #f8fafc; align-items: center; justify-content: flex-start; gap: 8px; } .menu-status { min-width: 0; margin: 0; color: #334155; font-size: 14px; line-height: 20px; } #sampleWeekButton { flex: 0 0 auto; } .btn:focus-visible { border-color: #0f6cbd; box-shadow: 0 0 0 2px rgb(15 108 189 / 22%); } #schedulerMenu { display: none; } #theScheduler { height: calc(100vh - 50px); min-height: 560px; } .sample-context-menu { min-width: 220px; border-color: #94a3b8; box-shadow: 0 4px 12px rgb(15 23 42 / 18%); } .sample-context-menu .bi { width: 18px; margin-right: 8px; color: #526170; text-align: center; } .priority-event { box-shadow: inset 3px 0 #fbbf24; } .priority-event > .bi { margin-right: 8px; color: #fde68a; } .wj-scheduler-item > .bi { margin-right: 8px; } @media (max-width: 768px) { .sample-bar { align-items: flex-start; } .menu-status { overflow-wrap: anywhere; } #theScheduler { height: 790px; min-height: 680px; } } 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);