How to use DDay.iCal and Kendo UI RadScheduler Server-side for Recurring Appointments

2 posts, 0 answers
  1. Robert
    Robert avatar
    57 posts
    Member since:
    Sep 2011

    Posted 14 Dec 2016 Link to this post

    The 2 most important things to note is that when working with any RadScheduler

    1. The backend implementation of the RadScheduler is NOT consistent across telerik platforms.
    2. Kendo UI does NOT support any type server-side processing

    Because of the above, I have spent much time struggling with processing the server-side when using the Kendo UI RadScheduler. I saw the recommendations about using ASP.NET RadScheduler, or WinForms RadScheduler, DDay.iCal or iCal.NET. 

    One of the most frustrating things was the backend storage of the recurrence string did not plug/play into other RadScheduler platforms directly: recurrence string differences, class names, methods, etc. (#1 issue above)

    In many forum answers, it just says use DDay.ICal or iCal.NET but there is no sample code. Well here it is for DDay.iCal! I was able to get DDay.iCal to do server-side processing of recurring events. I offer my code up to the community for review and use. Use at own risk.

    Please note that I modified that standard Kendo UI backend database structure to include an “Estimated End Date”. It is NULL when the appointment “never” ends. I calculate the value during the appointment save using settings from the recurrence string. As long as I estimate correctly or even if I over-estimate, I am in the clear with my code.

    Also note, that my appointments can have multiple people tied to it. Thus, you will see a final “foreach” loop that will flatten out the appointments per person.

    My primary goal was to get a list of appointments, per person, within a specified time range so i can send reminder emails.

     

    public List<AppointmentItem> GetAppointments(DateTime startDate, DateTime endDate)
    {
        var results = new List<AppointmentItem>();
      
        var appts = _dataContext.tblAppointments
                                .Where(w =>
                                        w.StartDateTime <= endDate
                                        && (
                                            (w.EstimatedEndDateTime == null) // this gets the "never" ends entries
                                            ||
                                            (w.EstimatedEndDateTime.Value >= w.StartDateTime)
                                        )
                                )
                                .ToList();
      
        foreach (var appt in appts)
        {
            IICalendar iCal = new iCalendar();
            IEvent eventHolder = iCal.Create<Event>();
            eventHolder.Start = new iCalDateTime(appt.StartDateTime);
            eventHolder.End = new iCalDateTime(appt.EndDateTime);
      
            if (!appt.RecurrenceAppointmentId.HasValue)
            {
                var recurPattern = new RecurrencePattern($"RRULE:{appt.RecurrenceRule}");
                eventHolder.RecurrenceRules.Add(recurPattern);
            }
      
            if (!string.IsNullOrEmpty(appt.RecurrenceException))
            {
                var periodList = new PeriodList();
                var allDates = appt.RecurrenceException.Split(char.Parse(","));
      
                foreach (var item in allDates)
                    periodList.Add(new iCalDateTime(DateTime.ParseExact(item, "yyyyMMddTHHmmssZ", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal)));
      
                eventHolder.ExceptionDates.Add(periodList);
            }
      
            //if seems GetOccurrences does BETWEEN check, non-inclusive so i have to widen the dates a little bit.
            foreach (var occurrence in iCal.GetOccurrences(startDate.AddMilliseconds(-1), endDate.AddDays(1)))
            {
                results.AddRange(appt.tblAppointmentStudents.Select(student => new AppointmentItem
                                                                               {
                                                                                   AppointmentName = appt.AppointmentName,
                                                                                   AppointmentNotes = appt.AppointmentDetails,
                                                                                   EndTime = occurrence.Period.EndTime.Date,
                                                                                   StartTime = occurrence.Period.StartTime.Date,
                                                                                   StudentId = student.StudentId
                                                                               }));
            }
        }
      
        //results = results.OrderBy(t => t.StartTime).ToList();
        return results;
    }

     

    The AppointmentItem class:

     

    public class AppointmentItem
    {
       
        public int StudentId { get; set; }
        public string AppointmentName { get; set; }
        public string AppointmentNotes { get; set; }
        public DateTime StartTime { get; set; }
        public DateTime EndTime { get; set; }
    }

    The function call

    testing.GetAppointments(new DateTime(2016,12,15), new DateTime(2016, 12, 21));


    Database Table tblAppointment

     

    CREATE TABLE [dbo].[tblAppointment]
    (
    [AppointmentId] [int] NOT NULL IDENTITY(1, 1),
    [AppointmentName] [varchar] (500) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
    [AppointmentDetails] [varchar] (max) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
    [EndDateTime] [datetime] NOT NULL,
    [StartDateTime] [datetime] NOT NULL,
    [EstimatedEndDateTime] [datetime2] NULL,
    [RecurrenceAppointmentId] [int] NULL,
    [RecurrenceException] [varchar] (max) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
    [RecurrenceRule] [varchar] (1024) COLLATE SQL_Latin1_General_CP1_CI_AS NULL
    ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
    GO
    ALTER TABLE [dbo].[tblAppointment] ADD CONSTRAINT [PK_tblAppointment] PRIMARY KEY CLUSTERED ([AppointmentId]) ON [PRIMARY]
    GO
    ALTER TABLE [dbo].[tblAppointment] ADD CONSTRAINT [FK_tblAppointment_tblAppointment] FOREIGN KEY ([RecurrenceAppointmentId]) REFERENCES [dbo].[tblAppointment] ([AppointmentId])
    GO

     

    Warning: i am using a beta candidate of DDay.iCal. After going through each type pattern (daily, weekly, monthly, yearly) and each type of pattern (until, first day of, exact day, etc.) I did not encounter any problems.

     

    The only problem is when the appointment is set up "oddly". By that i mean this by way of example. Appointment occurs the 15th of every month. You set the initial date on the 20th of January. Kendo UI RadScheduler correctly does NOT show an entry on Jan 15 or Jan 20. However, DDay will have an occurrence on Jan 20 for your "15th of the month" event. What it boils down to is setting up the appointment skewed from pattern. It is a small risk in general in my application. The worst case scenario is that an even shows when it should not. Probably better than not showing it when it should.

    Happy Programming!

  2. Ivan Danchev
    Admin
    Ivan Danchev avatar
    912 posts

    Posted 16 Dec 2016 Link to this post

    Hello Robert,

    Thank you for the effort you have put in creating this solution. If you have the time, we would suggest creating a simple sample project that demonstrates the described functionality. This way it can be added as a Code Library and would make it more accessible and easy to test and use for the community.

    Regards,
    Ivan Danchev
    Telerik by Progress
    Try our brand new, jQuery-free Angular 2 components built from ground-up which deliver the business app essential building blocks - a grid component, data visualization (charts) and form elements.
Back to Top