-
Notifications
You must be signed in to change notification settings - Fork 0
readme [loseva] #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
4867097
readme
MargaritaLoseva d301f8b
changes
MargaritaLoseva 33943cb
changes
MargaritaLoseva 932bf83
changes
MargaritaLoseva 3e1248e
Apply suggestions from code review
MargaritaLoseva d0d2592
Apply suggestions from code review
MargaritaLoseva 90e4aa6
changes
MargaritaLoseva 3090e0e
changes
MargaritaLoseva 36afaa4
update UI names
MargaritaLoseva 2703fa6
apply Ray suggestions
MargaritaLoseva 7950d26
update image
MargaritaLoseva File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,38 +1,234 @@ | ||
| <!-- default badges list --> | ||
|  | ||
| [](https://supportcenter.devexpress.com/ticket/details/T1328040) | ||
|  | ||
| [](https://supportcenter.devexpress.com/ticket/details/T1326784) | ||
| [](https://docs.devexpress.com/GeneralInformation/403183) | ||
| [](#does-this-example-address-your-development-requirementsobjectives) | ||
| <!-- default badges end --> | ||
| # 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`)* <br/> Switch to Today | | ||
| | Time Cell | Switch to Today <br/> Switch to Day View *(only if `ActiveViewType != Day`)* | | ||
| | Date Header | Switch to Day View *(only if `ActiveViewType != Day`)* <br/> Switch to Today | | ||
| | Day of Week Header | Switch to Today | | ||
| | Resource Header | Hide Resource *(only if visible resource count > 1)* <br/> Show All Resources | | ||
| | Time Ruler | Toggle Work Time | | ||
| | Toolbar | Switch to Day View *(if not `Day`)* <br/> Switch to Week View *(if not `Week`)* <br/> Switch to Work Week View *(if not `WorkWeek`)* <br/> Switch to Month View *(if not `Month`)* <br/> Switch to Timeline View *(if not `Timeline`)* <br/> 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: | ||
|
|
||
|  | ||
|
|
||
| Resource header menu: | ||
|
|
||
|  | ||
|
|
||
| ## 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<DxSchedulerAppointmentView> appointmentTemplate = context => @<div class="card @context.Label?.BackgroundCssClass"> | ||
| <div @oncontextmenu="((e) => ShowAppointmentContextMenu(e, context.Appointment))"> | ||
| @context.Appointment.Subject | ||
| </div> | ||
| </div>; | ||
| } | ||
| ``` | ||
|
|
||
| **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 | ||
| <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> | ||
| ``` | ||
|
|
||
| ### 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) | ||
|
|
||
|
MargaritaLoseva marked this conversation as resolved.
|
||
| ## Related Examples | ||
|
|
||
| ## More Examples | ||
| - [Blazor Scheduler - Get Started](https://github.com/DevExpress-Examples/blazor-scheduler-get-started) | ||
|
|
||
| - link | ||
| - link | ||
| - ... | ||
| <!-- feedback --> | ||
| ## Does This Example Address Your Development Requirements/Objectives? | ||
|
|
||
| [<img src="https://www.devexpress.com/support/examples/i/yes-button.svg"/>](https://www.devexpress.com/support/examples/survey.xml?utm_source=github&utm_campaign=draft-Devexpress-Blazor-Scheduler-Context-Menu&~~~was_helpful=yes) [<img src="https://www.devexpress.com/support/examples/i/no-button.svg"/>](https://www.devexpress.com/support/examples/survey.xml?utm_source=github&utm_campaign=draft-Devexpress-Blazor-Scheduler-Context-Menu&~~~was_helpful=no) | ||
| [<img src="https://www.devexpress.com/support/examples/i/yes-button.svg"/>](https://www.devexpress.com/support/examples/survey.xml?utm_source=github&utm_campaign=blazor-scheduler-context-menu&~~~was_helpful=yes) [<img src="https://www.devexpress.com/support/examples/i/no-button.svg"/>](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) | ||
| <!-- feedback end --> | ||
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.