License
Licensed for testing and evaluation purposes. You can use the source code of the tutorial if you are a licensed user of DayPilot Pro for ASP.NET WebForms.
Requirements
Visual Studio 2019
.NET Framework 3.5+
Microsoft SQL Server 2014+
Related
To see the implementation of recurring events in a standalone ASP.NET application, see also:
Shift Scheduling Tutorial (ASP.NET, SQL Server, C#, VB.NET)
Database
There are two options for storing the recurrence information:
Use the built-in support (DataRecurrenceField)
All recurrence-related information will be stored in a special varchar field.
The field is specified using DataRecurrenceField property.
The individual occurrences will be extracted automatically by DayPilot.
Use custom encoding (BeforeEventRecurrence)
You can use custom database fields, e.g. one field (boolean) for every day of week.
You have to handle BeforeEventRecurrence event, read the data from the special fields and create the rule manually.
Create the rule using RecurrenceRule class and assign it to e.Rule.
This tutorial explains how to work with the built-in support (DataRecurrenceField).
Visual Elements
Set the URLs of images that indicate a recurring event:
RecurrentEventImage="~/Media/recur10x9.png"
RecurrentEventExceptionImage="~/Media/recurex10x9.png"
Defining the rule
The rule can be created using RecurrenceRule class.
Every RecurrenceRule has three components:
time of day
repeating rule
time range
Example 1:
RecurrenceRule rule = RecurrenceRule.FromDateTime(DateTime.Now).Daily().Times(5);
Components of this rule:
FromDateTime(DateTime.Now) - each occurrence will start at the current time
Daily() - it will be repeated every day
Times(5) - there will be five occurrences
Example 2:
RecurrenceRule rule = RecurrenceRule.FromDateTime(DateTime.Now).Weekly().Until(DateTime.Today.AddMonths(1));
Components of this rule:
FromDateTime(DateTime.Now) - each occurrence will start at the current time
Weekly() - it will be repeated every week on the day specified using FromDateTime()
Until(DateTime.Today.AddMonths(1)) - it will not be repeated after one month from today
RecurrenceRule Modifiers
Initializers
static NoRepeat
static FromDateTime()
static FromCron()
Repeating
Daily()
Daily(int every)
Weekly()
Weekly(DayOfWeek[] days)
Weekly(DayOfWeek[] days, int every)
Monthly()
Monthly(int[] days)
Monthly(int[] days, int every)
Monthly(int[] days, int[] months)
Annually()
Range
Times(int times)
Until(DateTime until)
Storing the rule in the database
The RecurrenceRule has special methods for serializing and deserializing the rule to and from a string:
Encode()
static Decode(string encoded)
These methods should be used when working with the recurrence string stored in the database field defined using DataRecurrenceField property.
Exceptions from the rule
It is also possible to store exceptions from the rule. For example, an occurrence that should happen next Tuesday at 4 pm (by the rule) will be moved to 3 pm.
There are two kinds of exception:
Deleted (the occurrence will not happen)
Modified (the occurrence will happen, but it can have a different start, duration, text or other properties)
For every exception, you have to create a new record in the database.
the exception type will be stored using a special string in the DataRecurrenceField
if it is a "Modified" exception, the record filed values will be used as actual values (e.g. DataStartField, DataEndField, DataTextField)
Creating a "Deleted" exception
Create a new record in the table with events and set the DataRecurrenceField to a string returned by this call:
string recur = RecurrenceRule.EncodeExceptionDeleted(MasterId, OldStart);
Parameters:
MasterId holds the ID of the record that specifies the rule.
OldStart holds the DateTime of the planned occurrence.
Creating a "Modified" exception
Create a new record in the table with events and set the DataRecurrenceField to a string returned by this call:
string recur = RecurrenceRule.EncodeExceptionModified(MasterId, OldStart);
Parameters:
MasterId holds the ID of the record that specifies the rule.
OldStart holds the DateTime of the planned occurrence.
Recurrence-related records in the database
There will be several records related to each series:
Master record (1)
DataValueField: id, it will be available as RecurrenceMasterId in the generated occurrences
DataStartField: first occurrence, doesn't have to meet the rule
DataEndField: end of the first occurrence, it will be used to determine the duration of all events in the series
DataTextField: event text, it will be used for all events in the series
DateRecurrenceField: string encoded using RecurrenceRule.Encode(); contains the rule and range
Exception records (0 or more)
DataValueField: id of the exception
DataStartField: for "Modified" exception, it hold the new start
DataEndField: for "Modified" exception, it holds the new end
DataTextField: for "Modified" exception, it holds the new text
DataRecurrenceField: string encoded using RecurrenceRule.EncodeExceptionModified() or RecurrenceRule.EncodeExceptionDeleted(); contains the id of the series (MasterId) and the original start DateTime
Generated occurrences
During loading phase, DayPilot will transform the master record and exceptions into a set of events that will be displayed. The generated events will be passed to BeforeEventRender event and displayed. The users can interact with them and fire user actions (e.g. EventClick, EventMove).
There are three recurrence-related properties available in the server-side and client-side event-related event handlers:
Value/value() - event id
Recurrent/recurrent() - true for occurrences expanded from a rule
RecurrentMasterId/recurrentMasterId() - id of the series
You will work with three kinds of events.
1. Regular events (non-recurring)
Value: non-null value (event id)
Recurrent: false
RecurrentMasterId: null
2. Regular occurrence
Value: null value
Recurrent: true
RecurrentMasterId: non-null value (id of the series)
3. Exception
Value: non-null value
Recurrent: true
RecurrentMasterId: non-null value (id of the series)
Selecting the recurrent events from the database
Be careful:
You have to select all master records with start before the last visible time point (StartDate + Days). This includes dates before StartDate.
You have to select all exceptions between StartDate and StartDate + Days.
Example:
private IQueryable<Event> LoadEventsFromRange(DateTime start, DateTime end)
{
return from ev in db.Events where !(ev.eventend <= start || ev.eventstart >= end) || (ev.recurrence != null && ev.eventstart < end) select ev;
}
UI Helpers
There is a helper dialog available that reads the serialized rule into the form and returns a serialized selection.
When editing an event, the user has to select whether she wants to edit the occurrence or the series.
It is not able to show all possible recurrence rules. It only works with a predefined set of common rules.
UI: Choose whether to edit the series
This function will be called from EventClickJavaScript:
function ask(e) {
// it's a normal event
if (!e.recurrent()) {
edit(e);
return;
}
// it's a recurrent event but it's an exception from the series
if (e.value() !== null) {
edit(e);
return;
}
var modal = new DayPilot.Modal();
modal.top = 150;
modal.width = 300;
modal.height = 150;
modal.opacity = 0;
modal.border = "10px solid #d0d0d0";
modal.closed = function() {
if(this.result != "cancel") {
edit(e, this.result);
}
};
modal.showUrl("RecurrentEditMode.html");
}
It will show RecurrentEditMode.html page in a modal dialog.
UI: Editing the series
The following function will open the edit modal dialog (RecurrentEventEdit.aspx) depending on the editing mode selected by the user (mode parameter).
function edit(e, mode) {
var modal = new DayPilot.Modal();
modal.top = 60;
modal.width = 600;
modal.height = 330;
modal.opacity = 0;
modal.border = "10px solid #d0d0d0";
modal.closed = function() {
if (this.result === "OK") {
dpc.commandCallBack('refresh');
}
};
var url = "RecurrentEventEdit.aspx?q=1"
if (e.recurrentMasterId() !== null) {
url += "&master=" + e.recurrentMasterId();
}
if (e.value() !== null) {
url += "&id=" + e.value();
}
if (mode == "this") {
url += "&start=" + e.start().toStringSortable();
}
modal.showUrl(url);
}
UI: Loading the rule in the dialog
The recurrence section of the event edit dialog is predefined.
Copy the HTML code between <!-- Recurrence section --> and <!-- Recurrence section ends here -->
Include a reference to recurrence.js.
You can load the recurrence rule using the following
<script type="text/javascript">
var r = new DayPilot.Recurrence();
r.saveButtonId = "ButtonSave";
r.jsonHiddenId = "Recurrence";
r.config = <%= RecurrenceJson %>;
r.Init();
</script>
RecurrenceJson is defined in the code behind:
RecurrenceRule _rule = RecurrenceRule.Decode(master.recurrence);
return _rule.ToJson();
It is the DataRecurrenceField loaded using RecurrenceRule.Decode() and serialized to JSON using ToJson().
UI: Saving the rule edited in the dialog
The rule defined by the user in the form is automatically serialized by the recurrence.js script to a hidden field with id="Recurrence".
We need to read this field, parse the Json string using FromJson(), and saved to the DataRecurrenceField using Encode() method:
RecurrenceRule rule = RecurrenceRule.FromJson(masterId.ToString(), master.eventstart, Recurrence.Value);
master.recurrence = rule.Encode();