This tutorial shows how to create an ASP.NET timetable application that supports recurring events.

This is an advanced version of the basic ASP.NET timetable tutorial.

Sample Project

The sample project includes:

Features

Basic timetable features:

  • Based on the DayPilot event calendar ASP.NET control
  • Overview of events scheduled for the selected week
  • Changing the selected week using a date navigator
  • Time blocks with customizable start and end time (inline editing using header active areas)
  • Drag and drop event creating
  • Drag and drop event moving
  • Drag and drop event resizing
  • Custom event background color
  • Editable event description
  • Full calendar CSS support
  • Custom CSS theme
  • Storing data in an SQL Server database (sample database included)
  • C# source code of the sample project
  • VB source code of the sample project

Advanced timetable features (recurrence):

  • Support for recurring events
  • Recurring events are marked with a special icon
  • Customizable recurrence rules (daily, weekly, monthly, annually)
  • Exceptions from the rules (different time, description, color for a specified occurrence)

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. Buy a license.

Requirements

Visual Studio 2010 Solution

  • .NET Framework 4.0 or higher
  • Visual Studio 2010 or higher (optional)
  • Microsoft SQL Server 2008+ (Express) 

Visual Studio 2012 Solution

  • .NET Framework 4.0 or higher
  • Visual Studio 2012 or higher (optional)
  • Microsoft SQL Server 2012 (Express) 

Timetable Database Schema

asp.net-timetable-sql.png

We will add a special [AssignmentRecurrence] varchar field to the [Assignment] table. It will store the recurrence information (for master events and exceptions).

CREATE TABLE [dbo].[Assignment](
  [AssignmentId] [bigint] IDENTITY(1,1) NOT NULL,
  [AssignmentNote] [varchar](2000),
  [AssignmentStart] [datetime] NOT NULL,
  [AssignmentEnd] [datetime] NOT NULL,
  [AssignmentColor] [varchar](50),
  [AssignmentRecurrence] [varchar](250),
  CONSTRAINT [PK_Assignment] PRIMARY KEY CLUSTERED 
  (
    [AssignmentId] ASC
  )
)

The [Block] table:

CREATE TABLE [dbo].[Block](
  [BlockId] [int] NOT NULL,
  [BlockStart] [datetime] NOT NULL,
  [BlockEnd] [datetime] NOT NULL,
  CONSTRAINT [PK_Block] PRIMARY KEY CLUSTERED 
  (
    [BlockId] ASC
  )
)

Displaying Recurring Events in the Timetable

asp.net-timetable-recurrence-icons.png

DayPilot Calendar has built-in support for recurring events. It will automatically expand the individual occurrences from the rule and apply all exceptions (deleted or modified occurrences).

We will specify the database field that stores recurrence information using DataRecurrenceField property:

<DayPilot:DayPilotCalendar
  ID="DayPilotCalendar1" 
  runat="server" 
  DataEndField="AssignmentEnd"
  DataStartField="AssignmentStart" 
  DataTextField="AssignmentNote" 
  DataValueField="AssignmentId" 
  DataRecurrenceField="AssignmentRecurrence"
/>

In order to mark the recurring events in the timetable we will use BeforeEventRender event and add an icon to the upper-right corner of the recurring events:

C#

protected void DayPilotCalendar1_BeforeEventRender(object sender, DayPilot.Web.Ui.Events.Calendar.BeforeEventRenderEventArgs e)
{
  if (e.Recurrent)
  {
    if (e.RecurrentException)
    {
      e.Areas.Add(
        new Area()
          .Right(5)
          .Top(5)
          .Width(14)
          .Height(14)
          .Visibility(AreaVisibility.Visible)
          .CssClass("area_recurring_ex")
      );
    }
    else
    {
      e.Areas.Add(
        new Area()
          .Right(5)
          .Top(5)
          .Width(14)
          .Height(14)
          .Visibility(AreaVisibility.Visible)
          .CssClass("area_recurring")
      );
    }
  }
}

VB

Protected Sub DayPilotCalendar1_BeforeEventRender(ByVal sender As Object, ByVal e As DayPilot.Web.Ui.Events.Calendar.BeforeEventRenderEventArgs)
  Dim color As String = CStr(e.DataItem("AssignmentColor"))
  If Not String.IsNullOrEmpty(color) Then
    e.BackgroundColor = color
    e.BorderColor = color
    e.FontColor = "#ffffff"
  End If

  If e.Recurrent Then
    If e.RecurrentException Then
      e.Areas.Add((New Area()).Right(5).Top(5).Width(14).Height(14).Visibility(AreaVisibility.Visible).CssClass("area_recurring_ex"))
    Else
      e.Areas.Add((New Area()).Right(5).Top(5).Width(14).Height(14).Visibility(AreaVisibility.Visible).CssClass("area_recurring"))
    End If
  End If
End Sub

Timetable CSS Theme

The timetable_simple CSS theme was created using the online CSS theme designer. It uses the default colors and styles similar to the built-in CSS theme (calendar_default).

asp.net-timetable-css-theme.png

You can edit and download the CSS theme here:

It is manually customized to display smaller font in the time header (used for the block names):

.timetable_simple_rowheader_inner
{
  font-size: 12pt;

  text-align: right; 
  position: absolute;
  top: 0px;
  left: 0px;
  bottom: 0px;
  right: 0px;
  border-right: 1px solid #aaaaaa;
  border-bottom: 1px solid  #aaaaaa;

  color: #666666;
  background: #eeeeee;

  background: -webkit-gradient(linear, left top, right top, from(#f3f3f3), to(#e9e9e9));
  background: -webkit-linear-gradient(left, #f3f3f3 0%, #e9e9e9);
  background: -moz-linear-gradient(left, #f3f3f3 0%, #e9e9e9);
  background: -ms-linear-gradient(left, #f3f3f3 0%, #e9e9e9);
  background: -o-linear-gradient(left, #f3f3f3 0%, #e9e9e9);
  background: linear-gradient(left, #f3f3f3 0%, #e9e9e9);
  filter: progid:DXImageTransform.Microsoft.Gradient(startColorStr="#f3f3f3", endColorStr="#e9e9e9", GradientType=1);

}

Special CSS classes for active areas indicating event recurrence were added at the end of timetable_simple.css:

.area_recurring 
{
  background: no-repeat url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAIAAACQKrqGAAAAKnRFWHRDcmVhdGlvbiBUaW1lAHN0IDE5IElJIDIwMTQgMDk6NTU6MTAgKzAxMDCM4OYVAAAAB3RJTUUH3gITCDcVL0VpiQAAAAlwSFlzAAAOwwAADsMBx2+oZAAAAARnQU1BAACxjwv8YQUAAAE4SURBVHjahVI/64JAGPZPi1QOErhEs4tfoy38IEE4uAji4OLnCAJx0UC/QCAu5pSWq+FWCtqYYW/Y7zhp+D3D3fO+73N37z13ZNd1BIb7/V6WJUmSs9mM4zi8RHR/yLJMlmW8pGna9XqFkmVZ5/P5K/V9Hyk2m816vUahYRgwHg6Hj/R4PPbZ3W73eDz6xU3TbLdbtCAMQ+L5fC6XSwhc1+2GcBwHSYMgINI0BSZJ0uv1wnVJkkB+sVjM53MgcPIoz3Ngq9WKoij8ToIgQDMoHI/Ho7ZtgTEMQwxB0/RkMsEzFM/zMMVxTPyLuq57cjqduh/cbjfEP2bZtg3S6XR6uVxwXRRFuDPfJ1BVtd9b13XP8/b7vaIofcY0zYEUAP7/toebTeLfpaoq6LgoCjAOHBVFkWVZVH0Dxfr9q0syMB0AAAAASUVORK5CYII=);
}

.area_recurring_ex 
{
  background: no-repeat url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAIAAACQKrqGAAAAKnRFWHRDcmVhdGlvbiBUaW1lAHN0IDE5IElJIDIwMTQgMDk6NTk6NTkgKzAxMDCHDk2yAAAAB3RJTUUH3gITCQAI3YbPUwAAAAlwSFlzAAAOwwAADsMBx2+oZAAAAARnQU1BAACxjwv8YQUAAAGmSURBVHjaZVFNywFhFJ0ZryTzIpGU/AH8gfkBdpKyVEOWSn6AhSysbBRLZaFEYjsbCwuyYOUzO1I2aHxtGOY948mTeu9i5s6955459x5WVdXn86nT6Zh37Pf7w+HAsqzdbrfZbMx3XK/XUqk0HA6Xy2U6nf5uZTKZzWYDrnq9Pp/PNZpCoZDNZgOBAEGkUqlkMkkHcrkcnr1ej8UQXq1WC3+0Wq2JRILnefQul0un0xFFkQwMBgNOUZR8Pl+tVg0GgyzL/X6f9H7fQblfrxczm82QhUKh8/kM0VDSbDbRmE6nqHs8HrfbjWQ0Gv2s12tkwWAQHLFYDDhsAD3hcBgaKKvJZNIEIDMajeQ7Go2CCWgIhST+Ezgf53Q6ARqPx2Rar9dTdK1WezwelJg9nU4WiwXZZDLx+XykClPa7TbQLpcrHo9jHkXObDY3Gg1kgiDABQKFeZFIBLjdblcsFm+3m1bEyl6v936/d7vdcrmMM6OxWCwqlQrccjgcOPZqtQKGUT8BZcy/wHKSJMFOmKq5RRvH4xGKt9stx3HYzO/3Qx7q+A+O8wenyt+j7JPKzQAAAABJRU5ErkJggg==);
}

The inline data URL is used to store the image data (14x14 PNG):

.area_recurring

recurring14x14.png

.area_recurring_ex

recurring_ex14x14.png

Adding Events to the Timetable

asp.net-timetable-recurrence-create-event.png

The users can add new events to the timetable by selecting the blocks using a mouse. 

A new event dialog will open:

asp.net-timetable-recurrence-create-dialog.png

The dialog is extended with the "Repeat" section that allows to specify the recurrence rule:

<div class="label">Repeat:</div>
<!-- Recurrence section -->
<script type="text/javascript" src="Scripts/DayPilot/recurrence.js"></script>
        <select id="repeat">
            <option value="norepeat">Does not repeat</option>
            <option value="daily">Daily</option>
            <option value="weekly">Weekly</option>
            <option value="monthly">Monthly</option>
            <option value="annually">Annually</option>
        </select>

<div id="select_norepeat" style="display:none">
</div>

<div id="select_daily" style="display:none">
Repeat every <input id="daily_every" style="width: 20px;" value="1" /> day(s).
</div>

<div id="select_weekly" style="display:none">
Repeat every <input id="weekly_every" style="width: 20px;" value="1" /> week(s).
<div id="select_weekly_error" style="display:none; margin-top: 5px; padding: 2px; background-color: #FFF1A8;">Please select at least one day:</div>
<br />
<table>
<tr>
<td>On days: </td>
<td><label for="weekly_0"><input type="checkbox" id="weekly_0" />Sun</label></td>
<td><label for="weekly_1"><input type="checkbox" id="weekly_1" />Mon</label></td>
<td><label for="weekly_2"><input type="checkbox" id="weekly_2" />Tue</label></td>
<td><label for="weekly_3"><input type="checkbox" id="weekly_3" />Wed</label></td>
<td><label for="weekly_4"><input type="checkbox" id="weekly_4" />Thu</label></td>
<td><label for="weekly_5"><input type="checkbox" id="weekly_5" />Fri</label></td>
<td><label for="weekly_6"><input type="checkbox" id="weekly_6" />Sat</label></td>
</tr>
</table>
</div>

<div id="select_monthly" style="display:none">
Repeat every <input id="monthly_every" style="width: 20px;" value="1" /> month(s).
<div id="select_monthly_error" style="display:none; margin-top: 5px; padding: 2px; background-color: #FFF1A8;">Please select at least one day:</div>
<br />  
<label for="monthly_1"><input type="checkbox" id="monthly_1" />1</label>
<label for="monthly_2"><input type="checkbox" id="monthly_2" />2</label>
<label for="monthly_3"><input type="checkbox" id="monthly_3" />3</label>
<label for="monthly_4"><input type="checkbox" id="monthly_4" />4</label>
<label for="monthly_5"><input type="checkbox" id="monthly_5" />5</label>
<label for="monthly_6"><input type="checkbox" id="monthly_6" />6</label>
<label for="monthly_7"><input type="checkbox" id="monthly_7" />7</label>
<label for="monthly_8"><input type="checkbox" id="monthly_8" />8</label>
<label for="monthly_9"><input type="checkbox" id="monthly_9" />9</label>
<label for="monthly_10"><input type="checkbox" id="monthly_10" />10</label>
<label for="monthly_11"><input type="checkbox" id="monthly_11" />11</label>
<label for="monthly_12"><input type="checkbox" id="monthly_12" />12</label>
<label for="monthly_13"><input type="checkbox" id="monthly_13" />13</label>
<label for="monthly_14"><input type="checkbox" id="monthly_14" />14</label>
<label for="monthly_15"><input type="checkbox" id="monthly_15" />15</label>
<label for="monthly_16"><input type="checkbox" id="monthly_16" />16</label>
<label for="monthly_17"><input type="checkbox" id="monthly_17" />17</label>
<label for="monthly_18"><input type="checkbox" id="monthly_18" />18</label>
<label for="monthly_19"><input type="checkbox" id="monthly_19" />19</label>
<label for="monthly_20"><input type="checkbox" id="monthly_20" />20</label>
<label for="monthly_21"><input type="checkbox" id="monthly_21" />21</label>
<label for="monthly_22"><input type="checkbox" id="monthly_22" />22</label>
<label for="monthly_23"><input type="checkbox" id="monthly_23" />23</label>
<label for="monthly_24"><input type="checkbox" id="monthly_24" />24</label>
<label for="monthly_25"><input type="checkbox" id="monthly_25" />25</label>
<label for="monthly_26"><input type="checkbox" id="monthly_26" />26</label>
<label for="monthly_27"><input type="checkbox" id="monthly_27" />27</label>
<label for="monthly_28"><input type="checkbox" id="monthly_28" />28</label>
<label for="monthly_29"><input type="checkbox" id="monthly_29" />29</label>
<label for="monthly_30"><input type="checkbox" id="monthly_30" />30</label>
<label for="monthly_31"><input type="checkbox" id="monthly_31" />31</label>
</div>

<div id="select_annually" style="display:none">
</div>

<div id="range" style="display:none">
<div style="border-bottom: 2px solid #d0d0d0; margin-top:10px; margin-bottom: 10px;"></div>
<label for="repeat_indefinitely"><input type="radio" name="repeat_range" id="repeat_indefinitely" />Repeat indefinitely</label><br />
<label for="repeat_until"><input type="radio" name="repeat_range" id="repeat_until" />Repeat until: </label><input id="repeat_until_value" style="width: 150px;" value="12/31/2099" /><br />
<label for="repeat_times"><input type="radio" name="repeat_range" id="repeat_times" />Repeat </label><input id="repeat_times_value" style="width: 20px;" /> time(s).<br />
</div>

<script type="text/javascript">
var r = new DayPilot.Recurrence();
r.saveButtonId = "ButtonOK";
r.jsonHiddenId = "Recurrence";
r.config = <%= RecurrenceJson %>;
r.onSelect = function(args) {
    if (typeof modal !== 'undefined') {
        modal.stretch();
    }
};
r.Init();
</script>

<!-- Recurrence section ends here -->

It is a template that can be customized using your own styles and arrangement.

The recurrence.js helper (DayPilot.Recurrence class) saves the recurrence rule to a hidden field called "Recurrence" before submitting so it can be read in the ButtonOK.Click event handler.

C#

protected void ButtonOK_Click(object sender, EventArgs e)
{
  
  DateTime day = Convert.ToDateTime(TextBoxDay.Text);
  int start = Convert.ToInt32(DropDownListStart.SelectedValue);
  int duration = Convert.ToInt32(DropDownListDuration.SelectedValue);
  string note = TextBoxNote.Text;
  string color = DropDownListColor.SelectedValue;
  string recurrence = Recurrence.Value;

  int totalBlocks = TimetableManager.TotalBlocks;
  duration = start + duration > totalBlocks ? totalBlocks - start : duration; // make sure it's within the visible range

  DateTime startDate = day.Add(TimetableManager.MapBlock(start));
  DateTime endDate = startDate.Add(TimetableManager.MapBlock(duration));

  new DataManager().CreateAssignment(startDate, endDate, note, color, recurrence);

  // passed to the modal dialog close handler, see Scripts/DayPilot/event_handling.js
  Hashtable ht = new Hashtable();
  ht["refresh"] = "yes";
  ht["message"] = "Event created.";

  Modal.Close(this, ht);
}

VB

Protected Sub ButtonOK_Click(ByVal sender As Object, ByVal e As EventArgs)

  Dim day As Date = Convert.ToDateTime(TextBoxDay.Text)
  Dim start As Integer = Convert.ToInt32(DropDownListStart.SelectedValue)
  Dim duration As Integer = Convert.ToInt32(DropDownListDuration.SelectedValue)
  Dim note As String = TextBoxNote.Text
  Dim color As String = DropDownListColor.SelectedValue
  Dim recurrence_Renamed As String = Recurrence.Value

  Dim totalBlocks As Integer = TimetableManager.TotalBlocks
  duration = If(start + duration > totalBlocks, totalBlocks - start, duration) ' make sure it's within the visible range

  Dim startDate As Date = day.Add(TimetableManager.MapBlock(start))
  Dim endDate As Date = startDate.Add(TimetableManager.MapBlock(duration))

  CType(New DataManager(), DataManager).CreateAssignment(startDate, endDate, note, color, recurrence_Renamed)

  ' passed to the modal dialog close handler, see Scripts/DayPilot/event_handling.js
  Dim ht As New Hashtable()
  ht("refresh") = "yes"
  ht("message") = "Event created."

  Modal.Close(Me, ht)
End Sub

The DataManger.CreateAssignment() method uses RecurrenceRule.FromJson() to parse the rule and RecurrenceRule.Encode() converts it to the string that will be stored in the database (AssignmentRecurrence field).

C#

public void CreateAssignment(DateTime start, DateTime end, string note, string color, string recurrenceJson)
{
  using (DbConnection con = CreateConnection())
  {
      con.Open();

      var cmd = CreateCommand("insert into [Assignment] ([AssignmentStart], [AssignmentEnd], [AssignmentNote], [AssignmentColor]) values (@start, @end, @note, @color)", con);
      AddParameterWithValue(cmd, "start", start);
      AddParameterWithValue(cmd, "end", end);
      AddParameterWithValue(cmd, "note", note);
      AddParameterWithValue(cmd, "color", color);
      cmd.ExecuteNonQuery();

      cmd = CreateCommand("select @@identity;", con);
      int id = Convert.ToInt32(cmd.ExecuteScalar());

      RecurrenceRule rule = RecurrenceRule.FromJson(id.ToString(), start, recurrenceJson);
      string recurrenceString = rule.Encode();
      if (!String.IsNullOrEmpty(recurrenceString))
      {
          cmd = CreateCommand("update [Assignment] set [AssignmentRecurrence] = @recurrence where [AssignmentId] = @id", con);
          AddParameterWithValue(cmd, "recurrence", rule.Encode());
          AddParameterWithValue(cmd, "id", id);
          cmd.ExecuteNonQuery();
      }

  }
}

VB

Public Sub CreateAssignment(ByVal start As Date, ByVal [end] As Date, ByVal note As String, ByVal color As String, ByVal recurrenceJson As String)
  Using con As DbConnection = CreateConnection()
    con.Open()

    Dim cmd = CreateCommand("insert into [Assignment] ([AssignmentStart], [AssignmentEnd], [AssignmentNote], [AssignmentColor]) values (@start, @end, @note, @color)", con)
    AddParameterWithValue(cmd, "start", start)
    AddParameterWithValue(cmd, "end", [end])
    AddParameterWithValue(cmd, "note", note)
    AddParameterWithValue(cmd, "color", color)
    cmd.ExecuteNonQuery()

    cmd = CreateCommand("select @@identity;", con)
    Dim id As Integer = Convert.ToInt32(cmd.ExecuteScalar())

    Dim rule As RecurrenceRule = RecurrenceRule.FromJson(id.ToString(), start, recurrenceJson)
    Dim recurrenceString As String = rule.Encode()
    If Not String.IsNullOrEmpty(recurrenceString) Then
      cmd = CreateCommand("update [Assignment] set [AssignmentRecurrence] = @recurrence where [AssignmentId] = @id", con)
      AddParameterWithValue(cmd, "recurrence", rule.Encode())
      AddParameterWithValue(cmd, "id", id)
      cmd.ExecuteNonQuery()
    End If

  End Using
End Sub

The RecurrenceRule requires the event id when creating the rule so we need to 

  1. save the event first
  2. read its database-generated ID using "select @@identity", and
  3. create the rule
  4. store the encoded rule in AssignmentRecurrence

Note that these three SQL commands should be executed as a single transaction (it is not implemented in this sample).

Timetable Event Recurrence Rules

Recurrence rules supported:

  • daily (every X days)
  • weekly (every X weeks, on specified weekdays)
  • monthly (every X months, on specified days of month)
  • annually (very X years)

Daily Rule

asp.net-timetable-recurrence-daily.png

Weekly Rule

asp.net-timetable-recurrence-weekly.png

Monthly Rule

asp.net-timetable-recurrence-monthly.png

Annually Rule

asp.net-timetable-recurrence-annually.png

Editing the Series of an Occurrence

asp.net-timetable-recurrence-edit-series.png

If an event is a part of a recurrence rule we need to ask for the editing mode when opening the event for editing:

<DayPilot:DayPilotCalendar
  ID="DayPilotCalendar1" 
  runat="server" 
  ...
  EventClickHandling="JavaScript"
  EventClickJavaScript="ask(e)"
  ...
/>

The ask() function checks the event type:

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.id() !== null) {
      edit(e);
      return;
  }

  var modal = new DayPilot.Modal();
  modal.closed = function () {
      if (this.result != "cancel") {
          edit(e, this.result);
      }
  };

  modal.showUrl("RecurrentEditMode.html");
}

RecurrentEditMode.html page displays the options and returns "this" or "series" to the ask() function:

Do you want to edit this occurrence or the series?

<div><label for="this"><input type="radio" name="result" value="this" id="this" checked="checked" /> This occurrence only</label></div>
<div><label for="series"><input type="radio" name="result" value="series" id="series" /> Whole series</label></div>

<div style="margin-top:10px">
<button onclick="javascript:clicked(selected());">OK</button>
<button onclick="javascript:clicked('cancel');">Cancel</button>
</div>

</div>
<script type="text/javascript">

function selected() {
    if (document.getElementById("this").checked) {
        return "this";
    }
    return "series";
}

function clicked(result) {
    if (parent && parent.DayPilot && parent.DayPilot.ModalStatic) {
        parent.DayPilot.ModalStatic.close(result);
    }
}

</script>

The edit() function passes details about the event (master id, occurrence start) to the Edit.aspx page:

function edit(e, mode) {
  var url = "Edit.aspx?q=1"
  if (e.recurrentMasterId()) {
      url += "&master=" + e.recurrentMasterId();
  }
  if (e.value() !== null) {
      url += "&id=" + e.id();
  }
  if (mode == "this") {
      url += "&start=" + e.start();
  }
  createModal().showUrl(url);
}

Editing Recurring Events

asp.net-timetable-recurrence-edit.png

The code for event editing is much more complex that usually because we need to handle four possible events types:

  • a master event (we are editing the rule)
  • an regular occurrence (we will create a new exception when saved)
  • an exceptional occurrence (this one is already stored as a special record in the database)
  • a regular event (no recurrence)

C#

protected void ButtonOK_Click(object sender, EventArgs e)
{
  switch (Mode)
  {
      case EventMode.Master:
          SaveMaster();
          break;
      case EventMode.NewException:
          SaveNewException();
          break;
      case EventMode.Exception:
          SaveException();
          break;
      case EventMode.Regular:
          SaveRegular();
          break;
      default:
          throw new ArgumentOutOfRangeException();
  }

  Hashtable ht = new Hashtable();
  ht["refresh"] = "yes";
  ht["message"] = "Event updated.";

  Modal.Close(this, ht);
}

VB

Protected Sub ButtonOK_Click(ByVal sender As Object, ByVal e As EventArgs)
  Select Case Mode
    Case EventMode.Master
      SaveMaster()
    Case EventMode.NewException
      SaveNewException()
    Case EventMode.Exception
      SaveException()
    Case EventMode.Regular
      SaveRegular()
    Case Else
      Throw New ArgumentOutOfRangeException()
  End Select

  Dim ht As New Hashtable()
  ht("refresh") = "yes"
  ht("message") = "Event updated."

  Modal.Close(Me, ht)
End Sub

Something similar applies to the "Delete" button:

C#

protected void ButtonDelete_Click(object sender, EventArgs e)
{
  switch (Mode)
  {
      case EventMode.Master:
          new DataManager().DeleteAssignment(MasterId);
          break;
      case EventMode.Exception:
          new DataManager().DeleteAssignment(Id);
          break;
      case EventMode.NewException:
          new DataManager().CreateAssignmentExceptionDeleted(MasterId, Start);
          break;
      case EventMode.Regular:
          new DataManager().DeleteAssignment(Id);
          break;
  }

  Hashtable ht = new Hashtable();
  ht["refresh"] = "yes";
  ht["message"] = "Event deleted.";
  Modal.Close(this, ht);
}

VB

Protected Sub ButtonDelete_Click(ByVal sender As Object, ByVal e As EventArgs)
  Select Case Mode
    Case EventMode.Master
      CType(New DataManager(), DataManager).DeleteAssignment(MasterId)
    Case EventMode.Exception
      CType(New DataManager(), DataManager).DeleteAssignment(Id)
    Case EventMode.NewException
      CType(New DataManager(), DataManager).CreateAssignmentExceptionDeleted(MasterId, Start)
    Case EventMode.Regular
      CType(New DataManager(), DataManager).DeleteAssignment(Id)
  End Select

  Dim ht As New Hashtable()
  ht("refresh") = "yes"
  ht("message") = "Event deleted."
  Modal.Close(Me, ht)
End Sub