Calendar Tutorial: Recurring Events (ASP.NET, C#, VB.NET)

October 1, 2010 [last update October 15, 2012]

calendar-recurrence.png

Download

Requirements

  • Visual Studio 2008
  • .NET Framework 3.5
  • Microsoft SQL Server Express

Related

To see the implementation of recurring events in a standalone ASP.NET application, see also:

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

recurrence-edit-rule.png

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

recurrence-edit-series.png

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

DayPilot for JavaScript, ASP.NET WebForms, ASP.NET MVC, Java