Event Editor

This sample demonstrates the built-in FlexScheduler event editor and a custom editor implemented with the public create, update, remove, openCellEditor, and openItemEditor APIs.

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 wjcInput from '@mescius/wijmo.input'; import * as wjcScheduler from '@mescius/wijmo.scheduler'; import { getDisplayDate, getEvents, getRooms } from './data'; // document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', init) : init(); function init() { const rooms = getRooms(); let nextId = 9; let editorMode = 'builtIn'; let editingItem = null; let isNewItem = false; let selectedItem = null; let selectedCell = null; const events = new wjcCore.CollectionView(getEvents(), { newItemCreator: () => ({ id: nextId++ }) }); const scheduler = new wjcScheduler.FlexScheduler('#theScheduler', { itemsSource: events, resources: [ { fieldExpr: 'roomId', label: 'Room', dataSource: rooms, allowMultiple: false, valueExpr: 'id', displayExpr: 'name', backgroundColorExpr: 'backgroundColor', foreColorExpr: 'foreColor', }, ], definition: getWorkWeekDefinition(), displayDate: getDisplayDate(), workHours: [{ start: '08:00', end: '18:00' }], toolbarViewItems: [{ name: 'Work Week', key: wjcScheduler.SchedulerBuiltInViewItem.WorkWeek }], }); const popup = new wjcInput.Popup('#customEditor', { hideTrigger: wjcInput.PopupTrigger.None }); const startInput = new wjcInput.InputDateTime('#customStart', { isRequired: true, format: 'MMM d, yyyy h:mm tt' }); const endInput = new wjcInput.InputDateTime('#customEnd', { isRequired: true, format: 'MMM d, yyyy h:mm tt' }); const roomInput = new wjcInput.ComboBox('#customRoom', { itemsSource: rooms, displayMemberPath: 'name', selectedValuePath: 'id', }); const subjectInput = document.querySelector('#customSubject'); const descriptionInput = document.querySelector('#customDescription'); const deleteButton = document.querySelector('#deleteEditorButton'); const error = document.querySelector('#customEditorError'); const openCustomButton = document.querySelector('#openCustomEditorButton'); document.querySelectorAll('[data-editor-mode]').forEach((button) => { button.addEventListener('click', () => { editorMode = button.dataset.editorMode; document.querySelectorAll('[data-editor-mode]').forEach((candidate) => { candidate.setAttribute('aria-pressed', String(candidate === button)); }); setStatus(editorMode === 'builtIn' ? 'Built-in Scheduler editor is active.' : 'Select an event or time slot, then open the custom editor.'); openCustomButton.hidden = editorMode !== 'custom'; openCustomButton.disabled = editorMode !== 'custom' || (!selectedItem && !selectedCell); }); }); scheduler.hostElement.addEventListener('click', (event) => { if (editorMode !== 'custom') return; const hit = scheduler.hitTest(event); selectedItem = hit.hitTestType === wjcScheduler.SchedulerHitTestType.Item ? hit.getHitInfo().item : null; selectedCell = hit.hitTestType === wjcScheduler.SchedulerHitTestType.Cell ? hit.getHitInfo() : null; openCustomButton.disabled = !selectedItem && !selectedCell; if (selectedItem) setStatus(`Selected "${selectedItem.subject}" for custom editing.`); if (selectedCell) setStatus(`Selected ${wjcCore.Globalize.format(selectedCell.start, 'ddd, h:mm tt')} for a new event.`); }); openCustomButton.addEventListener('click', () => { if (selectedItem) openCustomEditor(selectedItem, false); if (selectedCell) openCellEditor(selectedCell); }); scheduler.hostElement.addEventListener('dblclick', (event) => { if (editorMode !== 'custom') return; const hit = scheduler.hitTest(event); if (hit.hitTestType !== wjcScheduler.SchedulerHitTestType.Cell && hit.hitTestType !== wjcScheduler.SchedulerHitTestType.Item) return; event.preventDefault(); event.stopImmediatePropagation(); if (hit.hitTestType === wjcScheduler.SchedulerHitTestType.Item) { const presentation = hit.getHitInfo(); openCustomEditor(presentation.item, false); } else { openCellEditor(hit.getHitInfo()); } }, true); document.querySelector('#saveEditorButton').addEventListener('click', () => { const subject = subjectInput.value.trim(); if (!subject || !startInput.value || !endInput.value || endInput.value <= startInput.value || !roomInput.selectedValue) { error.textContent = 'Enter a subject, valid time range and room.'; return; } const values = { subject, start: startInput.value, end: endInput.value, roomId: roomInput.selectedValue, description: descriptionInput.value.trim(), }; if (isNewItem) { scheduler.create(values); } else { scheduler.update(editingItem, values); } popup.hide(); }); deleteButton.addEventListener('click', () => { if (editingItem && !isNewItem) scheduler.remove(editingItem); popup.hide(); }); document.querySelector('#cancelEditorButton').addEventListener('click', () => popup.hide()); document.querySelector('#closeEditorButton').addEventListener('click', () => popup.hide()); scheduler.itemChanged.addHandler((_sender, args) => updateStatus(args, events, rooms)); function openCellEditor(cell) { openCustomEditor({ id: nextId, subject: '', start: cell.start, end: cell.end, roomId: cell.resources.roomId || rooms[0].id, description: '', }, true); } function openCustomEditor(item, createNew) { editingItem = item; isNewItem = createNew; subjectInput.value = item.subject || ''; startInput.value = item.start || null; endInput.value = item.end || null; roomInput.selectedValue = item.roomId || rooms[0].id; descriptionInput.value = item.description || ''; deleteButton.hidden = createNew; error.textContent = ''; popup.hostElement.querySelector('#customEditorTitle').textContent = createNew ? 'Create event' : 'Edit event'; popup.hostElement.querySelector('#customEditorSubtitle').textContent = createNew ? 'New event from selected time slot' : item.subject; popup.show(true); subjectInput.focus(); } } function getWorkWeekDefinition() { const definition = wjcScheduler.createSchedulerDefinitionPreset(wjcScheduler.SchedulerBuiltInViewItem.WorkWeek); return Object.assign(Object.assign({}, definition), { rows: { bindings: [ { type: wjcScheduler.SchedulerAxisBindingType.Time, unit: wjcScheduler.SchedulerTimeUnit.Hour, visible: (args) => args.hour >= 8 && args.hour < 18, }, ], } }); } function updateStatus(args, events, rooms) { const item = args.result && args.result.item; const action = getActionText(args.request.action); const room = item && rooms.find((candidate) => candidate.id === item.roomId); const count = events.sourceCollection.length; setStatus(`${action}${(item === null || item === void 0 ? void 0 : item.subject) ? ` "${item.subject}"` : ''}${room ? ` in ${room.name}` : ''}. ${count} events.`); } function setStatus(message) { document.querySelector('#crudStatus').textContent = message; } 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'; } }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>MESCIUS Wijmo Scheduler Event Editor</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="editor-toolbar"> <div class="editor-heading"><strong><i class="bi bi-pencil-square" aria-hidden="true"></i> Event editor</strong><span>Double-click a time slot or event.</span></div> <div class="editor-mode" role="group" aria-label="Editor type"> <button type="button" data-editor-mode="builtIn" aria-pressed="true">Built-in</button> <button type="button" data-editor-mode="custom" aria-pressed="false">Custom</button> </div> <button id="openCustomEditorButton" class="btn btn-sm btn-primary" type="button" hidden disabled><i class="bi bi-box-arrow-up-right" aria-hidden="true"></i> Open custom editor</button> <p id="crudStatus" class="crud-status" role="status" aria-live="polite">Built-in Scheduler editor is active.</p> </div> <div id="theScheduler"></div> <div id="customEditor" class="custom-editor" aria-labelledby="customEditorTitle"> <div class="custom-editor-header"> <div><span class="editor-icon"><i class="bi bi-calendar2-check" aria-hidden="true"></i></span><div><strong id="customEditorTitle">Schedule event</strong><span id="customEditorSubtitle">Custom Wijmo editor</span></div></div> <button id="closeEditorButton" class="icon-button" type="button" aria-label="Close editor"><i class="bi bi-x-lg" aria-hidden="true"></i></button> </div> <div class="custom-editor-body"> <label class="editor-field editor-field-full"><span>Subject</span><input id="customSubject" class="form-control" type="text" autocomplete="off"></label> <label class="editor-field"><span>Start</span><div id="customStart"></div></label> <label class="editor-field"><span>End</span><div id="customEnd"></div></label> <label class="editor-field editor-field-full"><span>Room</span><div id="customRoom"></div></label> <label class="editor-field editor-field-full"><span>Description</span><textarea id="customDescription" class="form-control" rows="3"></textarea></label> <p id="customEditorError" class="custom-editor-error" role="alert"></p> </div> <div class="custom-editor-footer"> <button id="deleteEditorButton" class="btn btn-sm btn-outline-danger" type="button"><i class="bi bi-trash3" aria-hidden="true"></i> Delete</button> <span></span> <button id="cancelEditorButton" class="btn btn-sm btn-outline-secondary" type="button">Cancel</button> <button id="saveEditorButton" class="btn btn-sm btn-primary" type="button"><i class="bi bi-check-lg" aria-hidden="true"></i> Save event</button> </div> </div> </main> </body> </html>
function createDate(day, hour, minute = 0) { return new Date(2026, 4, day, hour, minute); } export function getDisplayDate() { return createDate(13, 9); } export function getRooms() { return [ { id: 'atlas', name: 'Atlas Room', backgroundColor: '#1d4ed8', foreColor: '#ffffff' }, { id: 'cedar', name: 'Cedar Room', backgroundColor: '#0f766e', foreColor: '#ffffff' }, { id: 'studio', name: 'Design Studio', backgroundColor: '#7e22ce', foreColor: '#ffffff' }, { id: 'summit', name: 'Summit Room', backgroundColor: '#a13f16', foreColor: '#ffffff' }, ]; } export function getEvents() { return [ { id: 1, subject: 'Sprint Planning', start: createDate(11, 9), end: createDate(11, 10, 30), roomId: 'atlas', description: 'Confirm sprint goals and owners.', }, { id: 2, subject: 'Design Critique', start: createDate(11, 13), end: createDate(11, 14), roomId: 'studio', description: 'Review the new onboarding flow.', }, { id: 3, subject: 'Customer Workshop', start: createDate(12, 10), end: createDate(12, 11, 30), roomId: 'summit', description: 'Map the customer deployment plan.', }, { id: 4, subject: 'Hiring Panel', start: createDate(13, 11), end: createDate(13, 12, 30), roomId: 'cedar', description: 'Final interview panel.', }, { id: 5, subject: 'Release Readiness', start: createDate(14, 9, 30), end: createDate(14, 10, 30), roomId: 'atlas', description: 'Review launch blockers.', }, { id: 6, subject: 'Brand Review', start: createDate(15, 14), end: createDate(15, 15), roomId: 'studio', description: 'Approve campaign assets.', }, { id: 7, subject: 'Operations Review', start: createDate(11, 11), end: createDate(11, 12), roomId: 'cedar', description: 'Review weekly service metrics.', }, { id: 8, subject: 'Partner Briefing', start: createDate(11, 15), end: createDate(11, 16), roomId: 'summit', description: 'Prepare partner launch briefing.', }, ]; }
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; } .editor-toolbar { display: flex; min-height: 52px; align-items: center; gap: 12px; margin-bottom: 6px; padding: 6px 10px; border: 1px solid #dbe2ea; background: #f8fafc; } .editor-heading { display: flex; flex-direction: column; } .editor-heading strong { display: flex; align-items: center; gap: 6px; } .editor-heading span { color: #64748b; font-size: 12px; } .editor-mode { display: inline-flex; overflow: hidden; border: 1px solid #64748b; border-radius: 4px; } .editor-mode button { min-height: 32px; padding: 4px 11px; border: 0; border-right: 1px solid #64748b; background: #fff; color: #334155; font: inherit; font-size: 13px; } .editor-mode button:last-child { border-right: 0; } .editor-mode button[aria-pressed="true"] { background: #0f6cbd; color: #fff; } .crud-status { min-width: 0; margin: 0 0 0 auto; color: #475569; font-size: 13px; } #openCustomEditorButton { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; } #openCustomEditorButton[hidden] { display: none; } #theScheduler { height: calc(100vh - 58px); min-height: 560px; } .custom-editor { width: min(560px, calc(100vw - 24px)); overflow: hidden; border: 1px solid #94a3b8; border-radius: 6px; background: #fff; box-shadow: 0 18px 50px rgb(15 23 42 / 25%); } .custom-editor-header { display: flex; min-height: 64px; align-items: center; justify-content: space-between; padding: 10px 14px; border-bottom: 1px solid #dbe2ea; background: #f8fafc; } .custom-editor-header > div { display: flex; align-items: center; gap: 10px; } .custom-editor-header > div > div { display: flex; flex-direction: column; } .custom-editor-header span { color: #64748b; font-size: 12px; } .editor-icon { display: inline-grid; width: 36px; height: 36px; place-items: center; border-radius: 4px; background: #e0effc; color: #075ea8 !important; font-size: 17px !important; } .icon-button { display: inline-grid; width: 34px; height: 34px; place-items: center; border: 0; border-radius: 3px; background: transparent; color: #475569; } .icon-button:hover { background: #e2e8f0; } .custom-editor-body { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; padding: 14px; } .editor-field { display: flex; min-width: 0; flex-direction: column; gap: 4px; } .editor-field > span { color: #334155; font-size: 12px; font-weight: 650; } .editor-field-full { grid-column: 1 / -1; } .editor-field .wj-control { width: 100%; } .custom-editor-error { grid-column: 1 / -1; min-height: 18px; margin: 0; color: #b42318; font-size: 12px; } .custom-editor-footer { display: grid; grid-template-columns: auto 1fr auto auto; gap: 7px; padding: 10px 14px; border-top: 1px solid #dbe2ea; background: #f8fafc; } .btn:focus-visible, .editor-mode button:focus-visible, .icon-button:focus-visible { outline: 2px solid #0f6cbd; outline-offset: 1px; } @media (max-width: 700px) { .editor-toolbar { align-items: flex-start; flex-wrap: wrap; } .crud-status { flex: 1 1 100%; margin-left: 0; } #theScheduler { height: 760px; min-height: 680px; } .custom-editor-body { grid-template-columns: 1fr; max-height: 62vh; overflow: auto; } .editor-field-full { grid-column: auto; } .custom-editor-footer { grid-template-columns: 1fr 1fr; } .custom-editor-footer span { display: none; } } 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);