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);
}
}