Here are some cool things you can let your users do with the Telerik Scheduler for Blazor: drag and drop both events and schedules, add whole new schedules or events, and update your database with the user’s changes.
The Progress Telerik UI Scheduler for Blazor gives you a lot of power: the ability to create an application where your user both generates a schedule of recurring events and then manage individual events in that schedule—cancelling/deleting individual or even changing the entire schedule.
In Part 1: Getting Started, I walked through configuring the Scheduler and defining the single object that you’d need. In Part 2: Managing Schedules and Occurrences, I looked at two operations in managing a schedule: deleting (really, suppressing) scheduled occurrences and adding off-schedule events or events with different titles/descriptions (I called those “exceptions”). In Part 3: Adding Exceptions to the Schedule, I showed how to manage those exceptions to the schedule’s regularly scheduled events.
In this post, I’m going to look at some more specialized topics: Supporting drag and drop (you’ve already done that if you’ve implemented the code from the previous posts) and allowing the user to add new events and schedules.
Plus, one more thing: In this series, I’ve structured my case study application so that my code supports an application with a Save button. That means, after making all their changes, a user can save their schedule (another design would be to save the user’s changes as they make them). At the end of this post, I’m going to take a high-level look at the code involved in saving and retrieving your data.
Here’s what the event object I’m using with Scheduler looks like:
List<BillingPlan> DataList = new List<BillingPlan>();
public class BillingPlan: Appointment
{
public BillingPlan()
{
Id = Guid.NewGuid();
}
public string RecurrenceRuleString { get; set; } = string.Empty;
}
And here’s how I’ve configured scheduler to support all the functionality I’ve discussed so far:
<TelerikRootComponent>
<TelerikScheduler Height="600px"
Data="DataList"
RecurrenceRuleField="RecurrenceRuleString"
AllowUpdate="true"
AllowDelete="true"
OnDelete="@DeleteSchedException"
OnUpdate="@UpdateExceptionSched"
OnCreate="@CreateExceptionSched">
If you implement all of the OnUpdate
and OnCreate
code from my previous posts, you’ve also given your user the ability to drag and drop any schedule, an occurrence of that schedule or an exception to that schedule to a new date in the Scheduler’s UI.
If the user drags an exception, Scheduler moves the exception to the new date. The code for an OnCreate
method that I covered in my previous post will handle that.
If, on the other hand, the user drags an occurrence, they’ll get a dialog asking if they want to edit the schedule or the occurrence.
If the user chooses to edit the occurrence, your OnCreate
method is called, followed by your OnUpdate
method. The code in both events from my previous posts will create a new exception on the date the exception was dragged, add the exception to the Scheduler’s Data List, and suppress the original occurrence (you may also get Blazor’s “An unhandled error has occurred. Reload” pop-up bar but, in fact, your application will work fine).
If, on the other hand, the user chooses to edit the series, your OnUpdate
method is called. Your existing OnUpdate
method will update your existing schedule object by setting the schedule’s start date set to the new date the schedule was dragged to.
In addition to allowing your user to create exceptions to existing occurrences, you can also let your user create whole new exceptions and add them to the schedule by double-clicking on a blank date. You enable that by setting the Scheduler’s AllowCreate
property to true.
That means that a Scheduler with everything possible enabled looks like this:
<TelerikScheduler Height="600px"
Data="DataList"
RecurrenceRuleField="RecurrenceRuleString"
AllowCreate="true"
AllowUpdate="true"
AllowDelete="true"
OnDelete="@DeleteSchedException"
OnUpdate="@UpdateExceptionSched"
OnCreate="@CreateExceptionSched">
Setting AllowCreate
to true will also let your user add whole new schedules … which you may (or may not) want to allow.
When the user double-clicks on an empty date, the user will be presented with a version of the Event form that allows the user to create either a new event or a new schedule—it all depends on whether the user changes the recurrence option in the Repeat section of the form.
If the user keeps the default option in the Repeat section set to Never, then the form will create an event object. If the user chooses one of the other Repeat options (daily, weekly, etc.), then the form will create a schedule object.
Regardless of the choice the user makes with the repeat setting, your OnCreate
method will be called. If the user has left the repeat setting at Never, then the Item object’s RecurrenceStatus
will be set to NonRecurring
. You could just add that event object to your Data List as an event that’s not part of any schedule.
But you might also choose to add that event as an exception to your schedule. To do that, you need to retrieve, from the Data List, the schedule object you want to attach the event to. Once you’ve got the schedule object, you just need to set the event object’s RecurrenceId
to the Schedule’s Id
property to make the event an exception to that schedule.
On the other hand, if the user does set the repeat setting in the Event form, then the object passed to your OnCreate
method will be an event object with its RecurrenceStatus
property set to Master
. You could just add that object to the Data List as a schedule after setting the object’s RecurrenceRule
property from your RecurrencePlanString
property—Scheduler can handle multiple schedules in its UI.
There is a problem with having multiple schedules in Scheduler, though: If, as I suggested earlier, you let the user add both new events to the schedule and have multiple schedules in Scheduler, you now have to decide which schedule to assign new events to. In that case, you’ll need to provide some way to let the user pick the schedule to assign a new event to.
Enhancing the OnCreate
method to support adding new items and schedules would look something like this:
void CreateExceptionSched(SchedulerCreateEventArgs e)
{
BillingPlan? bpNew = e.Item as BillingPlan;
switch (bpNew.RecurrenceStatus)
{
case RecurrenceStatus.NonRecurring:
IEnumerable<BillingPlan> bpScheds = DataList.Where(
bpSch => bpSch.RecurrenceStatus == RecurrenceStatus.Master);
if (bpScheds.Count() == 1)
{
bpNew.RecurrenceId = bpScheds.First().Id;
}
else
{
//have the user select a schedule to
//assign to the exception
bpNew.RecurrenceId = selectedSched.Id;
}
DataList.Add(bpNew);
break;
case RecurrenceStatus.Master:
//Either pop up a message saying that schedules can’t be added
//or
sdm.Add(New);
break;
case RecurrenceStatus.Exception:
BillingPlan bpSched = DataList.First(bp => bp.Id == bpNew.RecurrenceId);
bpNew.DataItem = bpSched.DataItem;
DataList.Add(bpNew);
break;
}
}
Finally, let’s take a high-level look at how your application would interact with some data store to share your schedule’s occurrences and exceptions with other applications.
There will be times when your application starts by displaying a blank Scheduler and your user will double-click on dates to add new occurrences or schedules. Most of the time, though, your user will be modifying an existing schedule and you’ll need to build that schedule from whatever data you’ve stored in your database.
To support that, I’ve assumed you’ll store your schedule data in two tables—one for schedule objects and one for exceptions. Building a schedule from those two tables would look a little like this:
protected override Task OnParametersSetAsync()
{
//Get the schedule objects
IList<BillingPlan> bpSchedules = GetBillingPlanSchedulesForCustomer(parmCustId);
//Create the list, including both the schedule objects and any exceptions
DataList.AddRange(bpSchedules);
DataList.AddRange(GetBillingPlanExceptionsForCustomer(parmCustId);
}
When the user saves their schedule, as I discussed in an earlier post on recurring events, you’ll need to generate the occurrences from your schedule object and add them to the database so that other applications can have access to those events. You’ll also need to process changes to what’s currently in the database, based on what’s in your Data List and your list of deleted items.
The following code organizes those updates into two groups: First, it calls the methods to delete any old events/schedules and then it calls the methods to save the current events/schedules.
You have two strategies for your delete method:
RecurrenceId
that matches the schedule’s Id
)That results in two strategies for the save method:
With the second strategy, it might make sense to add a three-state Updated
property to the event objects you use to flag them as changed, added or unchanged since retrieval. Personally, I’d prefer the “delete all” strategy (it provides the fewest opportunity for bugs).
This code assumes a selective strategy in the delete method and avoids the resulting save issues by deferring reconciling adds/updates to child methods:
private void OnSave()
{
DeleteItems()
SaveItems();
}
private void DeleteItems()
{
//Delete any schedules found in the deleted items list
foreach (BillingPlan bpSched in DeletedItems.Where(
bpItem => bpItem.RecurrenceStatus == RecurrenceStatus.Master))
{
DeleteBillingPlanForCustomer(parmCustId, bpSched);
// Delete all generated occurrences and newly suppressed dates
// (these two methods could be included in the
// DeleteBillingPlanForCustomer method)
DeleteBillingPlanOccurencesForCustomer(parmCustId, bpItem);
DeleteBillingPlanSuppressed DatesForCustomer(parmCustId, bpItem);
}
//Delete any exceptions/events added to the deleted item list
foreach (BillingPlan bpItem in DeletedItems.Where(
bpItm => bpItm.RecurrenceStatus != RecurrenceStatus.Master))
{
DeleteBillingPlanExceptionsForCustomer(parmCustId, bpItem);
}
}
Private void SaveItems()
{
//Save all the schedule objects in the List,
// updating any schedules already saved
foreach(BillingPlan bpSched in DataList.Where(
bpSch => bpSch.RecurrenceStatus == ReccurenceStatus.Master) )
{
SaveBillingPlanForCustomer(parmCustId, bpSched);
//Save the list of occurrences generated from the schedule and any
//newly suppressed occurrences, updating any occurrences already saved
//with changes made to the schedule (e.g. title, description)
// (these two methods could be included in the
// SaveBillingPlanForCustomerMethod)
SaveBillingPlanOccurencesForCustomer(parmCustId, bpSched);
SaveBillingPlanOccurencesForCustomer(parmCustId, bpItem);
}
//Save any exceptions in the Data List, reconciling
//against any existing items
foreach(BillingPlan bpItem in DataList.Where(
bpItem => bpItem.RecurrenceStatus != ReccurenceStatus.Master) )
{
SaveBillingPlanExceptionsForCustomer(parmCustId, bpItem);
}
}
In this series, I’ve covered all the options that Scheduler provides for working with recurring events: all three processes (adding/updating/deleting) for all three different kinds of objects (schedules, occurrences and exceptions). You don’t have to implement all of this functionality—you can choose to handle as many or as few of them as makes sense for your application. Personally, I’d handle all of them … but I’m a consultant, so I’m paid by the hour. You might make a more rational decision.
Get all the C# files for this series here. And if you haven’t already started, check out Telerik UI for Blazor yourself!
Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter also writes courses and teaches for Learning Tree International.