Hi, I am using an external form component to add events to the scheduler. I would like to make use of the Bryntum Scheduler listeners with this external form component. Specifically, how can I notify the scheduler when the save button on the custom form is clicked and trigger listeners like beforeEventSave?
Support Forum
Hey yash,
Thanks for reaching out and welcome to our forums.
You can use https://bryntum.com/products/scheduler/docs/api/Scheduler/view/Scheduler#function-trigger function to trigger the event you want manually, please pay attention to the parameters that need to be passed on each event. It works just like a normal event triggering on JavaScript.
Best regards,
Márcio
How to ask for help? Please read our Support Policy
Hi Marcio,
Thank you for your reply. The link you provided redirects me to the scheduler's documentation, but I couldn't find any property or function named "function trigger."
Let me provide more context regarding my question:
I have created a custom form using MUI components instead of using the built-in form provided by Bryntum Scheduler. The form is displayed when a user clicks on an option called "Add" in the scheduler menu defined using the scheduleMenuFeature. This sets a state to true, triggering the display of the custom form.
Here is a simplified version of my code:
<BryntumSchedulerPro
{...other configurations},
scheduleMenuFeature={{
items: {
addEvent: false,
extraItem:
{
text: addLabel,
icon: 'b-fa b-fa-fw b-fa-flag',
onItem({ resourceRecord }) {
setResourceData(resourceRecord);
setAddEvent(true);
}
}
}
}}
/>
{addEvent && (
<CustomFormDataProvider>
<CustomForm
open={addEvent}
setIsOpen={setAddEvent}
onClose={() => {
setAddEvent(false);
setResourceData({});
}}
data={resourceData}
/>
</CustomFormDataProvider>
)}
The CustomForm includes a Save button. I want to implement a solution where clicking the Save button triggers the beforeEventSave event in Bryntum Scheduler. I want to display this event on the scheduler and simultaneously call a save API to the backend.
Currently, I am referring to this example: https://bryntum.com/products/scheduler/examples/custom-event-editor/. However, in this example, the form is within the scheduler’s context.
In my case, the custom form exists outside the scheduler’s context, meaning I don’t have direct access to resources like the eventStore, assignmentStore, etc. Additionally, the example fetches form data by referencing form component IDs, which doesn’t align with my implementation.
Could you guide me on how to trigger the beforeEventSave event from my external form and interact with the scheduler’s eventStore to add or update events programmatically?
If additional steps or configurations are required, I’d appreciate the clarification. Thank you!
Hey yash,
Regarding the link, you need to enable the option to show advanced API (as you can see on the attached screenshot).
I'm sharing an updated demo code of how to add a custom event inside the Scheduler and outside the scheduler, which you can use to understand how you can set things in your project (also, check the attached video to see it working).
class Match extends EventModel {
static get fields() {
return [
{ name : 'duration', defaultValue : 3 },
{ name : 'durationUnit', defaultValue : 'h' }
];
}
}
// Adding outside Scheduler listener
document.getElementById("container").addEventListener('customEvent', ev => {
console.log('addEventListener customEvent', ev)
})
const scheduler = new Scheduler({
appendTo : 'container',
startDate : new Date(2020, 8, 18),
endDate : new Date(2020, 8, 29),
viewPreset : 'dayAndWeek',
rowHeight : 85,
barMargin : 0,
fillTicks : true,
tickSize : 215,
createEventOnDblClick : false,
// These are set to null to have less default styling from Scheduler interfering with custom CSS.
// Makes life easier when you are creating a custom look
eventColor : null,
eventStyle : null,
// CrudManager is used to load data to all stores in one go (Events, Resources and Assignments)
crudManager : {
autoLoad : true,
// This config enables response validation and dumping of found errors to the browser console.
// It's meant to be used as a development stage helper only so please set it to false for production systems.
validateResponse : true,
eventStore : {
// Provide our custom event model representing a single match
modelClass : Match
},
transport : {
load : {
url : 'data/data.json'
}
}
},
features : {
// Features disabled to give a better demo experience
eventDragCreate : false,
eventResize : false,
columnLines : false,
// Initial sort
sort : 'name'
},
columns : [
{ text : 'Name', field : 'name', width : 130 }
],
// A custom eventRenderer, used to generate the contents of the events
eventRenderer({ eventRecord, assignmentRecord, renderData }) {
const
{ resources } = eventRecord,
// 19:00
startTime = DateHelper.format(eventRecord.startDate, 'HH:mm'),
// First resource is the home team, second the away
[home, away] = resources,
// If the assignment being rendered is the home team, this is a home game
homeGame = assignmentRecord.resource === home;
// Different icons depending on if the game is at home or away
renderData.iconCls = homeGame ? 'b-fa b-fa-hockey-puck' : 'b-fa b-fa-shuttle-van';
// HTML config:
// <div class="time">19:00</div>
// Home - Away
// <div class="arena">Arena name</div>
return {
children : [
{
className : 'time',
text : startTime
},
{
text : `${home.name} - ${away?.name || 'TBD'}`
},
{
className : 'arena',
text : home.arena
}
]
};
},
listeners : {
customEvent: ev => {
console.log(ev)
},
// Listener called before the built-in editor is shown
beforeEventEdit({ eventRecord, resourceRecord }) {
const teams = eventRecord.resources;
// Show custom editor
$('#customEditor').modal('show');
// Fill its fields
if (teams.length === 0) {
// New match being created
$('#home').val(resourceRecord.id);
}
else {
$('#home').val(teams[0].id);
$('#away').val(teams[1]?.id || '');
}
$('#startDate').val(DateHelper.format(eventRecord.startDate, 'YYYY-MM-DD'));
$('#startTime').val(DateHelper.format(eventRecord.startDate, 'HH:mm'));
// Store record being edited, to be able to write changes back to it later
this.editingRecord = eventRecord;
// Prevent built-in editor
return false;
},
paint({ firstPaint }) {
if (firstPaint) {
const me = this;
me.onEditorShow = me.onEditorShow.bind(me);
me.onEditorClose = me.onEditorClose.bind(me);
me.onEditorSaveClick = me.onEditorSaveClick.bind(me);
me.onEditorCancelClick = me.onEditorCancelClick.bind(me);
// Focus control when editor is shown
$('#customEditor').on('shown.bs.modal', me.onEditorShow);
// If they exit *not* via the save click, remove any provisional record added via context menu
$('#customEditor').on('hidden.bs.modal', me.onEditorClose);
// When clicking save in the custom editor
$('#save').on('click', me.onEditorSaveClick);
if (navigator.userAgent.match(/Firefox|Safari/)) {
$('#cancel').on('click', me.onEditorCancelClick);
}
}
},
beforeDestroy() {
$('#customEditor').off('hidden.bs.modal', this.onEditorClose);
$('#customEditor').off('shown.bs.modal', this.onEditorShow);
$('#save').off('click', this.onEditorSaveClick);
$('#cancel').off('click', this.onEditorCancelClick);
}
},
onEditorShow() {
$('#home').trigger('focus');
},
onEditorClose(e) {
const eventData = {
bubbles: true,
ev: e,
eventRecord: this.editingRecord
}
// Triggering outside Scheduler context
const customEvent = new CustomEvent('customEvent', eventData);
document.getElementById('container').dispatchEvent(customEvent);
// Triggering for Scheduler events
scheduler.trigger('customEvent', eventData)
if (this.editingRecord?.isCreating) {
this.editingRecord.remove();
delete this.editingRecord;
}
},
onEditorCancelClick() {
document.getElementById('customEditor').blur();
},
onEditorSaveClick(e) {
const
{
assignmentStore,
eventStore,
resourceStore,
editingRecord
} = this,
// Extract teams
home = $('#home').val(),
away = $('#away').val(),
// Extract date & time
date = $('#startDate').val(),
time = $('#startTime').val(),
oldTeams = editingRecord.resources,
newTeams = [resourceStore.getById(away), resourceStore.getById(home)],
teamChanges = ArrayHelper.delta(newTeams, oldTeams, true);
if (home === away) {
Toast.show('A team cannot play itself');
return false;
}
if (!home || !away) {
Toast.show('Both teams must be selected');
return false;
}
// Prevent multiple commits from this flow
assignmentStore.suspendAutoCommit();
// Avoid multiple redraws, from event changes + assignment changes
this.suspendRefresh();
editingRecord.beginBatch();
// Update record
editingRecord.set({
startDate : DateHelper.parse(date + ' ' + time, 'YYYY-MM-DD HH:mm')
});
// Update the two teams involved
eventStore.unassignEventFromResource(editingRecord, teamChanges.toRemove);
eventStore.assignEventToResource(editingRecord, teamChanges.toAdd);
editingRecord.endBatch();
// If it was a provisional event, passed in here from drag-create or dblclick or contextmenu,
// it's now it's no longer a provisional event and will not be removed in the focusout handler
// Also, when promoted to be permanent, auto syncing will kick in if configured.
editingRecord.isCreating = false;
assignmentStore.resumeAutoCommit();
// Redraw once
this.resumeRefresh(true);
}
});
If you still have questions about it, could you please share a runnable test case so we can better assist you with this topic?
- Attachments
-
- Screenshot 2025-01-27 at 10.29.47.png (217.29 KiB) Viewed 62 times
-
- Screen Recording 2025-01-27 at 10.26.19.mov
- (4.85 MiB) Downloaded 6 times
Best regards,
Márcio
How to ask for help? Please read our Support Policy