I have created a Blazor Webassembly Project and added a key listener in JavaScript, which is listening to every key stroke on the DOM document. Everything works as expected, however when I open the Blazor page where the key listener is registered and later open it again, the following error occurs in the Web Browser:
There is no tracked object with id '2'. Perhaps the
DotNetObjectReference instance was already disposed. (Parameter
'dotNetObjectId')
Obviously the object "dotnethelper" is disposed but the Javascript is still listening / getting called.
Basically I implemented the "Component instance .NET method helper class" from the Microsoft Documentation.
Blazor Page:
Note: The IDisposable is injected on the top and the Dispose function is getting called.
#code {
private KeyListenerInvokeHelper _keyListenerInvokeHelper;
private DotNetObjectReference<KeyListenerInvokeHelper>? objRef;
protected override async Task OnInitializedAsync()
{
objRef = DotNetObjectReference.Create(_keyListenerInvokeHelper);
await JS.InvokeVoidAsync("initializeKeyListener", objRef);
}
public void Dispose()
{
objRef?.Dispose();
}
}
Javascript File:
window.initializeKeyListener = (dotnetHelper) => {
document.addEventListener('keydown', logKey);
function logKey(e) {
dotnetHelper.invokeMethod('OnKeyDown', e.key);
console.log('key down ' + e.key);
}
}
KeyListenerInvokeHelper:
public class KeyListenerInvokeHelper
{
private readonly Action<string> action;
public KeyListenerInvokeHelper(Action<string> action)
{
this.action = action;
}
[JSInvokable("OnKeyDown")]
public void OnKeyDown(string key)
{
action.Invoke(key);
}
}
What have I tried so far?
I tried to reset the function on window.initializeKeyListener (i.e. setting window.initializeKeyListener), however this did not achieve anything
I tried removing the eventlistener on the 'keydown' event.
When you dispose of your object, you need to remove the event listener as well. You mentioned I tried removing the eventlistener on the 'keydown' event., but perhaps the way you did it was not correct?
My javascript is a little rusty, but I think you could do something like the following:
var logkey;
window.initializeKeyListener = (dotnetHelper) => {
logkey = (e) => {
dotnetHelper.invokeMethod('OnKeyDown', e.key);
console.log('key down ' + e.key);
};
document.addEventListener('keydown', logkey);
}
window.removeKeyListener = () => {
document.removeEventListener('keydown', logkey);
}
and then in your component:
#implements IAsyncDisposable
public async ValueTask DisposeAsync()
{
await JSRuntime.InvokeVoidAsync("removeKeyListener");
objRef?.Dispose();
}
Having said that, perhaps calling a static method in C# using [JSInvokable] would be better suited for your use case?
Related
Following this tutorial https://vaadin.com/blog/calling-java-from-javascript I'm trying to call a Java function from javascript but that doesn't seem to work as expected.
I'm having a View that contains a button which, on its onClick handler, triggers a call to a Javascript function, which works as expected.
The problem I'm having is that the getElement() that I`m passing to the javascript function is undefined when it reaches the javascript side of things.
My code looks as follows:
#JavaScript("./js/script.js")
public class RouteGraphicsView extends Div {
....
Button b = new Button("Test Button");
b.addClickListener(new ComponentEventListener<ClickEvent<Button>>() {
private static final long serialVersionUID = 1L;
#Override
public void onComponentEvent(final ClickEvent<Button> event) {
UI.getCurrent().getPage().executeJs("greet($0, $1)", "test name", UI.getCurrent().getElement());
}
});
....
}
The above call reaches the script.js file which looks like this
window.greet = function greet(name, element) {
console.log("Hello, I am greeting you, " + name);
try {
console.log("Element ", element);
console.log("Logging 1", element.$server);
} catch (e) {
console.log(e);
}
}
The output shown by the greet function above is
Hello, I am greeting you, test name
vaadin-bundle-62ac8b…b56c6.cache.js:4813 Element
vaadin-bundle-62ac8b…b56c6.cache.js:4813 Logging 1 undefined
Since the element.$server is undefined I can not get the javascript function to call my greet function in the View, which is annotated with #ClientCallable
#ClientCallable
public void greet(final String name) {
System.out.println("Called from JavaScript: " + name + " \n\n\n");
}
I've tried various other ways of calling the script.js, like using button's element to invoke the executeJs function or passing the button's element (b.getElement()) as an argument to the function but to no avail.
What am I doing wrong ?
You're doing element.$server on the element that you passed as UI.getCurrent().getElement(). This corresponds to the UI instance and not an instance of the RouteGraphicsView class that (I assume) has the #ClientCallable method. Using the button would also not work for the same reason.
You should pass an instance of the view, which in your case needs to be written as RouteGraphicsView.this because of the way the regular this refers to the click listener.
I have a javascript event called Hello:
addEventListener('hello', function () {
alert("event listener");
})
and, in another javascript function, I raise the event:
let event = new Event("hello", { bubbles: true });
document.dispatchEvent(event);
What I want to do now is let the event trigger in a javascript function.
Blazor should listen to the event, not javascript calling a Blazor method.
Hope anyone can assist me.
Regards me,
For custom events, you will need to manually utilize JavaScript/.NET interoperability.
Using the Instance Method Call method:
Pass the .NET instance by reference to JavaScript:
Make a static call to DotNetObjectReference.Create.
Wrap the instance in a DotNetObjectReference instance and call Create on the DotNetObjectReference instance. Dispose of DotNetObjectReference objects (an example appears later in this section).
Invoke .NET instance methods on the instance using the invokeMethod or invokeMethodAsync functions. The .NET instance can also be passed as an argument when invoking other .NET methods from JavaScript.
Example
Note, this is a very simplified example. You probably want to add a few things; start by IDisposable on your interop classes to avoid memory leaks.
In C#, create a helper class to manage the interop:
public class CustomEventHelper
{
private readonly Func<EventArgs, Task> _callback;
public CustomEventHelper(Func<EventArgs, Task> callback)
{
_callback = callback;
}
[JSInvokable]
public Task OnCustomEvent(EventArgs args) => _callback(args);
}
public class CustomEventInterop : IDisposable
{
private readonly IJSRuntime _jsRuntime;
private DotNetObjectReference<CustomEventHelper> Reference;
public CustomEventInterop(IJSRuntime jsRuntime)
{
_jsRuntime = jsRuntime;
}
public ValueTask<string> SetupCustomEventCallback(Func<EventArgs, Task> callback)
{
Reference = DotNetObjectReference.Create(new ScrollEventHelper(callback));
// addCustomEventListener will be a js function we create later
return _jsRuntime.InvokeAsync<string>("addCustomEventListener", Reference);
}
public void Dispose()
{
Reference?.Dispose();
}
}
In a Blazor component, add an instance of the interop class (Interop) and add a local method as a callback (HandleCustomEvent):
private CustomEventInterop Interop { get; set; }
protected override async Task OnAfterRenderAsync(bool firstRender) {
if (!firstRender)
{
return;
}
Interop = new(JS);
await Interop.SetupCustomEventCallback(args => HandleCustomEvent(args));
HasRendered = true;
}
private void HandleCustomEvent(EventArgs args) {
// ... handle custom event here
}
In JavaScript, add a method that references the DotNetObjectReference and can call the interop in C#:
function addCustomEventListener(dotNetObjectRef) {
document.addEventListener('hello', (event) => {
// Calls a method by name with the [JSInokable] attribute (above)
dotNetObjectRef.invokeMethodAsync('OnCustomEvent')
});
}
If using TypeScript, you might check out this GitHub Issue.
For anyone wondering the solution proposed by #Mister Magoo is no longer a preview for .NET 6 and is documented here with some exemples.
In a nutshell :
Create a C# class with the EventHandlerAttribute :
[EventHandler("oncityclicked", typeof(CustomSelectionCityEventArgs),
enableStopPropagation: true, enablePreventDefault: true)]
public static class EventHandlers
{
}
public class CustomSelectionCityEventArgs: EventArgs
{
public Guid Id { get; set; }
}
Add JS inside ./wwwroot/index.html and after <script src="_framework/blazor.webview.js" autostart="false"></script> :
<script>
Blazor.registerCustomEventType('cityclicked', {
createEventArgs: event => {
return {
id: event.detail
};
}
});
</script>
In you razor :
#code {
private void HandleCityClicked(CustomSelectionCityEventArgs eventArgs)
{
Console.WriteLine("Bouep");
}
}
<div id="CarteLeaflet" #oncityclicked="HandleCityClicked"></div>
And finally in the JS you can dispatch the event :
function OnMarkerClick(pId) {
const event = new CustomEvent('cityclicked', {
bubbles: true,
detail: pId
});
document.getElementById('CarteLeaflet').dispatchEvent(event);
}
Don't make the same mistake as me, the event name in the C# should start with "on" (JS : "cityclicked", C# : "oncityclicked").
I am working on a task where I have an event listener window.addEventListener which is in javascript, based on event data I want to trigger a function of typescript and pass the data to that function. the point I am not getting is how to call typescript function in javascript. I have tried different things like a global variable, returning a value from js function but didn't work for me.
ngOnInit() {
(function ($) {
$(document).ready(function () {
window.addEventListener('message', function (event) {
console.log("Rece new Call event ibrahim " + JSON.stringify(event.data));
let obj: any = JSON.stringify(event.data)
obj = JSON.parse(obj);
console.log(obj)
if (obj.EventType === "handleContactIncoming") {
var num = obj.Number
// here i want to trigger Typescript function and pass this num to that function.
}
else if (event.data === "endCall") {
// return "No"
// var dbBtn = document.getElementById("db");
// dbBtn.click();
}
// $('.sidebar-menu').tree();
});
});
});
There is no difference when calling function from TS or JS. Finally there's always only JS in the browser, TS exists only in source code.
Also your code is a bit messy. There's no need to use jQuery inside angular (with an exception when you want to use some plugins or custom controls).
$(document).ready(function () {
is also redundant, if angular works the document is for sure ready.
Your code is quite messy and breaks separation of concerns. You should not use jQuery inside Angular. The injectable EventManager provides functionality for setting events on the window or document objects.
constructor(private readonly eventManager: EventManager) {}
ngOnInit(): void {
this.eventManager.addGlobalEventListener(target, event, () => {
this.hello();
});
}
hello(): void {
console.log('Hello World!');
}
I have built an angular application and I am using signalR to get notifications.
For the signalR stuff i have installed "#aspnet/signalr": "^1.0.4".
In the Angular App I have built a service that handles all notifications that are sent over signalR:
private _hubConnection: HubConnection;
numberX: number = 0;
constructor() {
this.createConnection();
this.registerOnServerEvents();
this.startConnection();
}
private createConnection() {
this._hubConnection = new HubConnectionBuilder().withUrl(someURL).build();
}
private startConnection(): void {
this._hubConnection.start();
}
private registerOnServerEvents(): void {
this._hubConnection.on('Notify', (data: any) => {
this.numberX++;
});
}
When I get an event from signalR the event handler is actually called, when I send something to the console output it is actually written to the console.
But when I try to access a member property (in this case numberX), I always get an exception telling me that numberX is undefined, even I define it at the very start.
Could this be some kind of scope thing?
EDIT:
The solution from Kenzk447 actually works for my given scenario. But this would not work when I go one step further by not just increasing a number, but using an EventEmitter that can be consumed by components:
In the service:
someEvent: EventEmitter<any> = new EventEmitter();
private registerOnServerEvents(): void {
const $self = this;
this._hubConnection.on('ErrorMessage', (data: any) => {
$self.someEvent.emit(data);
});
}
In the component:
notificationCount: number = 0;
this.notificationService.someEvent.subscribe(this.onSomeEvent);
onSomeEvent(data: any) {
this.notificationCount++;
}
When doing this, 'this' in the component is undefined and I cannot access properties of the component.
EDIT 2:
Ok I got it working with
this solution
The trick is to bind the eventHandler.
Nevertheless I will accept the answer because it worked in my given example and pointed me to the right direction.
I was researched on SignalR repo, and i found:
methods.forEach((m) => m.apply(this, invocationMessage.arguments));
Issue here: m.apply(this,..., SignalR trying to impose 'this' as HubConnection class instance. So, my solution:
private registerOnServerEvents(): void {
const $self = this;
this._hubConnection.on('Notify', (data: any) => {
$self.numberX++;
});
}
I am trying to call a Wicket component's method from JavaScript and receive a value from this method which I want to use in the remaining bit of the JavaScript function which I used to call the component. However, I only seem to be able to call a Wicket component without waiting for it to produce a result.
More explicitly, I want to implement an AbstractDefaultAjaxBehavior which allows me to conditionally warn a user when he or she is leaving a page. This condition is for now determined by some OuterClass.shouldWarn method. However, even though this method gets called in my example below, I seem to be both unable to wait for a result of this method as well as I am unable to return some sort of result at all. Instead, the JavaScript just continues in its execution concurrently to the Java method call.
I hope the (not correctly running) example below clarifies my question:
class PageExitWarningBehavior extends AbstractDefaultAjaxBehavior {
#Override
protected void respond(AjaxRequestTarget target) {
target.appendJavaScript("return " +
(OuterClass.this.shouldWarn() ? "false" : "true"));
}
#Override
public void renderHead(Component component, IHeaderResponse response) {
String callbackFunktion = String.format(
"Wicket.Event.add(window, 'beforeunload', function( e ) {%n"
+ "if( e ) { e.returnValue = '%s'; }%n"
+ "var attrs = { 'u': '%s', 'c': '%s', 'ep': { } };%n"
+ "Wicket.Ajax.get( attrs );%n"
+ "return false;%n;"
+ "});",
this.getCallbackUrl(),
OuterClass.this.getMarkupId());
response.render(JavaScriptHeaderItem.forScript(callbackFunktion,
"remind-of-running-task"));
}
}
I believe there is an easier way to intercept a page exit event than implementing your own AjaxBehavior:
Try implementing the following Behavior:
public class PageExitWarningBehavior extends Behavior {
private boolean shouldWarn = false;
#Override
public void renderHead(Component component, IHeaderResponse response) {
super.renderHead(component, response);
if (shouldWarn) {
response.render(new OnDomReadyHeaderItem("window.onbeforeunload = function (e) {"
+ "var message = 'Your confirmation message goes here.',"
+ "e = e || window.event;" + "if (e) {"
+ "e.returnValue = message;" + "}" + "return message;" + "};"));
}
}
#Override
public void onEvent(Component component, IEvent<?> event) {
super.onEvent(component, event);
if (event.getPayload() instanceof PageExitWarningEvent) {
PageExitWarningEvent exitEvent = (PageExitWarningEvent) event.getPayload();
this.shouldWarn = exitEvent.isPageExitWarningEnabled();
}
}
}
In the renderHead method you conditionally add a simple javascript that triggers the browser to show a confirmation dialog when leaving the page (the javascript code is from this post).
In the onEvent method we listen if some other Wicket component has sent an PageExitWarningEvent to inform us that a warning should be displayed at all. You can send such an event from any Wicket component (such as a link or button) like this:
send(HomePage.this, Broadcast.BREADTH, new PageExitWarningEvent(true));
The PageExitWarningEvent class looks like this:
public class PageExitWarningEvent {
private boolean pageExitWarningEnabled = false;
public PageExitWarningEvent(boolean pageExitWarningEnabled) {
this.setPageExitWarningEnabled(pageExitWarningEnabled);
}
public boolean isPageExitWarningEnabled() {
return pageExitWarningEnabled;
}
public void setPageExitWarningEnabled(boolean pageExitWarningEnabled) {
this.pageExitWarningEnabled = pageExitWarningEnabled;
}
}
Let me know if that meets your requirements.