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
.
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.