This example adds a DevExpress Blazor Context Menu to the DevExpress Blazor Scheduler. When users right-click within any Scheduler region, the application detects the clicked region and displays a context menu with relevant commands.
| Scheduler Region | Context Menu Commands |
|---|---|
| Appointment | Edit |
| All Day Area | Switch to Day View (only if ActiveViewType != Day) Switch to Today |
| Time Cell | Switch to Today Switch to Day View (only if ActiveViewType != Day) |
| Date Header | Switch to Day View (only if ActiveViewType != Day) Switch to Today |
| Day of Week Header | Switch to Today |
| Resource Header | Hide Resource (only if visible resource count > 1) Show All Resources |
| Time Ruler | Toggle Work Time |
| Toolbar | Switch to Day View (if not Day) Switch to Week View (if not Week) Switch to Work Week View (if not WorkWeek) Switch to Month View (if not Month) Switch to Timeline View (if not Timeline) Switch to Today |
Toolbar menu:
Resource header menu:
The application uses a combination of Blazor and JavaScript to detect the clicked region and display the context menu. For appointments, a shared appointment template identifies the clicked appointment. For other regions and elements, a JavaScript module handles the contextmenu event and calls back into .NET to display the menu.
If you need to add a context menu to an element that supports templates, you can use the approach used for appointments. If you need to add a context menu to an element that does not support templates, you can use the same approach as this application uses for other regions.
To detect a clicked appointment, Index.razor defines a shared appointmentTemplate object. The template is reused across all Scheduler views (Day, Week, Work Week, Month, and Timeline).
The template's context parameter contains an Appointment property that gets the current appointment. The template handles right-clicks on the appointment (@oncontextmenu event) and calls ShowAppointmentContextMenu(e, context.Appointment).
Index.razor
@{
RenderFragment<DxSchedulerAppointmentView> appointmentTemplate = context => @<div class="card @context.Label?.BackgroundCssClass">
<div @oncontextmenu="((e) => ShowAppointmentContextMenu(e, context.Appointment))">
@context.Appointment.Subject
</div>
</div>;
}Index.razor.cs
private async Task ShowAppointmentContextMenu(MouseEventArgs e, DxSchedulerAppointmentItem appointment) {
if(ContextMenu is null || appointment is null)
return;
ClickedRegion = "Appointment";
ContextMenuAppointment = appointment;
await ContextMenu.ShowAsync(e);
}The example relies on the appointmentContextMenu.js module to process the following Scheduler regions: date header, time cell, resource header, all-day cell, and day-of-week header. The module handles the browser's contextmenu event, determines region by a CSS class, and displays the appropriate menu on the .NET side of the application.
The OnHtmlCellDecoration event handler is used to apply custom CSS classes to Scheduler regions.
Index.razor.cs
private void OnHtmlCellDecoration(SchedulerHtmlCellDecorationEventArgs e) {
switch(e.CellType) {
case SchedulerCellType.DateHeader:
e.CssClass = "custom-date-header";
break;
case SchedulerCellType.TimeCell:
e.CssClass = "custom-time-cell";
break;
case SchedulerCellType.ResourceHeader:
e.CssClass = "custom-resource-header-" + e.Resources.FirstOrDefault()?.Id;
break;
case SchedulerCellType.AllDayTimeCell:
e.CssClass = "custom-all-date-time-cell";
break;
case SchedulerCellType.DayOfWeekHeader:
e.CssClass = "custom-day-of-week-header";
break;
case SchedulerCellType.None:
e.CssClass = "custom-none";
break;
}
}The getRegion method determines an event target, matches region CSS classes, and passes region name (along with an optional resource id and cell start_date) back to the Scheduler component:
appointmentContextMenu.js
export function setup(schedulerElement, dotNetRef) {
schedulerElement.addEventListener('contextmenu', (e) => {
e.preventDefault();
const region = getRegion(e.target, schedulerElement);
if (!region) return;
dotNetRef.invokeMethodAsync('ShowAppointmentContextMenu',
e.clientX, e.clientY, e.pageX, e.pageY, region.name, region.id, region.start_date);
});
}When a region is detected, the module calls the [JSInvokable] ShowAppointmentContextMenu method which then sets the ClickedRegion value and displays the menu at the cursor position.
Index.razor.cs
[JSInvokable]
public async Task ShowAppointmentContextMenu(double clientX, double clientY, double pageX, double pageY, string region, int? id, long? startDate) {
ClickedRegion = region;
ClickedId = id;
DayToGo = startDate.HasValue ? DateTimeOffset.FromUnixTimeMilliseconds(startDate.Value).DateTime : null;
StateHasChanged();
await (ContextMenu?.ShowAsync(new MouseEventArgs {
ClientX = clientX,
ClientY = clientY,
PageX = pageX,
PageY = pageY
}) ?? Task.FromResult(false));
}Note
The appointmentContextMenu.js module relies on DevExpress internal CSS classes (dxbl-sc-*, dxbl-v-*). These classes may change between release cycles. Review and update them as necessary when/if you upgrade DevExpress-powered Blazor app.
Index.razor declares a single DxContextMenu used for all regions. Its items are generated dynamically based on the ClickedRegion value. A switch block renders DxContextMenuItem items applicable to a clicked region and the current view. For example, "Open this day in Day View" is hidden when the Day View is active.
Index.razor
<DxContextMenu @ref="@ContextMenu" ItemClick="@OnItemClick">
<Items>
<DxContextMenuItem Text="@ClickedRegion" Enabled="false" CssClass="fw-bold"></DxContextMenuItem>
@switch (ClickedRegion)
{
case "Appointment":
<DxContextMenuItem Text="Edit" Name="Edit" IconUrl="@GetMenuIcon("Edit")"></DxContextMenuItem>
break;
case "All Day Area":
@if (ActiveViewType != SchedulerViewType.Day) {
<DxContextMenuItem Text="@OpenDayInDayViewText" Name="SwitchToDayView" IconUrl="@GetMenuIcon("SwitchToDayView")"></DxContextMenuItem>
}
<DxContextMenuItem Text="@GoToTodayText" Name="GoToToday" IconUrl="@GetMenuIcon("GoToToday")"></DxContextMenuItem>
break;
case "Time Cell":
<DxContextMenuItem Text="@GoToTodayText" Name="GoToToday" IconUrl="@GetMenuIcon("GoToToday")"></DxContextMenuItem>
@if (ActiveViewType != SchedulerViewType.Day) {
<DxContextMenuItem Text="@OpenDayInDayViewText" Name="SwitchToDayView" IconUrl="@GetMenuIcon("SwitchToDayView")"></DxContextMenuItem>
}
break;
// other regions
}
</Items>
</DxContextMenu>The OnItemClick event handler processes item clicks based on command Name value:
Edit— opens the appointment edit form usingShowAppointmentEditFormAsync.SwitchToDayView/SwitchToWeekView/SwitchToWorkWeekView/SwitchToMonthView— changesActiveViewTypeand navigates to the clicked day (if available).GoToToday— resetsStartDatetoDateTime.Today.HideResource/ShowAllResources— updates theVisibleResourcescollection bound toVisibleResourcesDataSource.ToggleWorkTime— toggles theShowWorkTimeOnlyoption across views.
Index.razor.cs
private async Task OnItemClick(ContextMenuItemClickEventArgs args) {
switch(args.ItemInfo.Name) {
case "Edit":
if(Scheduler is null || ContextMenuAppointment is null)
break;
await Scheduler.ShowAppointmentEditFormAsync(false, ContextMenuAppointment);
break;
case "GoToToday":
StartDate = DateTime.Today;
break;
case "SwitchToDayView":
ActiveViewType = SchedulerViewType.Day;
if(DayToGo is not null)
StartDate = DayToGo.Value;
break;
// other commands
}
StateHasChanged();
}- Index.razor
- Index.razor.cs
- Index.razor.css
- appointmentContextMenu.js
- Program.cs
- RecurringAppointmentCollection.cs
- ResourceCollection.cs
- DevExpress Blazor Scheduler
- DevExpress Blazor Scheduler - Appointments
- DevExpress Blazor Context Menu
- Call JavaScript functions from .NET methods (Microsoft)
(you will be redirected to DevExpress.com to submit your response)

