Monthly Calendar and SQL Server Tutorial (C# and VB.NET)

monthly-calendar.png

Download

Prerequisites

Overview

This tutorial shows how to use the Month AJAX component from DayPilot Pro package in a simple ASP.NET MVC project.

SQL Server Database

This tutorial uses a simple SQL Server database, located in App_Data directory (daypilot.mdf).

The database contains a single table with the following structure:

CREATE TABLE [dbo].[event](
  [id] [int] IDENTITY(1,1) NOT NULL,
  [name] [varchar](50),
  [eventstart] [datetime] NOT NULL,
  [eventend] [datetime] NOT NULL,
  CONSTRAINT [PK_event] PRIMARY KEY
)

Required Scripts

It's necessary to include the following scripts in the web page (the scripts can be found in Scripts/DayPilot directory):

  • common.js
  • menu.js
  • month.js

All the scripts are included in the site template (Views/Shared/Site.master).

DayPilot MVC DLL

The DayPilot.Web.Mvc.dll must be placed in the Bin directory of the web applicaton.

View

The main view (Views/Home/Index.aspx) uses an HTML Helper to generate the client-side code for DayPilot Month:

C#

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>
<%@ Import Namespace="DayPilot.Web.Mvc.Enums" %>
<%@ Import Namespace="DayPilot.Web.Mvc.Events.Month" %>

<asp:Content ID="aboutTitle" ContentPlaceHolderID="TitleContent" runat="server">
    Month
</asp:Content>

<asp:Content ID="aboutContent" ContentPlaceHolderID="MainContent" runat="server">
    <p>

        <%= Html.DayPilotMonth("dpm", new DayPilotMonthConfig {
                BackendUrl = ResolveUrl("~/Month/Backend")
            })%>
    </p>
</asp:Content>

VB.NET

<%@ Page Language="VB" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>
<%@ Import Namespace="DayPilot.Web.Mvc.Enums" %>
<%@ Import Namespace="DayPilot.Web.Mvc.Events.Month" %>

<asp:Content ID="aboutTitle" ContentPlaceHolderID="TitleContent" runat="server">
    Month
</asp:Content>

<asp:Content ID="aboutContent" ContentPlaceHolderID="MainContent" runat="server">
    <p>
        <%= Html.DayPilotMonth("dpm", new DayPilotMonthConfig With { 
            .BackendUrl = ResolveUrl("~/Month/Backend")
           })%>
    </p>
</asp:Content>

The only required property is BackendUrl which points to a controller that will handle all AJAX requests. Here it points to /Month/Backend. We will create the controller in the next step

Backend Controller

The backend controller (MonthController) is located in App_Code/Controllers directory. It defines the Backend action:

C#

    [HandleError]
    public class MonthController : Controller
    {

        public ActionResult Backend()
        {
            return new Dpm().CallBack(this);
        }
    }

VB.NET

<HandleError()>
Public Class MonthController
Inherits Controller

Public Function Backend() As ActionResult
Return New Dpm().CallBack(Me)
End Function

End Class

This Backend action simply passes the control to a newly created class (Dpm) which inherits DayPilotMonth class from DayPilot.Web.Mvc namespace.

The first thing we want to do is to load the events from our database. We will handle an Init event (which is fired after the client-side initialization) by overriding OnInit() method:

C#

        public class Dpm : DayPilotMonth
{
protected override void OnInit(InitArgs initArgs)
{
// this select is a really bad example, no where clause
Events = new EventManager(Controller).FilteredData(VisibleStart, VisibleEnd).AsEnumerable();

DataStartField = "eventstart";
DataEndField = "eventend";
DataTextField = "name";
DataIdField = "id";
Update();
}
}

VB.NET

Public Class Dpm
  Inherits DayPilotMonth

  Protected Overrides Sub OnInit(ByVal e As DayPilot.Web.Mvc.Events.Month.InitArgs)
    Dim em = New EventManager(Controller)
    Events = em.FilteredData(VisibleStart, VisibleEnd).AsEnumerable

    DataStartField = "eventstart"
    DataEndField = "eventend"
    DataTextField = "name"
    DataIdField = "id"

    Update()
  End Sub
End Class

Our OnInit method does the following:

1. It loads our data to Events property.

EventManager is a simple class that handles data access (it can be found in App_Code/Data directory).

C#

new EventManager(Controller).FilteredData(VisibleStart, VisibleEnd)

VB.NET

Dim em = New EventManager(Controller)
em.FilteredData(VisibleStart, VisibleEnd)

FilteredData() method returns a DataTable. We convert it to Enumerable using AsEnumerable() method and assign the result to Events property (which accepts classes that implement IEnumerable interface):

C#

Events = new EventManager(Controller).FilteredData(VisibleStart, VisibleEnd).AsEnumerable();

VB.NET

Dim em = New EventManager(Controller)
Events = em.FilteredData(VisibleStart, VisibleEnd).AsEnumerable

2. It maps the data source columns. DayPilotMonth will use this mapping to load the key event properties from the data source.

DataStartField = "eventstart";
DataEndField = "eventend";
DataTextField = "name";
DataIdField = "id";

3. And finally, it requests an update of the event set on the client side:

Update();

Event Moving

monthly-calendar-move.png

In order to enable event moving using drag & drop AJAX operation, we need to do the following:

1. Enable event moving on the client side by setting EventMoveHandling = EvenMoveHandlingType.CallBack:

C#

        <%= Html.DayPilotMonth("dpm", new DayPilotMonthConfig {
                BackendUrl = ResolveUrl("~/Month/Backend"),
                EventMoveHandling = EventMoveHandlingType.CallBack,
            })%>

VB.NET

<%= Html.DayPilotMonth("dpm", new DayPilotMonthConfig With { 
            .BackendUrl = ResolveUrl("~/Month/Backend"),
            .EventMoveHandling = EventMoveHandlingType.CallBack
           })%>

2. Handle the EventMove request in the Dpm class:

C#

protected override void OnEventMove(EventMoveArgs e)
{
new EventManager(Controller).EventMove(e.Id, e.NewStart, e.NewEnd);

Events = new EventManager(Controller).FilteredData(VisibleStart, VisibleEnd).AsEnumerable();

DataStartField = "eventstart";
DataEndField = "eventend";
DataTextField = "name";
DataIdField = "id";
  Update();
}

VB.NET

Protected Overrides Sub OnEventMove(ByVal e As DayPilot.Web.Mvc.Events.Month.EventMoveArgs)
  Dim em = New EventManager(Controller)
  em.EventMove(e.Id, e.NewStart, e.NewEnd)

  Events = em.FilteredData(VisibleStart, VisibleEnd).AsEnumerable

  DataStartField = "eventstart"
  DataEndField = "eventend"
  DataTextField = "name"
  DataIdField = "id"

  Update()
End Sub

3. For convenience, we move the Events update to a OnFinish() method, which is called at the end of AJAX request.

Our new OnEventMove method will be very short:

C#

protected override void OnEventMove(EventMoveArgs e)
{
  new EventManager(Controller).EventMove(e.Id, e.NewStart, e.NewEnd);
  Update();  // this sets CallBackUpdate = CallBackUpdateType.EventsOnly
}

VB.NET

Protected Overrides Sub OnEventMove(ByVal e As DayPilot.Web.Mvc.Events.Month.EventMoveArgs)
  Dim em = New EventManager(Controller)
  em.EventMove(e.Id, e.NewStart, e.NewEnd)
  Update()
End Sub

OnInit() will be even shorter because we don't do any updates, just send a fresh data set:

C#

protected override void OnInit(InitArgs initArgs)
{
  Update();
}

VB.NET

Protected Overrides Sub OnInit(ByVal e As DayPilot.Web.Mvc.Events.Month.InitArgs)
  Update()
End Sub

In OnFinish(), we check if the update was requested by the event handler and load the events:

C#

protected override void OnFinish()
{
  // only load the data if an update was requested by an Update() call
  if (UpdateType == CallBackUpdateType.None)
  {
    return;
  }

  Events = new EventManager(Controller).FilteredData(VisibleStart, VisibleEnd).AsEnumerable();

  DataStartField = "eventstart";
  DataEndField = "eventend";
  DataTextField = "name";
  DataIdField = "id";

}

VB.NET

Protected Overrides Sub OnFinish()
  If UpdateType = CallBackUpdateType.None Then
    Return
  End If

  Dim em = New EventManager(Controller)
  Events = em.FilteredData(VisibleStart, VisibleEnd).AsEnumerable
  DataStartField = "eventstart"
  DataEndField = "eventend"
  DataTextField = "name"
  DataIdField = "id"
End Sub

Event Resizing

monthly-calendar-resize.png

Adding AJAX event resizing is easy now:

1. Enable event resizing on the client side by setting EventResizeHandling = EvenResizeHandlingType.CallBack:

C#

        <%= Html.DayPilotMonth("dpm", new DayPilotMonthConfig {
                BackendUrl = ResolveUrl("~/Month/Backend"),
                EventMoveHandling = EventMoveHandlingType.CallBack,
                EventResizeHandling = EventResizeHandlingType.CallBack
            })%>

VB.NET

<%= Html.DayPilotMonth("dpm", new DayPilotMonthConfig With { 
            .BackendUrl = ResolveUrl("~/Month/Backend"),
            .EventMoveHandling = EventMoveHandlingType.CallBack,
            .EventResizeHandling = EventResizeHandlingType.CallBack
           })%>

2. Add a new event handler to Dpm class:

C#

protected override void OnEventResize(EventResizeArgs e)
{
  new EventManager(Controller).EventMove(e.Id, e.NewStart, e.NewEnd);
  Update();
}

VB.NET

Protected Overrides Sub OnEventResize(ByVal e As DayPilot.Web.Mvc.Events.Month.EventResizeArgs)
  Dim em = New EventManager(Controller)
  em.EventMove(e.Id, e.NewStart, e.NewEnd)
  Update()
End Sub

Adding a New Event

The support for creating a new event using AJAX (selecting a time range) can be added using OnTimeRangeSelected handler. The process is similar:

1. Enable time range selecting on the client side by setting TimeRangeSelectedHandling = TimeRangeSelectedHandlingType.CallBack:

C#

        <%= Html.DayPilotMonth("dpm", new DayPilotMonthConfig {
                BackendUrl = ResolveUrl("~/Month/Backend"),
                EventMoveHandling = EventMoveHandlingType.CallBack,
                EventResizeHandling = EventResizeHandlingType.CallBack,
                TimeRangeSelectedHandling = TimeRangeSelectedHandlingType.CallBack
            })%>

VB.NET

<%= Html.DayPilotMonth("dpm", new DayPilotMonthConfig With { 
            .BackendUrl = ResolveUrl("~/Month/Backend"),
            .EventMoveHandling = EventMoveHandlingType.CallBack,
            .EventResizeHandling = EventResizeHandlingType.CallBack,
            .TimeRangeSelectedHandling = TimeRangeSelectedHandlingType.CallBack
           })%>

2. Add a handler to Dpm class:

C#

protected override void OnTimeRangeSelected(TimeRangeSelectedArgs e)
{
  new EventManager(Controller).EventCreate(e.Start, e.End, "Default name");
  Update();
}

VB.NET

Protected Overrides Sub OnTimeRangeSelected(ByVal e As TimeRangeSelectedArgs)
  Dim em = New EventManager(Controller)
  em.EventCreate(e.Start, e.End, "Default name")
  Update()
End Sub

Note: This simple handler doesn't allow the user to specify the event name. This event will be created right away with a default name.

Context Menu for Events

monthly-calendar-context-menu.png

As the next step, we will add a context menu to events. There will be two menu items:

  • Open - it will show event id using a client-side JavaScript handler
  • Delete - it will call the server and delete the event

First, we need to define the context menu using Html.DayPilotMenu() in the view (Views/Home/Index.aspx):

C#

  <%= Html.DayPilotMenu("menu", new DayPilotMenuConfig {
                    CssClassPrefix = "menu_",
                    Items = new DayPilot.Web.Mvc.MenuItemCollection
                                {
                                    new DayPilot.Web.Mvc.MenuItem { Text = "Open", Action = MenuItemAction.JavaScript, JavaScript = "alert(e.value());"},
                                    new DayPilot.Web.Mvc.MenuItem { Text = "Delete", Action = MenuItemAction.CallBack, Command = "Delete"}
                                }
                })
        %>

VB.NET

            <%= Html.DayPilotMenu("menu", new DayPilotMenuConfig With { 
                    .CssClassPrefix = "menu_", 
                    .Items = New DayPilot.Web.Mvc.MenuItemCollection From { 
                        new DayPilot.Web.Mvc.MenuItem With { .Text = "Open", .Action = MenuItemAction.JavaScript, .JavaScript = "alert(e.value());" },
                        new DayPilot.Web.Mvc.MenuItem With { .Text = "Delete", .Action = MenuItemAction.CallBack, .Command = "Delete" }
                    } 
                })
            %>

The context menu block has to be placed above the DayPilotMonth block (otherwise the reference to the menu would be broken).

We will update the DayPilotMonth declaration with a link to the menu (ContextMenu = "menu"):

C#

        <%= Html.DayPilotMonth("dpm", new DayPilotMonthConfig {
                BackendUrl = ResolveUrl("~/Month/Backend"),
                EventMoveHandling = EventMoveHandlingType.CallBack,
                EventResizeHandling = EventResizeHandlingType.CallBack,
                TimeRangeSelectedHandling = TimeRangeSelectedHandlingType.CallBack,
                ContextMenu = "menu"
            })%>

VB.NET

        <%= Html.DayPilotMonth("dpm", new DayPilotMonthConfig With { 
            .BackendUrl = ResolveUrl("~/Month/Backend"),
            .EventMoveHandling = EventMoveHandlingType.CallBack,
            .EventResizeHandling = EventResizeHandlingType.CallBack,
            .TimeRangeSelectedHandling = TimeRangeSelectedHandlingType.CallBack,
            .ContextMenu = "menu"
           })%>

Now we need to handle the "Delete" menu command invoked by the second menu item. We will do this by overriding a OnEventMenuClick method:

C#

protected override void OnEventMenuClick(EventMenuClickArgs e)
{
  switch (e.Command)
  {
    case "Delete":
      new EventManager(Controller).EventDelete(e.Id);
      Update();
      break;
  }
}

VB.NET

Protected Overrides Sub OnEventMenuClick(ByVal e As DayPilot.Web.Mvc.Events.Month.EventMenuClickArgs)
  If e.Command = "Delete" Then
    Dim em = New EventManager(Controller)
    em.EventDelete(e.Id)
    Update()
  End If
End Sub

Summary

All the basic AJAX operations are enabled now. It didn't take more than about 100 lines of code (excluding the data handling EventManager class).

Full source of Views/Home/Index.aspx:

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>
<%@ Import Namespace="DayPilot.Web.Mvc.Enums" %>
<%@ Import Namespace="DayPilot.Web.Mvc.Events.Month" %>

<asp:Content ID="aboutTitle" ContentPlaceHolderID="TitleContent" runat="server">
    Month
</asp:Content>

<asp:Content ID="aboutContent" ContentPlaceHolderID="MainContent" runat="server">
    <p>

            <%= Html.DayPilotMenu("menu", new DayPilotMenuConfig {
                    CssClassPrefix = "menu_",
                    Items = new DayPilot.Web.Mvc.MenuItemCollection
                                {
                                    new DayPilot.Web.Mvc.MenuItem { Text = "Open", Action = MenuItemAction.JavaScript, JavaScript = "alert(e.value());"},
                                    new DayPilot.Web.Mvc.MenuItem { Text = "Delete", Action = MenuItemAction.CallBack, Command = "Delete"}
                                }
                })
        %>



        <%= Html.DayPilotMonth("dpm", new DayPilotMonthConfig {
                BackendUrl = ResolveUrl("~/Month/Backend"),
                EventMoveHandling = EventMoveHandlingType.CallBack,
                EventResizeHandling = EventResizeHandlingType.CallBack,
                TimeRangeSelectedHandling = TimeRangeSelectedHandlingType.CallBack,
                ContextMenu = "menu"
                
            })%>
    </p>
</asp:Content>

App_Code/Controllers/MonthController.cs:

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using DayPilot.Web.Mvc;
using DayPilot.Web.Mvc.Enums;
using DayPilot.Web.Mvc.Events;
using DayPilot.Web.Mvc.Events.Month;

namespace MvcApplication1.Controllers
{
    [HandleError]
    public class MonthController : Controller
    {

        public ActionResult Backend()
        {
            return new Dpm().CallBack(this);
        }

        public class Dpm : DayPilotMonth
        {

            protected override void OnTimeRangeSelected(TimeRangeSelectedArgs e)
            {
                new EventManager(Controller).EventCreate(e.Start, e.End, "Default name");
                Update();
            }

            protected override void OnEventMove(EventMoveArgs e)
            {
                new EventManager(Controller).EventMove(e.Id, e.NewStart, e.NewEnd);
                Update();
            }

            protected override void OnEventMenuClick(EventMenuClickArgs e)
            {
                switch (e.Command)
                {
                    case "Delete":
                        new EventManager(Controller).EventDelete(e.Id);
                        Update();
                        break;
                }
            }

            protected override void OnEventResize(EventResizeArgs e)
            {
                new EventManager(Controller).EventMove(e.Id, e.NewStart, e.NewEnd);
                Update();
            }

            protected override void OnInit(InitArgs initArgs)
            {
                Update();
            }

            protected override void OnFinish()
            {
                // only load the data if an update was requested by an Update() call
                if (UpdateType == CallBackUpdateType.None)
                {
                    return;
                }

                Events = new EventManager(Controller).FilteredData(VisibleStart, VisibleEnd).AsEnumerable();

                DataStartField = "eventstart";
                DataEndField = "eventend";
                DataTextField = "name";
                DataIdField = "id";

            }
        }
    }
}