From f66528ecf9bdc00e8f0b24af1180536311aca3fe Mon Sep 17 00:00:00 2001 From: Tsvetomir Hristov <106250052+Tsvetomir-Hr@users.noreply.github.com> Date: Mon, 20 Oct 2025 16:51:16 +0300 Subject: [PATCH 1/2] docs(Scheduler): add docs for recurrence edit mode --- .../scheduler/editing/edit-appointments.md | 4 +- components/scheduler/recurrence.md | 172 ++++++++++++++++++ 2 files changed, 175 insertions(+), 1 deletion(-) diff --git a/components/scheduler/editing/edit-appointments.md b/components/scheduler/editing/edit-appointments.md index 53d290b8dd..e46c344155 100644 --- a/components/scheduler/editing/edit-appointments.md +++ b/components/scheduler/editing/edit-appointments.md @@ -34,7 +34,7 @@ Main events you need to implement so you can store the appointment information c * `OnCreate` - fires when the user saves a new appointment, including an exception for a recurring appointment. * `OnUpdate` - fires when the user changes an existing appointment. Fires for the recurring appointment when an exception has been created for it. -* `OnDelete` - fires when the user deletes and appointment (including a recurring appointment). You can also enable a [delete confirmation dialog](#delete-confirmation-dialog). +* `OnDelete` - fires when the user deletes an appointment (including a recurring appointment). You can also enable a [delete confirmation dialog](#delete-confirmation-dialog). There are two other events that you are not required to handle - you can use them to implement application logic: @@ -45,6 +45,8 @@ There are two other events that you are not required to handle - you can use the * `OnEdit` does not fire when the user drags to resize or move appointments, it fires only for the advanced edit form (double clicks on the appointment). * `OnCancel` - fires when the user clicks the `Cancel` button in the edit form or the `[x]` close button at the window titlebar to discard the changes they just made to an appointment. +The event arguments that are received by the `OnEdit`, `OnUpdate`, and `OnDelete` events include a `EditMode` property that indicates whether the user chose to delete the entire series (`SchedulerRecurrenceEditMode.Series`) or only a single occurrence (`SchedulerRecurrenceEditMode.Occurrence`). For detailed information and examples, see the [Handling Recurring Appointments in CRUD Events](slug:scheduler-recurrence#handling-recurring-appointments-in-crud-events) section. + ## User Experience diff --git a/components/scheduler/recurrence.md b/components/scheduler/recurrence.md index e1f09181fe..3ad90976a6 100644 --- a/components/scheduler/recurrence.md +++ b/components/scheduler/recurrence.md @@ -15,6 +15,7 @@ The Telerik Scheduler for Blazor supports displaying and editing of recurring ap * Configure the Scheduler for using recurring appointments. * Define recurrence rules and recurrence exceptions in the Scheduler data. * Edit recurring appointments and exceptions. +* Handle editing and deleting recurring appointments with the recurrence context. ## Basics @@ -219,6 +220,177 @@ A single Scheduler data item defines one series of recurring appointments. Set t } ```` +## Handling Recurring Appointments in CRUD Events + +When users edit, update, or delete a recurring appointment, the Scheduler prompts them to choose whether to modify only the current occurrence or the entire series. This choice is reflected in the `EditMode` property of the event arguments. + +### RecurrenceEditMode Property + +The `OnEdit`, `OnUpdate`, and `OnDelete` event handlers receive event arguments that include a `EditMode` property. This property indicates the user's choice when interacting with recurring appointments: + +* `SchedulerRecurrenceEditMode.Series` - The user chose to edit or delete the entire series of recurring appointments. +* `SchedulerRecurrenceEditMode.Occurrence` - The user chose to edit or delete only a single occurrence of the recurring appointment. + +### Example + +You can use the `EditMode` property to implement different logic based on whether the user is modifying a single occurrence or the entire series. + +>caption Handle RecurrenceEditMode in OnUpdate and OnDelete events + +````RAZOR + + + + + + + + +@code { + private List SchedulerData { get; set; } = new(); + private DateTime SchedulerDate { get; set; } = DateTime.Today; + private SchedulerView SchedulerView { get; set; } = SchedulerView.Week; + private DateTime SchedulerViewStartTime { get; set; } = DateTime.Today.AddHours(8); + + private void OnSchedulerCreate(SchedulerCreateEventArgs args) + { + Appointment item = (Appointment)args.Item; + SchedulerData.Add(item); + } + + private void OnSchedulerEdit(SchedulerEditEventArgs args) + { + Appointment item = (Appointment)args.Item; + + // OnEdit fires when the user is about to edit or create an appointment + // SchedulerRecurrenceEditMode indicates whether they chose to edit the series or occurrence + if (args.EditMode == SchedulerRecurrenceEditMode.Series) + { + Console.WriteLine("User chose to edit the entire series"); + // The item represents the recurring appointment with RecurrenceRule + } + else if (args.EditMode == SchedulerRecurrenceEditMode.Occurrence) + { + Console.WriteLine("User chose to edit only this occurrence"); + // The item will have RecurrenceId set to the parent appointment's Id + Console.WriteLine($"Editing exception for recurring appointment: {item.RecurrenceId}"); + } + + // You can cancel the event to prevent editing + // args.IsCancelled = true; + } + + private void OnSchedulerUpdate(SchedulerUpdateEventArgs args) + { + Appointment item = (Appointment)args.Item; + + if (args.EditMode == SchedulerRecurrenceEditMode.Series) + { + // User chose to update the entire series + Console.WriteLine("Updating entire series of recurring appointments"); + + // Update the recurring appointment + int originalItemIndex = SchedulerData.FindIndex(a => a.Id == item.Id); + if (originalItemIndex >= 0) + { + SchedulerData[originalItemIndex] = item; + } + } + else if (args.EditMode == SchedulerRecurrenceEditMode.Occurrence) + { + // User chose to update only this occurrence + Console.WriteLine("Creating exception for single occurrence"); + + // This creates a new exception appointment + // The item will have RecurrenceId pointing to the parent + int originalItemIndex = SchedulerData.FindIndex(a => a.Id == item.Id); + if (originalItemIndex >= 0) + { + SchedulerData[originalItemIndex] = item; + } + } + } + + private void OnSchedulerDelete(SchedulerDeleteEventArgs args) + { + Appointment item = (Appointment)args.Item; + + if (args.EditMode == SchedulerRecurrenceEditMode.Series) + { + // User chose to delete the entire series + Console.WriteLine("Deleting entire series"); + + // Remove the recurring appointment + SchedulerData.Remove(item); + + // Optionally, remove all exceptions for this series + if (!string.IsNullOrEmpty(item.RecurrenceRule)) + { + SchedulerData.RemoveAll(a => a.RecurrenceId?.Equals(item.Id) == true); + } + } + else if (args.EditMode == SchedulerRecurrenceEditMode.Occurrence) + { + // User chose to delete only this occurrence + Console.WriteLine("Deleting single occurrence"); + + if (item.RecurrenceId != null) + { + // This is already an exception, just remove it + SchedulerData.Remove(item); + + // Update the parent appointment's RecurrenceExceptions list + var parentAppointment = SchedulerData.FirstOrDefault(a => a.Id.Equals(item.RecurrenceId)); + if (parentAppointment != null) + { + // Remove the exception date from the parent's list + parentAppointment.RecurrenceExceptions?.Remove(item.Start); + } + } + else + { + // This is a recurring appointment, create an exception + // The Scheduler automatically adds the occurrence date to RecurrenceExceptions + SchedulerData.Remove(item); + } + } + } + + public class Appointment + { + public Guid Id { get; set; } + public string Title { get; set; } = string.Empty; + public DateTime Start { get; set; } + public DateTime End { get; set; } + public bool IsAllDay { get; set; } + public string RecurrenceRule { get; set; } = string.Empty; + public List? RecurrenceExceptions { get; set; } + public Guid? RecurrenceId { get; set; } + + public Appointment() + { + Id = Guid.NewGuid(); + } + } +} +```` + +### Important Considerations + +* When the user edits or deletes a single occurrence of a recurring appointment, the Scheduler automatically manages the `RecurrenceExceptions` list and creates exception appointments with the appropriate `RecurrenceId`. +* The `RecurrenceEditMode` property is only relevant when working with recurring appointments. For regular (non-recurring) appointments, this property is not used. +* When creating a new appointment in a time slot that matches a recurring appointment, the user can choose to create an exception or a new independent appointment. + ## Recurrence Editor Components Telerik UI for Blazor provides standalone components that you can use to edit recurring appointments outside the Scheduler or in a [custom Scheduler popup edit form](slug:scheduler-kb-custom-edit-form). From f7f451daa6a051bebf5f016f3393e36875bc6a6b Mon Sep 17 00:00:00 2001 From: Tsvetomir Hristov <106250052+Tsvetomir-Hr@users.noreply.github.com> Date: Tue, 21 Oct 2025 11:36:13 +0300 Subject: [PATCH 2/2] chore: apply recommendation --- components/scheduler/recurrence.md | 38 +++++++++++++++--------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/components/scheduler/recurrence.md b/components/scheduler/recurrence.md index 3ad90976a6..e69c3cebb4 100644 --- a/components/scheduler/recurrence.md +++ b/components/scheduler/recurrence.md @@ -272,21 +272,21 @@ You can use the `EditMode` property to implement different logic based on whethe { Appointment item = (Appointment)args.Item; - // OnEdit fires when the user is about to edit or create an appointment - // SchedulerRecurrenceEditMode indicates whether they chose to edit the series or occurrence + // OnEdit fires when the user is about to edit or create an appointment. + // SchedulerRecurrenceEditMode indicates whether they chose to edit the series or occurrence. if (args.EditMode == SchedulerRecurrenceEditMode.Series) { Console.WriteLine("User chose to edit the entire series"); - // The item represents the recurring appointment with RecurrenceRule + // The item represents the recurring appointment with RecurrenceRule. } else if (args.EditMode == SchedulerRecurrenceEditMode.Occurrence) { Console.WriteLine("User chose to edit only this occurrence"); - // The item will have RecurrenceId set to the parent appointment's Id + // The item will have RecurrenceId set to the parent appointment's Id. Console.WriteLine($"Editing exception for recurring appointment: {item.RecurrenceId}"); } - // You can cancel the event to prevent editing + // You can cancel the event to prevent editing. // args.IsCancelled = true; } @@ -296,10 +296,10 @@ You can use the `EditMode` property to implement different logic based on whethe if (args.EditMode == SchedulerRecurrenceEditMode.Series) { - // User chose to update the entire series + // User chose to update the entire series. Console.WriteLine("Updating entire series of recurring appointments"); - // Update the recurring appointment + // Update the recurring appointment. int originalItemIndex = SchedulerData.FindIndex(a => a.Id == item.Id); if (originalItemIndex >= 0) { @@ -308,11 +308,11 @@ You can use the `EditMode` property to implement different logic based on whethe } else if (args.EditMode == SchedulerRecurrenceEditMode.Occurrence) { - // User chose to update only this occurrence + // User chose to update only this occurrence. Console.WriteLine("Creating exception for single occurrence"); - // This creates a new exception appointment - // The item will have RecurrenceId pointing to the parent + // This creates a new exception appointment. + // The item will have RecurrenceId pointing to the parent. int originalItemIndex = SchedulerData.FindIndex(a => a.Id == item.Id); if (originalItemIndex >= 0) { @@ -327,13 +327,13 @@ You can use the `EditMode` property to implement different logic based on whethe if (args.EditMode == SchedulerRecurrenceEditMode.Series) { - // User chose to delete the entire series + // User chose to delete the entire series. Console.WriteLine("Deleting entire series"); - // Remove the recurring appointment + // Remove the recurring appointment. SchedulerData.Remove(item); - // Optionally, remove all exceptions for this series + // Optionally, remove all exceptions for this series. if (!string.IsNullOrEmpty(item.RecurrenceRule)) { SchedulerData.RemoveAll(a => a.RecurrenceId?.Equals(item.Id) == true); @@ -341,26 +341,26 @@ You can use the `EditMode` property to implement different logic based on whethe } else if (args.EditMode == SchedulerRecurrenceEditMode.Occurrence) { - // User chose to delete only this occurrence + // User chose to delete only this occurrence. Console.WriteLine("Deleting single occurrence"); if (item.RecurrenceId != null) { - // This is already an exception, just remove it + // This is already an exception, just remove it. SchedulerData.Remove(item); - // Update the parent appointment's RecurrenceExceptions list + // Update the parent appointment's RecurrenceExceptions list. var parentAppointment = SchedulerData.FirstOrDefault(a => a.Id.Equals(item.RecurrenceId)); if (parentAppointment != null) { - // Remove the exception date from the parent's list + // Remove the exception date from the parent's list. parentAppointment.RecurrenceExceptions?.Remove(item.Start); } } else { - // This is a recurring appointment, create an exception - // The Scheduler automatically adds the occurrence date to RecurrenceExceptions + // This is a recurring appointment, create an exception. + // The Scheduler automatically adds the occurrence date to RecurrenceExceptions. SchedulerData.Remove(item); } }