javascriptc#kendo-uiblazortelerik

Passing a Blazor object reference to a Telerik Chat control


I've read Microsoft's documentation for calling JavaScript from Blazor. I've been on the Telerik site and searched through their documentation and forums. I've done many search engine queries. I've searched SO and found similar issues that don't quite answer my problem.

Specifically, I want JavaScript code that is a property of an object passed to kendo.Class.extend to be able to call a Blazor component instance method.

What I'm not trying to do is call a static method - I can get that to work and it isn't what I want. I don't want to call an arbitrary method in a script tag because I have a specific scenario for using the Telerik Chat control with specific syntax.

Again, if you refer me to another question or show me a link, please ensure it's specific to my situation.

The context of this approach is that Telerik doesn't have a Blazor chat control. Therefore, by Telerik's recommendation, I needed to communicate between the Blazor component and the Kendo jQuery Chat widget.

Here's the code that's similar to Telerik examples of how to write the jQuery portion:

// MyChatComponent.razor.js

var chat = $("#chat").kendoChat({
    post: function (args) {
        agent.postMessage(args);
    }
}).data("kendoChat");

window.ChatAgent = kendo.Class.extend({
    init: function (chat, dotnet) {
        this.chat = chat;
        this.dotnet = dotnet;

        console.log('dotneti', dotnet);
    },
    postMessage: function (args) {
        var chat = this.chat;

        this.dotnet.invokeMethodAsync('MyChatComponent', 'AskAsync', args.text)
            .then(data => {
                chat.renderMessage({
                    type: "text",
                    text: data,
                    timestamp: new Date()
                }, {
                    name: "Generellem"
                });
            });
    }
});

var agent = new ChatAgent(chat);

agent.chat.renderMessage({
    type: "text",
    text: "Hi, How can I help today?",
    timestamp: new Date()
}, {
    name: "Generellem"
});

The modification that I made was to add the dotnet parameter to the init function in the kendo.Class.extend object. Further down, in the postMessage function, I use that dotnet instance variable to call invokeMethodAsync. This doesn't work - the console.log in the init function prints out that dotnet is undefined.

One of the problems is that I don't know where to put dotnet so that it will have the proper instance reference. That brings me to the component, which is the instance I want the code above to receive:

// MyChatComponent.razor

@page "/chat"
@rendermode InteractiveServer

@inject IJSRuntime JS

<h3>Chat</h3>

<div id="chat"></div>

@code {
    CancellationTokenSource cancelTokenSource = new();
    DotNetObjectReference<Chat>? objRef;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            objRef = DotNetObjectReference.Create(this);

            await JS.InvokeAsync<IJSObjectReference>(
            "import", "./Components/Pages/Chat.razor.js", objRef);
        }
    }

    [JSInvokable]
    public async Task<string> AskAsync(string message)
    {
        return await Task.FromResult(message);
    }
}

There are a couple of items here that are interesting. The first is in OnAfterRenderAsync where I call DotNetObjectReference.Create(this) to get an instance of the current object, as objRef. I want to pass objRef to the JavaScript code so that I can call the AskAsync instance method.

Inside of AskAsync, I added objRef as the 3rd argument to InvokeAsync. I don't know if that's the right thing to do, but I tried it.

To summarize, I want to pass objRef from the C# code to the JavaScript code so that the JavaScript code has a reference to the component instance when it calls AskAsync.


Solution

  • Here's the work-around I'm using, though it feels like it lacks elegance. Essentially, I called a JS function to explicitly set the object reference, rather than trying to figure out how to pass an argument during the import. Here's the updated JS code:

    // MyChatComponent.razor.js

    var chat = $("#chat").kendoChat({
        post: function (args) {
            agent.postMessage(args);
        }
    }).data("kendoChat");
    
    window.ChatAgent = kendo.Class.extend({
        init: function (chat) {
            this.chat = chat;
        },
        postMessage: function (args) {
            var chat = this.chat;
    
            this.dotnet.invokeMethodAsync('MyChatComponent', 'AskAsync', args.text)
                .then(data => {
                    chat.renderMessage({
                        type: "text",
                        text: data,
                        timestamp: new Date()
                    }, {
                        name: "Generellem"
                    });
                });
        }
    });
    
    var agent = new ChatAgent(chat);
    
    agent.chat.renderMessage({
        type: "text",
        text: "Hi, How can I help today?",
        timestamp: new Date()
    }, {
        name: "Generellem"
    });
    
    var dotnet = {};
    
    export function setDotnetInstance(objRef) {
        dotnet = objRef;
    };
    

    I removed the dotnet parameter from the kendo.Class.extend.init. Then, I created an object variable, dotnet, at the end of the file. I also added the setDotnetInstance function export to set the dotnet variable with the reference to the Blazor component.

    In the Blazor component, I invoided setDotnetInstance in OnAfterRenderAsync, like this:

        protected override async Task OnAfterRenderAsync(bool firstRender)
        {
            if (firstRender)
            {
                objRef = DotNetObjectReference.Create(this);
    
                IJSObjectReference module = await JS.InvokeAsync<IJSObjectReference>(
                    "import", "./Components/Pages/Chat.razor.js");
                await module.InvokeVoidAsync("setDotnetInstance", objRef);
            }
        }
    

    The change was to take the IJSObjectReference, module, returned from the JS.InvokeAsync to call setDotnetInstance with InvokeVoidAsync.

    If I find a better way to do this, I'll update this answer.