This is a migrated thread and some comments may be shown as answers.

Issue with SignalR data source

12 Answers 506 Views
Scheduler
This is a migrated thread and some comments may be shown as answers.
Erik
Top achievements
Rank 1
Erik asked on 05 Feb 2018, 03:28 PM

Hi,

I've been trying to get a scheduler widget working with a signalr datasource, following the example project "scheduler-signalr-server-filtering".

The scheduler doesn't show any data, and the read method in my Hub never gets hit. The javascript console in Chrome shows the following error: "kendo.all.js:13206 Uncaught Error: The "promise" option must be set."

This option is set in the widget, which looks like this:

<script src="@Url.Content("~/signalr/hubs")"></script>
<script>
    var lockedRecords = {};
    var scheduleHub, hubStart;
 
    $(function() {
        scheduleHub = $.connection.scheduleHub;
        $.connection.hub.qs = {'subscriptionId': subscriptionId}
 
        scheduleHub.client.lockRecord = function(record) {
            lockedRecords[record.id] = true;
        };
 
        scheduleHub.client.unlockRecord = function(record) {
            lockedRecords[record.id] = false;
        }
         
        hubStart = $.connection.hub.start();
    });
 
    function forRead(data, type) {
        var scheduler = $("#scheduler").data("kendoScheduler");
 
        var timezone = scheduler.options.timezone;
        var startDate = kendo.timezone.convert(scheduler.view().startDate(), timezone, "Etc/UTC");
 
        var initialEndDate = scheduler.view().endDate();
        var augmentedEndDate = new Date(initialEndDate.valueOf());
        augmentedEndDate.setDate(initialEndDate.getDate() + 1);
        var endDate = kendo.timezone.convert(augmentedEndDate, timezone, "Etc/UTC");
 
        var result = {
            Start: new Date(startDate.getTime() - (startDate.getTimezoneOffset() * kendo.date.MS_PER_MINUTE)),
            End: new Date(endDate.getTime() - (endDate.getTimezoneOffset() * kendo.date.MS_PER_MINUTE))
        }
 
        console.log(result);
 
        return result;
    }
 
    function forCreate(data, type) {
        console.log(data);
        console.log(type);
    }
 
    function onMap(data, type) {
        switch (type) {
        case "read": {
            return forRead(data, type);
        }
        case "create":
        {
            return forCreate(data, type);
        }
        default: {
            return data;
        }
        }
    }
</script>
 
<script>
    function onDataBound(e) {
        this.view().content.on("click", ".k-event-delete", preventEvent);
    }
 
    function preventEvent(e) {
        var scheduler = $("#scheduler").data("kendoScheduler");
        var eventUid = $(this).closest(".k-event").attr(kendo.attr("uid"));
        var event = scheduler.occurrenceByUid(eventUid);
 
        if (lockedRecords[event.id]) {
            e.stopImmediatePropagation();
            alert("Currently the event cannot be deleted");
        }
    }
 
    function scheduler_edit(e) {
        var salesPersonId = e.container.find("#SalesPersonPicker").data("kendoDropDownList");
        salesPersonId.dataSource.data(e.sender.resources[0].dataSource.data());
    }
 
    function scheduler_resize(e) {
        if (salesPersonIsOccupied(e.start, e.end, e.event, e.resources)) {
            this.wrapper.find(".k-marquee-color").addClass("invalid-slot");
            e.preventDefault();
        }
    }
 
    function scheduler_resizeEnd(e) {
        if (!checkAvailability(e.start, e.end, e.events)) {
            e.preventDefault();
        }
    }
 
    function scheduler_move(e) {
        if (salesPersonIsOccupied(e.start, e.end, e.event, e.resources)) {
            this.wrapper.find(".k-event-drag-hint").addClass("invalid-slot");
        }
    }
 
    function scheduler_moveEnd(e) {
        if (!checkAvailability(e.start, e.end, e.event, e.resources)) {
            e.preventDefault();
        }
    }
 
    function scheduler_add(e) {
        if (!checkAvailability(e.event.start, e.event.end, e.event)) {
            e.preventDefault();
        }
    }
 
    function scheduler_save(e) {
        if (!checkAvailability(e.event.start, e.event.end, e.event)) {
            e.preventDefault();
        }
    }
 
    function occurrencesInRangeByResource(start, end, resourceFieldName, event, resources) {
        var scheduler = $("#scheduler").getKendoScheduler();
 
        var occurrences = scheduler.occurrencesInRange(start, end);
 
        var idx = occurrences.indexOf(event);
        if (idx > -1) {
            occurrences.splice(idx, 1);
        }
 
        event = $.extend({}, event, resources);
        return filterByResource(occurrences, resourceFieldName, event[resourceFieldName]);
    }
 
    function filterByResource(occurrences, resourceFieldName, value) {
        var result = [];
        var occurrence;
 
        for (var idx = 0, length = occurrences.length; idx < length; idx++) {
            occurrence = occurrences[idx];
            var resourceValue = occurrence[resourceFieldName];
 
            if (resourceValue === value) {
                result.push(occurrence);
            } else if (resourceValue instanceof kendo.data.ObservableArray) {
                if (value) {
                    for (var i = 0; i < value.length; i++) {
                        if (resourceValue.indexOf(value[i]) != -1) {
                            result.push(occurrence);
                            break;
                        }
                    }
                }
            }
        }
        return result;
    }
 
    function salesPersonIsOccupied(start, end, event, resources) {
        var occurrences = occurrencesInRangeByResource(start, end, "SalesPersonId", event, resources);
        if (occurrences.length > 0) {
            return true;
        }
        return false;
    }
 
    function checkAvailability(start, end, event, resources) {
        if (salesPersonIsOccupied(start, end, event, resources)) {
            setTimeout(function () {
                    alert("Verkoper heeft al een afspraak.");
                },
                0);
            return false;
        }
        return true;
    }
</script>
 
@(Html.Kendo()
          .Notification()
          .Name("notification")
          .Width("100%")
          .Position(pos => pos.Top(0).Left(0)))
 
@(Html.Kendo()
          .Scheduler<ScheduleViewModel>()
          .Name("scheduler")
          .Date(DateTime.Now)
          .StartTime(7, 0, 0)
          .EndTime(19, 0, 0)
          .Height(600)
          .Views(views =>
          {
              views.DayView();
              views.WorkWeekView(wv => wv.Selected(true).Title("Werkweek"));
              views.MonthView();
              views.AgendaView();
          })
          .Editable(e => { e.TemplateName("ScheduleEditorTemplate"); })
          .Height(800)
          .Resources(resource =>
          {
              resource.Add(m => m.SalesPersonId)
                  .Title("Verkoper")
                  .Name("SalesPerson")
                  .DataTextField("Name")
                  .DataValueField("Id")
                  .DataSource(ds => ds.Read(read => read.Action("SalesPerson_Read", "Planning")));
          })
          .Events(events => events
              .Add("scheduler_add")
              .Save("scheduler_save")
              .Resize("scheduler_resize")
              .ResizeEnd("scheduler_resizeEnd")
              .Move("scheduler_move")
              .MoveEnd("scheduler_moveEnd")
              .Edit("scheduler_edit")
              .DataBound("onDataBound"))
          .DataSource(d => d
              .SignalR()
              .ServerFiltering(true)
              .Transport(tr => tr
                  .ParameterMap("onMap")
                  .Promise("hubStart")
                  .Hub("scheduleHub")
                  .Client(c => c
                      .Read("read")
                      .Create("create")
                      .Update("update")
                      .Destroy("destroy"))
                  .Server(s => s
                      .Read("read")
                      .Create("create")
                      .Update("update")
                      .Destroy("destroy")))
              .Schema(schema => schema
                  .Data("Data")
                  .Total("Total")
                  .Model(model =>
                  {
                      model.Id(m => m.Id);
                      model.Field("subscriptionId", typeof(int)).From("SubscriptionId").Editable(false);
                      model.Field("salesPersonId", typeof(int)).From("SalesPersonId").Editable(false);
                      model.Field("prospectId", typeof(Guid)).From("ProspectId").Editable(false);
                      model.Field("location", typeof(string)).From("Location");
                      model.Field("title", typeof(string)).From("Title");
                      model.Field("description", typeof(string)).From("Description");
                      model.Field("isAllDay", typeof(bool)).From("IsAllDay");
                      model.Field("start", typeof(DateTime)).From("Start");
                      model.Field("end", typeof(DateTime)).From("End");
                      model.Field("startTimezone", typeof(string)).From("StartTimezone");
                      model.Field("endTimezone", typeof(string)).From("EndTimezone");
                      model.Field("recurrenceRule", typeof(string)).From("RecurrenceRule");
                      model.Field("recurrenceException", typeof(string)).From("RecurrenceException");
                  }))))

 

The hub looks like this:

[HubName("scheduleHub")]
    public class ScheduleHub : Hub
    {
        //private static readonly ConcurrentDictionary<int, ConcurrentDictionary<string, string>> Connections = new ConcurrentDictionary<int, ConcurrentDictionary<string, string>>();
         
        public IEnumerable<ScheduleViewModel> Read(FilterRange range)
        {
            var scheduleService = GetScheduleService();
            return scheduleService.GetRange(range);
        }
 
        public void Update(ScheduleViewModel schedule)
        {
            var subscriptionId = GetSubscriptionId();
            var scheduleService = new ScheduleService(subscriptionId);
            scheduleService.Update(schedule);
            Clients.OthersInGroup(subscriptionId.ToString()).update(schedule);
        }
 
        public void Destroy(ScheduleViewModel schedule)
        {
            var subscriptionId = GetSubscriptionId();
            var scheduleService = new ScheduleService(subscriptionId);
            scheduleService.Delete(schedule);
            Clients.OthersInGroup(subscriptionId.ToString()).destroy(schedule);
        }
 
        public ScheduleViewModel Create(ScheduleViewModel schedule)
        {
            var subscriptionId = GetSubscriptionId();
            var scheduleService = new ScheduleService(subscriptionId);
            scheduleService.Insert(schedule);
            Clients.OthersInGroup(subscriptionId.ToString()).create(schedule);
            return schedule;
        }
 
        public void LockRecord(int id)
        {
            Clients.OthersInGroup(GetSubscriptionId().ToString()).lockRecord(new {id = id});
        }
 
        public void UnlockRecord(int id)
        {
            Clients.OthersInGroup(GetSubscriptionId().ToString()).unlockRecord(new {id = id});
        }
 
        public override Task OnConnected()
        {
            var subscriptionId = GetSubscriptionId();
            //var sessionId = Context.QueryString["sessionId"];
            Groups.Add(Context.ConnectionId, subscriptionId.ToString());
 
            //var subscriptionDictionary = Connections.GetOrAdd(subscriptionId, x => new ConcurrentDictionary<string, string>());
            //subscriptionDictionary.GetOrAdd(sessionId, x => Context.ConnectionId);
 
            return base.OnConnected();
        }
 
        private ScheduleService GetScheduleService()
        {
            var subscriptionId = GetSubscriptionId();
            return new ScheduleService(subscriptionId);
        }
 
        private int GetSubscriptionId()
        {
            return Convert.ToInt32(Context.QueryString["subscriptionId"]);
        }
    }

12 Answers, 1 is accepted

Sort by
0
Plamen
Telerik team
answered on 07 Feb 2018, 03:15 PM
Hi,

I have tested the application "scheduler-signalr-server-filtering" at our side and it worked correctly without changing anything would you please let us know if it works correctly at your side as it is. If you still can't figure out that issue it may be helpful to send us a runnable application to replcate the issue so we could be more helpful with a possible solution.

Regards,
Plamen
Progress Telerik
Try our brand new, jQuery-free Angular components built from ground-up which deliver the business app essential building blocks - a grid component, data visualization (charts) and form elements.
0
Erik
Top achievements
Rank 1
answered on 05 Mar 2018, 01:03 PM
The issue turned out to be that the scheduler was in a partial view, as a tab in a tabstrip. Moving the first script block (the one with "scheduleHub" and "hubStart") out of the partial into the view with the tabstrip  fixed the issue.

It would be nice if some info on this were included in the documentation, and if the widget would produce some more useful errors.
0
Erik
Top achievements
Rank 1
answered on 06 Mar 2018, 08:48 AM
Unfortunately, the scheduler is still not working for me. It renders, but does not show any of the events. I have attached a solution demonstrating the problem. The main differences with the sample project are the versions of Kendo and SignalR used, could that cause the issue?
0
Erik
Top achievements
Rank 1
answered on 06 Mar 2018, 09:12 AM
Unfortunately, the scheduler is still not working for me. It's rendering now, but events aren't showing, nor is creating new ones working. Attached is a solution demonstrating the issue. I am using much newer versions of Kendo and SignalR than the sample project, could that be the issue?
0
Plamen
Telerik team
answered on 07 Mar 2018, 01:01 PM
Hello,

I have tried to run the provided project but unfortunately couldn't because it had lots of error at my side and didn't run. Yet I have tested the "scheduler-signalr-server-filtering" project with the latest SignalR dlls and it ran correctly.
Would you please let us know if the "scheduler-signalr-server-filtering" it works correctly at your side as it is and then try to replicate the issue with it so we could inspect it and be more helpful?

Regards,
Plamen
Progress Telerik
Try our brand new, jQuery-free Angular components built from ground-up which deliver the business app essential building blocks - a grid component, data visualization (charts) and form elements.
0
Erik
Top achievements
Rank 1
answered on 12 Mar 2018, 09:51 AM
Well, I had to strip that project down to fit the 2MB upload limit. A simple package restore and rebuild should've taken care of that.
Anyway, here it is again, hopefully working this time, and also a link to the entire project, not stripped down.

Link to full project: https://drive.google.com/file/d/1iFdS0sBULCkp9a7nTuGhpD8rox-K8FuZ/view?usp=sharing
0
Plamen
Telerik team
answered on 13 Mar 2018, 09:51 AM
Hello,

I am attaching the updated index file that worked correctly at my side. The client fields that are expected by the Scheduler are with lower case letters and removed the additional data and total parent fields.

Hope this will be helpful.

Regards,
Plamen
Progress Telerik
Try our brand new, jQuery-free Angular components built from ground-up which deliver the business app essential building blocks - a grid component, data visualization (charts) and form elements.
0
Erik
Top achievements
Rank 1
answered on 13 Mar 2018, 02:28 PM

After implementing this, the "Read" method is indeed working, but creating new events is still not functional. The error I get from SignalR is this: "SignalR: 'Create' method could not be resolved. Potential candidates are: 
Create(schedule:ScheduleViewModel):ScheduleViewModel".

Any ideas?

0
Plamen
Telerik team
answered on 15 Mar 2018, 09:52 AM
Hello,

The issue seems to be caused by the Guid property that is not passes correctly from the client and that is why the method is not called. When I removed it from the object the method was called correctly. Here is the code that I used that worked correctly at my side:
function onMap(data, type) {
        switch (type) {
            case "read": {
                return forRead(data, type);
            }
            case "create": {
 
                delete data.ProspectId;
                return data;
            }
            default: {
                 
                return data;
            }
        }
    }


Regards,
Plamen
Progress Telerik
Try our brand new, jQuery-free Angular components built from ground-up which deliver the business app essential building blocks - a grid component, data visualization (charts) and form elements.
0
Erik
Top achievements
Rank 1
answered on 16 Mar 2018, 10:23 AM
This property is fairly essential to my project, as are the others I added to the ScheduleViewModel, which aren't posted either. Does the scheduler widget even actually support adding more properties to the model? And if so, is there any documentation on this, since it's obviously not a straightforward process.
0
Plamen
Telerik team
answered on 16 Mar 2018, 11:28 AM
Hi,

The issue is rather a SignalR issue then a Kendo Scheduler and what is the value that you want to be passed from the client side so that the server side recognizes the Guid property and the object correctly. You can test the scenario with a button and the following code where the issue is isolated from the Scheduler:
$.connection.hub.start().done(function () {
          $('#button1').click(function () {
              var a = {
                  Id : 1,
                  SubscriptionId : 1,
                  SalesPersonId : 1,
                  ProspectId: null,
                  Location: "aaa",
                  Start : new Date(),
                  End : new Date(),
                  Title : 'aaaa',
                  Description : 'aaaa',
                  IsAllDay : false,
                  StartTimezone : "",
                  EndTimezone : "",
                  RecurrenceRule : "",
                  RecurrenceException : ""
              }
 
              scheduleHub.server.create(a);
              
          });
      });

If you want to generate the Guid from the client side you can use the kendo.guid() and assign the ProspectId to it in the onMap function . If you don't want to generate it from the client you should remove it because the otherwise the property is not recognized by the server.

Hope this will explain the issue. If you have further questions please let me know.


Regards,
Plamen
Progress Telerik
Try our brand new, jQuery-free Angular components built from ground-up which deliver the business app essential building blocks - a grid component, data visualization (charts) and form elements.
0
Erik
Top achievements
Rank 1
answered on 16 Mar 2018, 11:30 AM
Thank you for your assistance. I have solved the issue by setting the values for the extra properties in the onMap function, as you suggested. 
Tags
Scheduler
Asked by
Erik
Top achievements
Rank 1
Answers by
Plamen
Telerik team
Erik
Top achievements
Rank 1
Share this question
or