Scheduler Recurrence
The Telerik Scheduler for Blazor supports displaying and editing of recurring appointments and exceptions. This article describes how to:
- Configure the Scheduler for using recurring appointments.
- Define recurrence rules and recurrence exceptions in the Scheduler data.
- Edit recurring appointments and exceptions.
Basics
To display recurring appointments in the Scheduler component, the model class must implement three recurrence-related properties:
RecurrenceRule
RecurrenceExceptions
RecurrenceId
You can also define custom property names through the respective Scheduler parameters:
RecurrenceRuleField
RecurrenceExceptionsField
RecurrenceIdField
A single Scheduler data item defines one series of recurring appointments. Set the RecurrenceRule
value, according to the RFC5545 standard. Then, if exceptions to the recurrence rule exist:
- Each exception must be a separate data item.
- The
RecurrenceId
property of each exception must be equal toId
value of the recurring appointment. - The
RecurrenceExceptions
property of the recurring appointment must contain theStart
values of all occurrences, which are exceptions to the recurrence rule. The correct values are the original startDateTime
values of the occurrences, which would apply if there were no exceptions.
Example
Bind Scheduler to recurring appointments and recurrence exceptions
<TelerikScheduler Data="@SchedulerData"
@bind-Date="@SchedulerDate"
@bind-View="@SchedulerView"
AllowCreate="true"
AllowDelete="true"
AllowUpdate="true"
OnCreate="@OnSchedulerCreate"
OnDelete="@OnSchedulerDelete"
OnUpdate="@OnSchedulerUpdate"
Height="99vh">
<SchedulerViews>
<SchedulerDayView StartTime="@SchedulerViewStartTime"
EndTime="@SchedulerViewEndTime" />
<SchedulerWeekView StartTime="@SchedulerViewStartTime"
EndTime="@SchedulerViewEndTime" />
<SchedulerMonthView />
</SchedulerViews>
<SchedulerSettings>
<SchedulerPopupEditSettings MaxHeight="99vh" />
</SchedulerSettings>
</TelerikScheduler>
@code {
private List<Appointment> SchedulerData { get; set; } = new();
private DateTime SchedulerDate { get; set; }
private SchedulerView SchedulerView { get; set; } = SchedulerView.Week;
private DateTime SchedulerViewStartTime { get; set; } = DateTime.Today.AddHours(10);
private DateTime SchedulerViewEndTime { get; set; } = DateTime.Today.AddHours(19);
private void OnSchedulerCreate(SchedulerCreateEventArgs args)
{
Appointment item = (Appointment)args.Item;
SchedulerData.Add(item);
}
private void OnSchedulerDelete(SchedulerDeleteEventArgs args)
{
Appointment item = (Appointment)args.Item;
SchedulerData.Remove(item);
}
private void OnSchedulerUpdate(SchedulerUpdateEventArgs args)
{
Appointment item = (Appointment)args.Item;
int originalItemIndex = SchedulerData.FindIndex(a => a.Id == item.Id);
if (originalItemIndex >= 0)
{
SchedulerData[originalItemIndex] = item;
}
}
protected override void OnInitialized()
{
SchedulerDate = GetNextMonthStart();
DateTime mondayMidnight = GetStartDateTime();
SchedulerData.Add(new Appointment
{
Title = "Weekly team meeting",
Start = mondayMidnight.AddHours(10).AddMinutes(30),
End = mondayMidnight.AddHours(11).AddMinutes(30),
RecurrenceRule = "FREQ=WEEKLY;BYDAY=MO"
});
SchedulerData.Add(new Appointment
{
Title = "Workout at the gym",
Start = mondayMidnight.AddHours(17),
End = mondayMidnight.AddHours(18),
RecurrenceRule = "FREQ=WEEKLY;BYDAY=MO,WE,FR"
});
SchedulerData.Add(new Appointment
{
Title = "Quaterly meeting with manager",
Start = mondayMidnight.AddDays(3).AddHours(14).AddMinutes(30),
End = mondayMidnight.AddDays(3).AddHours(15).AddMinutes(30),
RecurrenceRule = "FREQ=MONTHLY;INTERVAL=3;COUNT=36;BYDAY=TH;BYSETPOS=1"
});
SchedulerData.Add(new Appointment
{
Title = "Pay monthly bills",
Start = new DateTime(mondayMidnight.Year, mondayMidnight.Month, 1),
End = new DateTime(mondayMidnight.Year, mondayMidnight.Month, 1),
IsAllDay = true,
RecurrenceRule = "FREQ=MONTHLY"
});
// Create a base recurring appointment.
// Exceptions are defined below.
Appointment dailyLunch = new Appointment
{
Title = "Daily lunch",
Start = mondayMidnight.AddHours(12),
End = mondayMidnight.AddHours(13),
RecurrenceRule = "FREQ=DAILY"
};
SchedulerData.Add(dailyLunch);
// Create exceptions to the base appointment.
int daysSinceMonday = SchedulerDate.DayOfWeek - DayOfWeek.Monday;
DateTime lastMonday = DateTime.SpecifyKind(SchedulerDate.AddDays(-daysSinceMonday), DateTimeKind.Unspecified);
Appointment lateLunchException = new Appointment
{
Title = "Late lunch",
Start = lastMonday.AddHours(13),
End = lastMonday.AddHours(14),
RecurrenceId = dailyLunch.Id
};
SchedulerData.Add(lateLunchException);
Appointment earlyLunchException = new Appointment
{
Title = "Early lunch",
Start = lastMonday.AddDays(3).AddHours(11),
End = lastMonday.AddDays(3).AddHours(12),
RecurrenceId = dailyLunch.Id
};
SchedulerData.Add(earlyLunchException);
// Relate the exceptions to the base appointment.
DateTime lateLunchOriginalStart = DateTime.SpecifyKind(lastMonday.AddHours(12), DateTimeKind.Unspecified);
DateTime earlyLunchOriginalStart = DateTime.SpecifyKind(lastMonday.AddDays(3).AddHours(12), DateTimeKind.Unspecified);
dailyLunch.RecurrenceExceptions = new List<DateTime>()
{
lateLunchOriginalStart,
earlyLunchOriginalStart
};
}
private DateTime GetNextMonthStart()
{
DateTime today = DateTime.Today;
return new DateTime(today.Year, today.Month, 1).AddMonths(1);
}
private DateTime GetStartDateTime()
{
DateTime firstDayOfLastYear = new DateTime(DateTime.Today.Year, 1, 1).AddYears(-1);
return firstDayOfLastYear.AddDays(1 - (int)firstDayOfLastYear.DayOfWeek);
}
public class Appointment
{
public Guid Id { get; set; }
public string Title { get; set; } = string.Empty;
public string Description { 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<DateTime>? RecurrenceExceptions { get; set; }
public object? RecurrenceId { get; set; }
public Appointment()
{
var rand = new Random();
Id = Guid.NewGuid();
}
}
}
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.
The Telerik Blazor recurrence editor components include:
Component Name | Renders As | Description |
---|---|---|
RecurrenceFrequencyEditor | Button Group | Defines whether the appointment repeats daily, weekly, monthly, yearly, or never. |
RecurrenceIntervalEditor | Numeric TextBox | Defines whether the appointment repeats in each period (for example, every day), or skips periods (for example, once in three days). |
RecurrenceEditor | Button Group or Radio Group |
|
RecurrenceEndEditor | Radio Group, Numeric TextBox, Date Picker | Defines if the appointment repeats indefinitely, a number of times, or until a specific date. |
Parameters
All recurrence editor components expose:
- A
Rule
parameter of typeTelerik.Recurrence.RecurrenceRule
that supports two-way binding. - A
RuleChanged
event that receives aRecurrenceRule
argument. - A
Class
parameter for styling customizations.
In addition:
- The
RecurrenceIntervalEditor
supports anId
parameter of typestring
. Use it to set a customid
attribute to the Numeric TextBox and the samefor
attribute to the associated Repeat every label. - The
RecurrenceEndEditor
supports anEnd
parameter of typeDateTime
. Use it to set a default value for the End On Date Picker when there is noUNTIL
setting in the recurrence rule string.
Recurrence Rule Type Conversion
Use the following methods to convert from RFC5545 strings to RecurrenceRule
objects and vice-versa:
- The static method
RecurrenceRule.Parse()
to convert from RFC5545string
toRecurrenceRule
. - The instance method
RecurrenceRule.ToString()
to convert fromRecurrenceRule
to a RFC5545string
.
Converting between different recurrence rule formats
// RFC5545 string
string recurrenceString = "FREQ=WEEKLY;BYDAY=MO,WE,FR";
// Convert to RecurrenceRule
RecurrenceRule recurrenceRule = RecurrenceRule.Parse(recurrenceString);
// Make some changes...
// Convert to RFC5545 string
string newRecurrenceString = recurrenceRule.ToString();
Telerik Form Integration
There are two recommended ways to use the Telerik recurrence editors in a Telerik Form:
- Place each recurrence editor in a separate Form item
Template
. This is the simpler option to set up. - Place all recurrence editors in a
<FormItemsTemplate>
. This is a more verbose approach, which provides better control over the Form's HTML rendering, layout and styling.
The following examples can serve as a reference for creating custom Telerik Scheduler edit forms with recurrence editing. Using a markup structure that differs from the ones below may produce unexpected layouts.
Using Telerik recurrence editors in separate Form item templates
@using Telerik.Recurrence
<TelerikForm Model="@RecurringAppointment"
OnUpdate="@OnFormUpdate">
<FormItems>
<FormItem Field="@nameof(Appointment.Title)" />
<FormItem Field="@nameof(Appointment.Start)" />
<FormItem Field="@nameof(Appointment.End)" />
<FormItem Field="@nameof(Appointment.RecurrenceRule)"
LabelText="Recurrence Rule"
Enabled="false" />
<FormItem>
<Template>
<TelerikRecurrenceFrequencyEditor Rule="@Rule"
RuleChanged="@OnRuleChanged" />
</Template>
</FormItem>
<FormItem>
<Template>
@if (Rule != null)
{
<TelerikRecurrenceIntervalEditor Rule="@Rule" />
}
</Template>
</FormItem>
<FormItem>
<Template>
<TelerikRecurrenceEditor Rule="@Rule"
RuleChanged="@OnRuleChanged" />
</Template>
</FormItem>
<FormItem>
<Template>
@if (Rule != null)
{
<TelerikRecurrenceEndEditor Rule="@Rule"
EndDate="@RecurrenceEndDefaultDate" />
}
</Template>
</FormItem>
</FormItems>
</TelerikForm>
@code {
private Appointment? RecurringAppointment { get; set; }
private RecurrenceRule? Rule { get; set; }
private DateTime RecurrenceEndDefaultDate =>
new DateTime(Math.Max(RecurringAppointment?.End.Ticks ?? default, DateTime.Now.Ticks));
private void OnFormUpdate()
{
// Only necessary to refresh the UI until all Rule parameters gain two-way binding.
RecurringAppointment!.RecurrenceRule = Rule?.ToString() ?? string.Empty;
}
private void OnRuleChanged(RecurrenceRule newRule)
{
Rule = newRule;
RecurringAppointment!.RecurrenceRule = Rule?.ToString() ?? string.Empty;
}
protected override void OnInitialized()
{
DateTime nextMonday = DateTime.Today.AddDays(8 - (int)DateTime.Today.DayOfWeek);
RecurringAppointment = new Appointment
{
Title = "Workout at the gym",
Start = nextMonday.AddHours(17),
End = nextMonday.AddHours(18),
RecurrenceRule = "FREQ=WEEKLY;BYDAY=MO,WE,FR"
};
Rule = RecurrenceRule.Parse(RecurringAppointment.RecurrenceRule);
}
public class Appointment
{
public Guid Id { get; set; }
public string Title { get; set; } = string.Empty;
public string Description { 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<DateTime>? RecurrenceExceptions { get; set; }
public Guid? RecurrenceId { get; set; }
public Appointment()
{
var rand = new Random();
Id = Guid.NewGuid();
}
}
}
To add the recurrence editors to a FormItemsTemplate
, follow the same approach as in the example above, but add the following <FormItemsTemplate>
tag as a child of <TelerikForm>
.
Using Telerik recurrence editors in a FormItemsTemplate
<FormItemsTemplate Context="formContext">
@{
var formItems = formContext.Items.Cast<IFormItem>().ToList();
}
<TelerikFormItemRenderer Item="@( formItems.First(x => x.Field == nameof(Appointment.Title)) )" />
<TelerikFormItemRenderer Item="@( formItems.First(x => x.Field == nameof(Appointment.Start)) )" />
<TelerikFormItemRenderer Item="@( formItems.First(x => x.Field == nameof(Appointment.End)) )" />
<TelerikFormItemRenderer Item="@( formItems.First(x => x.Field == nameof(Appointment.RecurrenceRule)) )" />
<div class="k-form-field">
<TelerikRecurrenceFrequencyEditor Rule="@Rule"
RuleChanged="@OnRuleChanged" />
</div>
@if (Rule != null)
{
<div class="k-form-field">
<TelerikRecurrenceIntervalEditor Rule="@Rule" />
</div>
}
<div class="k-form-field">
<TelerikRecurrenceEditor Rule="@Rule"
RuleChanged="@OnRuleChanged" />
</div>
@if (Rule != null)
{
<div class="k-form-field">
<TelerikRecurrenceEndEditor Rule="@Rule"
EndDate="@RecurrenceEndDefaultDate" />
</div>
}
</FormItemsTemplate>