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);