diff --git a/CS/Components/Pages/Index.razor.cs b/CS/Components/Pages/Index.razor.cs index 0c67a88..e782008 100644 --- a/CS/Components/Pages/Index.razor.cs +++ b/CS/Components/Pages/Index.razor.cs @@ -33,9 +33,9 @@ public partial class Index { private static string GetMenuIcon(string actionName) => MenuIcons[actionName]; private string GoToTodayText => ActiveViewType switch { - SchedulerViewType.Week or SchedulerViewType.WorkWeek => "Go to the current Week", - SchedulerViewType.Month => "Go to the current Month", - SchedulerViewType.Day or SchedulerViewType.Timeline => "Go to Today", + SchedulerViewType.Week or SchedulerViewType.WorkWeek => "Switch to Current Week", + SchedulerViewType.Month => "Switch to Current Month", + SchedulerViewType.Day or SchedulerViewType.Timeline => "Switch to Today", _ => "Error: Unknown view type" }; diff --git a/README.md b/README.md index e22d355..140fc63 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,234 @@ -![](https://img.shields.io/endpoint?url=https://codecentral.devexpress.com/api/v1/VersionRange/1223677914/26.1.3%2B) -[![](https://img.shields.io/badge/Open_in_DevExpress_Support_Center-FF7200?style=flat-square&logo=DevExpress&logoColor=white)](https://supportcenter.devexpress.com/ticket/details/T1328040) +![](https://img.shields.io/endpoint?url=https://codecentral.devexpress.com/api/v1/VersionRange/1211416592/25.2.6%2B) +[![](https://img.shields.io/badge/Open_in_DevExpress_Support_Center-FF7200?style=flat-square&logo=DevExpress&logoColor=white)](https://supportcenter.devexpress.com/ticket/details/T1326784) [![](https://img.shields.io/badge/📖_How_to_use_DevExpress_Examples-e9f6fc?style=flat-square)](https://docs.devexpress.com/GeneralInformation/403183) [![](https://img.shields.io/badge/💬_Leave_Feedback-feecdd?style=flat-square)](#does-this-example-address-your-development-requirementsobjectives) -# Product/Platform - Task +# Blazor Scheduler — Custom Context Menu for Scheduler Regions -This is the repository template for creating new examples. Describe the solved task here. +This example adds a DevExpress Blazor [Context Menu](https://docs.devexpress.com/Blazor/DevExpress.Blazor.DxContextMenu) to the DevExpress Blazor [Scheduler](https://docs.devexpress.com/Blazor/DevExpress.Blazor.DxScheduler). When users right-click within any Scheduler region, the application detects the clicked region and displays a context menu with relevant commands. -Put a screenshot that illustrates the result here. +| 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 | -Then, add implementation details (steps, code snippets, and other technical information in a free form), or add a link to an existing document with implementation details. +Toolbar menu: + +![Toolbar Context Menu](toolbar-menu.png) + +Resource header menu: + +![Resource Header Context Menu](resource-header-menu.png) + +## Implementation Details + +### Detect a Clicked Region and Display the Menu + +The application uses a combination of Blazor and JavaScript to detect the clicked region and display the context menu. For [appointments](#appointments), a shared appointment template identifies the clicked appointment. For [other regions and elements](#other-regions), 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. + +#### Appointments + +To detect a clicked appointment, `Index.razor` defines a shared [appointmentTemplate](CS/Components/Pages/Index.razor#L9) 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)](CS/Components/Pages/Index.razor.cs#L99). + + +**Index.razor** +```razor +@{ + RenderFragment appointmentTemplate = context => @
+
+ @context.Appointment.Subject +
+
; +} +``` + +**Index.razor.cs** +```csharp +private async Task ShowAppointmentContextMenu(MouseEventArgs e, DxSchedulerAppointmentItem appointment) { + if(ContextMenu is null || appointment is null) + return; + + ClickedRegion = "Appointment"; + ContextMenuAppointment = appointment; + await ContextMenu.ShowAsync(e); +} + +``` + +#### Other Regions + +The example relies on the [appointmentContextMenu.js](CS/wwwroot/js/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](CS/Components/Pages/Index.razor.cs#L160) event handler is used to apply custom CSS classes to Scheduler regions. + +**Index.razor.cs** +```csharp +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](CS/wwwroot/js/appointmentContextMenu.js) 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** +```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](CS/Components/Pages/Index.razor.cs#L83) method which then sets the `ClickedRegion` value and displays the menu at the cursor position. + +**Index.razor.cs** +```csharp +[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](CS/wwwroot/js/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. + +### Region-Aware Menu Commands + +[Index.razor](CS/Components/Pages/Index.razor#L83) declares a single [DxContextMenu](https://docs.devexpress.com/Blazor/DevExpress.Blazor.DxContextMenu) used for all regions. Its items are generated dynamically based on the `ClickedRegion` value. A `switch` block renders [DxContextMenuItem](https://docs.devexpress.com/Blazor/DevExpress.Blazor.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** +```razor + + + + @switch (ClickedRegion) + { + case "Appointment": + + break; + case "All Day Area": + @if (ActiveViewType != SchedulerViewType.Day) { + + } + + break; + case "Time Cell": + + @if (ActiveViewType != SchedulerViewType.Day) { + + } + break; + // other regions + } + + +``` + +### Handle Context Menu Clicks + +The [OnItemClick](CS/Components/Pages/Index.razor.cs#115) event handler processes item clicks based on command `Name` value: + +- `Edit` — opens the appointment edit form using `ShowAppointmentEditFormAsync`. +- `SwitchToDayView` / `SwitchToWeekView` / `SwitchToWorkWeekView` / `SwitchToMonthView` — changes `ActiveViewType` and navigates to the clicked day (if available). +- `GoToToday` — resets `StartDate` to `DateTime.Today`. +- `HideResource` / `ShowAllResources` — updates the `VisibleResources` collection bound to `VisibleResourcesDataSource`. +- `ToggleWorkTime` — toggles the `ShowWorkTimeOnly` option across views. + +**Index.razor.cs** +```csharp +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(); +} +``` ## Files to Review -- link.cs (VB: link.vb) -- link.js -- ... +- [Index.razor](CS/Components/Pages/Index.razor) +- [Index.razor.cs](CS/Components/Pages/Index.razor.cs) +- [Index.razor.css](CS/Components/Pages/Index.razor.css) +- [appointmentContextMenu.js](CS/wwwroot/js/appointmentContextMenu.js) +- [Program.cs](CS/Program.cs) +- [RecurringAppointmentCollection.cs](CS/Data/RecurringAppointmentCollection.cs) +- [ResourceCollection.cs](CS/Data/ResourceCollection.cs) ## Documentation -- link -- link -- ... +- [DevExpress Blazor Scheduler](https://docs.devexpress.com/Blazor/DevExpress.Blazor.DxScheduler) +- [DevExpress Blazor Scheduler - Appointments](https://docs.devexpress.com/Blazor/403663/scheduler/appointments) +- [DevExpress Blazor Context Menu](https://docs.devexpress.com/Blazor/DevExpress.Blazor.DxContextMenu) +- [Call JavaScript functions from .NET methods (Microsoft)](https://learn.microsoft.com/en-us/aspnet/core/blazor/javascript-interoperability/call-javascript-from-dotnet) + +## Related Examples -## More Examples +- [Blazor Scheduler - Get Started](https://github.com/DevExpress-Examples/blazor-scheduler-get-started) -- link -- link -- ... ## Does This Example Address Your Development Requirements/Objectives? -[](https://www.devexpress.com/support/examples/survey.xml?utm_source=github&utm_campaign=draft-Devexpress-Blazor-Scheduler-Context-Menu&~~~was_helpful=yes) [](https://www.devexpress.com/support/examples/survey.xml?utm_source=github&utm_campaign=draft-Devexpress-Blazor-Scheduler-Context-Menu&~~~was_helpful=no) +[](https://www.devexpress.com/support/examples/survey.xml?utm_source=github&utm_campaign=blazor-scheduler-context-menu&~~~was_helpful=yes) [](https://www.devexpress.com/support/examples/survey.xml?utm_source=github&utm_campaign=blazor-scheduler-context-menu&~~~was_helpful=no) (you will be redirected to DevExpress.com to submit your response) diff --git a/resource-header-menu.png b/resource-header-menu.png new file mode 100644 index 0000000..fa848d7 Binary files /dev/null and b/resource-header-menu.png differ diff --git a/toolbar-menu.png b/toolbar-menu.png new file mode 100644 index 0000000..49c9c47 Binary files /dev/null and b/toolbar-menu.png differ