1

The Telerik UI for ASP.Net is fairly useful, but the documentation can be incomplete or unclear at times. The Scheduler is simple to implement initially, but customizing it can be difficult. I need to have a custom editor with different fields than the built-in editor and I need custom restrictions on the event start time and event end time such as the following for the creation of new appointments, but existing appointments should not be affected when opened:

  1. The start and end times provided in the drop down DateTimePicker must reflect our business hours (9:00 - 21:00), but the Kendo helper generates all 24 hours.
  2. Once start time is selected, the end time must occur after the start time and the DateTimePicker should be automatically set to the selected start time value.
  3. Appointment time slots cannot be booked twice.
Rachel Ostrander
  • 184
  • 2
  • 14
  • 1
    To be honest I think Telerik's documentation is so bad because you have to pay for their support services. I'm presently using KendoUI for their graphing features, so I know what you're going through (or went through, based on your answer). – Justin Sep 17 '15 at 18:28

1 Answers1

2

I spent some time on these requirements and through Telerik's blog, forum, demo, API documentation, and submitting tickets to their support, I have been able to accomplish all of these. They were harder than they should have been because of their placement in the custom editor for the scheduler which is a partial view and because of their reliance on fired events from the scheduler itself. Most of the information you need can be passed through events from the Scheduler, but the difficulty is finding documentation on them. I have 2 DateTimePickers, start and end times, in the editor:

<div data-container-for="start" class="k-edit-field">

   @(Html.Kendo().DateTimePickerFor(model => model.Start)
    .Name("start")
    .HtmlAttributes(generateDatePickerAttributes("startDateTime", "start", "value:start"))
    )
<span data-for="start" class="k-invalid-msg"></span>
</div>
<div class="k-edit-label">
    @(Html.Label("End Time*"))
</div>
 <div data-container-for="end" class="k-edit-field">
    @(Html.Kendo().DateTimePickerFor(model => model.End)
    .Name("end")
    .HtmlAttributes(generateDatePickerAttributes(
        "endDateTime",
        "end",
        "value:end",
                new Dictionary<string, object>() { { "data-dateCompare-msg", "End date should be greater than or equal to the start date" } }))
       )
   <span data-for="end" class="k-invalid-msg"></span>
</div>

They are given appropriate Kendo attributes with this method:

@functions{
public Dictionary<string, object> generateDatePickerAttributes(
       string elementId,
       string fieldName,
       string dataBindAttribute,
       Dictionary<string, object> additionalAttributes = null)
{

    Dictionary<string, object> datePickerAttributes = additionalAttributes != null ? new Dictionary<string, object>(additionalAttributes) : new Dictionary<string, object>();

    datePickerAttributes["id"] = elementId;
    datePickerAttributes["name"] = fieldName;
    datePickerAttributes["data-bind"] = dataBindAttribute;
    datePickerAttributes["required"] = "required";
    datePickerAttributes["style"] = "z-index: inherit;";
    datePickerAttributes["data-role"] = "datetimepicker";

    return datePickerAttributes;
}
}
  1. To make the DateTimePicker reflect business hours I subscribed to the Scheduler's Edit event (don't forget to reference this in the Scheduler Events specification) and then modified the widget using the setOptions() method:

    function onEdit(e) {//When we open or add an event
        var min = new Date(kendo.date.getDate(new Date()).getTime() +    (kendo.date.MS_PER_HOUR * 9));
        var max = new Date(kendo.date.getDate(new Date()).getTime() + (kendo.date.MS_PER_HOUR * 21));
    
        e.container.find("[data-role=datetimepicker]").each(function () {
            var widget = $(this).getKendoDateTimePicker();
            widget.timeView.setOptions({
            max: max,
            min: min
        });
    
       });
      }
    
  2. You can bind an onChange() function to the DateTimePicker itself to force the end time to always reflect >= the start time with the following code, also in the onEdit() method of the scheduler. It also handles the situation of changing the actual day (not just time) in the picker and having it stay within business hours. Without this bit, the min time is still whatever you initially set it to meaning that on the next day it will show all 24 hours again.:

       e.container.find("[name=start][data-role=datetimepicker]").bind("change",    function () {
        var widget = $(this).getKendoDateTimePicker();
        widget.timeView.setOptions({
            max: max,
            min: min
        });
    
        var endTimePicker = $("#endDateTime").data("kendoDateTimePicker");
        endTimePicker.value(widget.value());
        endPicker = e.container.find("[name=end][data-role=datetimepicker]").getKendoDateTimePicker();
        endPicker.timeView.setOptions({
            max: max,
            min: min
        });
    });
    e.container.find("[name=end][data-role=datetimepicker]").bind("change", function () {
        var widget = $(this).getKendoDateTimePicker();
        widget.timeView.setOptions({
            max: max,
            min: min
        });
    });
    
  3. Checking for double bookings isn't as complicated as you might assume because it doesn't require any server calls, getting all appointments, checking time ranges, and returning. Telerik has a method for checking appointments in the scheduler. It is a method they conveniently do not document well: occurrencesInRange(). I found it in one of the forum demos. You can just get all the occurrences within the time range you would like to use and if no one else is using it, book the event. If they are, you can just show an alert. Subscribe to the scheduler's Save event:

    function scheduler_save(e) { if (!checkAvailability(e.event.start, e.event.end, e.event)) { alert("This appointment slot is taken. Schedule a different time."); e.preventDefault(); } } function checkAvailability(start, end, event) { var scheduler = $("#scheduler").getKendoScheduler();

    var occurrences = scheduler.occurrencesInRange(start, end);
    var idx = occurrences.indexOf(event);
    if (idx > -1) {
        occurrences.splice(idx, 1);
    }
    return !occurrences.length;
    }
    
  4. Another problem you may run into is that of having the DateTimePicker contain only business hours, but the default value being 00:00:00 of that day, which of course, is outside your range. This was surprisingly difficult to default to business hour start, but you can do the following using the other very helpful method event.isNew():

    if (e.event.isNew()) {
        var startdate = e.event.start;
        startdate.setHours(9);
        var start = e.container.find("[name=start][data-role=datetimepicker]");
        var end = e.container.find("[name=end][data-role=datetimepicker]");
        $(start).data("kendoDateTimePicker").value(startdate); 
        $(end).data("kendoDateTimePicker").value(startdate);
     }
    

    This is the final set of JavaScript methods:

    function onEdit(e) {//When we open or add an event
       var min = new Date(kendo.date.getDate(new Date()).getTime() + (kendo.date.MS_PER_HOUR * 9));
       var max = new Date(kendo.date.getDate(new Date()).getTime() + (kendo.date.MS_PER_HOUR * 21));
       if (e.event.isNew()) {
           var startdate = e.event.start;
           startdate.setHours(9);
           var start = e.container.find("[name=start][data-role=datetimepicker]");
           var end = e.container.find("[name=end][data-role=datetimepicker]");
           $(start).data("kendoDateTimePicker").value(startdate);
           $(end).data("kendoDateTimePicker").value(startdate); 
       }
       e.container.find("[data-role=datetimepicker]").each(function () {
           var widget = $(this).getKendoDateTimePicker();
           widget.timeView.setOptions({
              max: max,
               min: min
           });
    
       });
        e.container.find("[name=start][data-role=datetimepicker]").bind("change", function () {
           var widget = $(this).getKendoDateTimePicker();
           widget.timeView.setOptions({
              max: max,
              min: min
          });
    
          var endTimePicker = $("#endDateTime").data("kendoDateTimePicker");
          endTimePicker.value(widget.value());
          endPicker = e.container.find("[name=end][data-role=datetimepicker]").getKendoDateTimePicker();
          endPicker.timeView.setOptions({
              max: max,
              min: min
          });
       });
       e.container.find("[name=end][data-role=datetimepicker]").bind("change",    function () {
            var widget = $(this).getKendoDateTimePicker();
            widget.timeView.setOptions({
                max: max,
                min: min
            });
        });
    }
    function scheduler_save(e) {
        if (!checkAvailability(e.event.start, e.event.end, e.event)) {
          alert("This appointment slot is taken. Schedule a different time.");
          e.preventDefault();
      }
      }
      function checkAvailability(start, end, event) {
           var scheduler = $("#scheduler").getKendoScheduler();
    
            var occurrences = scheduler.occurrencesInRange(start, end);
            var idx = occurrences.indexOf(event);
            if (idx > -1) {
                occurrences.splice(idx, 1);
            }
            return !occurrences.length;
         }
    

I hope this saves someone else the 20 hours it took me.

Sources: http://www.telerik.com/forums/set-default-time-in-scheduler-editor-window-when-adding-task-in-month-view

http://docs.telerik.com/KENDO-UI/api/javascript/ui/datetimepicker#events

http://docs.telerik.com/KENDO-UI/api/aspnet-mvc/kendo.mvc.ui.fluent/schedulerbuilder

http://www.telerik.com/forums/how-to-check-for-double-reservations-overlap-(repeating-events) Vladimir Illiev, Telerick DateTimePicker Specialist

Rachel Ostrander
  • 184
  • 2
  • 14