I'm experimenting with Blazor in Visual Studio, specifically with calling Blazor code from JavaScript. I'm reasonably confident that I've got all the right libraries in place. However, when I attempt to call my Blazor method with invokeMethodAsync, I get the message "no .net call dispatcher has been set". In my Index.Html file, I have this:
<script>
DotNet.invokeMethodAsync("BlazorFour.App", "HelloYou").then(data => alert(data), reason => alert(reason));
</script>
(It's the alert(reason) that generates the error message)
I've added a class file to my Blazor project and it contains this:
using Microsoft.JSInterop;
using System.Threading.Tasks;
public class HelloWorld
{
[JSInvokable]
public static Task<string> HelloYou()
{
return Task.FromResult("Hello, ");
}
}
I've used all the templates in Visual Studio and the dotnet -new blazor commandline utility to create my start points but get the same message in all of the projects. It's seems likely to me that I'm missing something fundamental here.
Peter Vogel:
It would be great if you could call Blazor C# methods from your JavaScript code. Right now, that's not quite possible: Simply calling Blazor C# from JavaScript won't work (you'll get a message about a call dispatcher not being available).
What you can do, however, is once your Blazor code is running, call JavaScript code from your C# code. Once you've done that, your JavaScript code can, in turn, call Blazor code.
Hope this solves the problem...
I'm using server-side-blazor
I was getting js errors
No .NET call dispatcher has been set
So yep .. looks like Blazor wasn't initialized..
Not keen on a timeout
.. seems hacky and gives a laggy UI
Not keen on 'call JavaScript code from your C# code. Once you've done that, your JavaScript code can, in turn, call Blazor code.'
.. again seems hacky
This works for me ..
async function myBlazoryFunctionThing() {
// see https://sourcegraph.com/github.com/aspnet/AspNetCore#bd65275148abc9b07a3b59797a88d485341152bf/-/blob/src/Components/Web.JS/src/Boot.Server.ts#L41:9
await window.Blazor.reconnect();
..now it's safe to do my stuff
}
it 'seems' that Blazor.reconnect() will re-use the existing connection (if there is one)
...so it's not actually 're-connecting' (so 'seems' not much overhead ;-))
I tried a setTimeout (5000 ms delay) and it worked for me.(of course it is not needed to use the setTimeout every time, just the first time).
e.g. setTimeout( DotNet.invokeMethod(...), 5000);
I guess this is a timing issue, not everything is loaded/initialized.
Have you tried putting the js code in a button onclick event?
using Microsoft.JSInterop;
[Inject] protected IJSRuntime JSRuntime { get; set; }
From your blazor function call this function from this method
await JSRuntime.InvokeAsync<object>("fnToJavascriptCall", DotNetObjectReference.Create(this));
Create this function in blazor file
[JSInvokable]
public async Task fnToCallFromJavascript(int param1, int param2)
{
//Do some stuff
StateHasChanged();
}
In your javascript file
var fnToJavascriptCall = function (param1,currentInstance)
{
//Do your stuff
currentInstance.invokeMethodAsync("fnToCallFromJavascript", param1, param2);
}
The No .NET call dispatcher has been set error can be received when the JavaScript interop is used at the wrong time. Have a look at this StackOverflow question.
Related
With Blazor WebAssembly, when an error occurs, it shows the div with id "blazor-error-ui".
Is it possible to call a JS function when an error occur without using an observer and without using the framework ?
I've dug a little deeper and it seems that the Blazor WebAssembly framework calls window.Module.printErr() whenever a runtime error happens.
I'm not a JavaScript expert, but it seems like you could intercept that call.
A naive implementation that proves the method would be to replace your script to load blazor webassembly in the index.html with this:
<script src="_framework/blazor.webassembly.js" autostart="false"></script>
<script>
document.addEventListener("DOMContentLoaded", function() {
Blazor.start().then(function () {
window["OLDprintErr"] = window.Module.printErr
window.Module.printErr = e => {
window["OLDprintErr"](e) // invoke standard behaviour
alert(e) // invoke custom behaviour - just an alert for POC
}
})
})
</script>
With that in place, any runtime errors will pop an alert and trigger the standard blazor-error-ui - of course you can replace the call to alert with whatever you want.
It is beyond my JS skills to do this in a nicer way, but I leave that to the reader / comments below - if any decent JS improvements appear I will edit the answer.
I have a bunch of Dart libraries that a customer wants to call from JavaScript. Is it possible to call functions created with dartdevc from JS?
If you only want to call functions, you can probably do that in this way. Although I only know it's works for functions, probably not for whole class. I am also not sure about dartdevc support, but dart2js should be able to do this:
import 'dart:js' as js;
main() {
js.context['methodFromDart'] = doMyStuff;
}
void doMyStuff(String text) => print(text);
And then in you Javascript you are free to do:
methodFromDart("Hello world to Dart!");
I have created the following class (condensed version), heres a reference to the full file
https://github.com/cotyembry/CastRemoteNative/blob/7e74dbc56f037cc61241f6ece24a94d8c52abb32/root/ios/CastRemoteNative/NativeMethods.swift
#objc(NativeMethods)
class NativeMethods: RCTEventEmitter {
#objc(sendEventToJSFromJS)
func sendEventToJSFromJS {
self.emitEvent(eventName: "test", body: "bodyTestString")
}
func emitEvent(eventName: String: body: Any) {
self.sendEvent(withName: eventName, body: body)
}
}
This works perfectly and fires my callback listener that is in my javascript code when I call the emitEvent method like the following, its an altered snippet from
https://github.com/cotyembry/CastRemoteNative/blob/7e74dbc56f037cc61241f6ece24a94d8c52abb32/root/js/Components/ChromecastDevicesModal.js
From the javascript side
import {
NativeModules,
NativeEventEmitter
} from 'react-native'
//here I bring in the swift class to use inside javascript
var NativeMethods = NativeModules.NativeMethods;
//create an event emitter to use to listen for the native events when they occur
this.eventEmitter = new NativeEventEmitter(NativeMethods);
//listen for the event once it sends
this.subscription = this.eventEmitter.addListener('test', (body) => { console.log('in test event listener callback', body)});
NativeMethods.sendEventToJSFromJS() //call the native method written in swift
I simply have the sendEventToJSFromJS method invoked on a button press in javascript
Again, this works and the console.log('in test event listener callback', body) code works and runs on the javascript side
My Issue where this does NOT work:
If I was to do the following inside the swift file after defining the class, this would not work:
var nativeMethodsInstance = nativeMethods()
nativeMethodsInstance.sendEventToJSFromSwift()
Why? Because the following error is thrown:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'bridge is not set. This is probably because you've explicitly synthesized the bridge in NativeMethods, even though it's inherited from RCTEventEmitter.'
So, when creating an instance of NativeMethods, versus not... what is the difference?
For additional information:
Objective-C gets the same bridge not set issue when I write these same snippets of code in .h and .m files instead of in .swift files
I found where the error message is getting printed in the native code, but it just has the variable
_bridge
and is checking to see if it is nil
The files are this error comes from is:
RCTEventEmitter.h
RCTEventEmitter.c
here is the full snippet of RCTEventEmitter.c
- (void)sendEventWithName:(NSString *)eventName body:(id)body
{
RCTAssert(_bridge != nil, #"bridge is not set. This is probably because you've "
"explicitly synthesized the bridge in %#, even though it's inherited "
"from RCTEventEmitter.", [self class]);
if (RCT_DEBUG && ![[self supportedEvents] containsObject:eventName]) {
RCTLogError(#"`%#` is not a supported event type for %#. Supported events are: `%#`",
eventName, [self class], [[self supportedEvents] componentsJoinedByString:#"`, `"]);
}
if (_listenerCount > 0) {
[_bridge enqueueJSCall:#"RCTDeviceEventEmitter"
method:#"emit"
args:body ? #[eventName, body] : #[eventName]
completion:NULL];
} else {
RCTLogWarn(#"Sending `%#` with no listeners registered.", eventName);
}
}
Where does this _bridge value get set and how does it get set so I can know, in the cases where it is failing how to set it
I found the following also in RCTEventEmitter.h
#property (nonatomic, weak) RCTBridge *bridge;
In the error that is given it mentions the bridge is inherited in the RCTEventEmitter, so is this maybe an issue with the weak part to the bridge property?
Or do I need to change my strategy in how I'm doing this all together?
I know it probably has to be something to do with me not fully understanding the
#synthesize bridge = _bridge;
part of the code and all the languages being mixed in doesnt help much lol...
This is really hard, so any help would be much appreciated!
Thanks so much for your time
here is a link to the full project when the project history code represented the code from my question above (since I have since made changes to the project):
https://github.com/cotyembry/CastRemoteNative/tree/7e74dbc56f037cc61241f6ece24a94d8c52abb32
I figured it out
Warning: this solution uses a deprecated method react native method - I could not figure out how to "properly" inherit from the RCTEventEmitter and send an event... every time I tried to the _bridge would end up being nil
Make sure Swift is bridged to Objective C (if you're using swift to send the event to javascript)
Do Not create instances of the exported Native modules (whether they be written in Swift or Objective C)
Let React Native's underlying implementation do this and for each and every class that needs to send an event, export that particular Native Class Objective C Implementation code or Swift code (the Native Module) to React-Native. This allows the javascript to be able to listen to the event
var publicBridgeHelperInstance = PublicBridgeHelper() //instantiate the the objective c class from inside the .swift file to use later when needing to get a reference to the bridge to send an event to javascript written in react native
#objc(DeviceManager) //export swift module to objective c
class DeviceManager: NSObject {
#objc(deviceDidComeOnline:) //expose the function to objective c
public func deviceDidComeOnline(_ device: GCKDevice) {
//imagine this deviceDidComeOnline function gets called from something from the Native code (totally independent of javascript) - honestly this could be called from a native button click as well just to test it works...
//emit an event to a javascript function that is a in react native Component listening for the event like so:
//1. get a reference to the bridge to send an event through from Native to Javascript in React Native (here is where my custom code comes in to get this to actually work)
let rnBridge = publicBridgeHelperInstance.getBridge() //this gets the bridge that is stored in the AppDelegate.m file that was set from the `rootView.bridge` variable (more on this later)
//(if you want to print the bridge here to make sure it is not `nil` go ahead:
print("rnBridge = \(rnBridge)")
//2. actually send the event through the eventDispatcher
rnBridge?.eventDispatcher().sendAppEvent(withName: "test", body: "testBody data!!!")
}
}
in AppDelegate.h put (additionally to the code that was already in the file)
#import "YourProjectsBridgingHeaderToMakeThisCodeAvailableInSwift.h" //replace this with your actual header you created when creating a swift file (google it if you dont know how to bridge swift to objective c)
#interface PublicBridgeHelper: NSObject
-(RCTBridge*)getBridge;
#end
in AppDelegate.m put (in addition to the code that was already in the file)
#import <React/RCTRootView.h>
RCTBridge *rnBridgeFromRootView;
#implementation PublicBridgeHelper //this is created to SIMPLY return rnBridgeFromRootView defined above over to my Swift class when actually sending the event to javascript that defines a react native Component
-(RCTBridge*)getBridge {
NSLog(#"rnBridgeFromRootView = #%#", rnBridgeFromRootView);
return rnBridgeFromRootView;
}
important - also make sure to add the following line of code to the Objective C .h's bridging header to make this PublicBridgeHelper definition available to be used in the .swift code
#import "AppDelegate.h"
finally,
now to show you how to set the rnBridgeFromRootView variable used in AppDelegate.m (that gets returned and used in the .swift code right before sending the event to javascript)
open AppDelegate.m and in the method body of
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ... }
include the following after the line of code that instantiates the rootView variable
i.e. after the line that probably looks like
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation moduleName:#"YourProjecNameProbably" initialProperties:nil launchOptions:launchOptions];
add:
rnBridgeFromRootView = rootView.bridge //set the bridge to be exposed and returned later and used by the swift class
Now to explain the publicBridgeHelperInstance.getBridge() part that is in the .swift file
publicBridgeHelper is an instance of an objective c class which allows the swift class ability to get a reference to the react native bridge
If you are still having problems understanding my answer after reading this I made a video over it and you can watch it here:
https://www.youtube.com/watch?v=GZj-Vm9cQIg&t=9s
I'm looking for a way to make async calls to Android's native code from JS
I have a Main Activity with the following code to make accessible native code to JS:
webView.addJavascriptInterface(new BindingHelper(this), "Android");
webView.loadUrl("file:///android_asset/www/index.html");
The BindingHelper Class contains something like this:
#JavascriptInterface
public void showToast(String toast) {
Toast.makeText(theContext, toast, Toast.LENGTH_SHORT).show();
}
#JavascriptInterface
public String SuperDuperComplexFunction () {
//A function that will need some time to finish...
return "{}";
}
The previous Methods can be called from the Index.html linked JS as follows:
<script type="text/js">
Android.showToast("Toast");
</script>
In that way the showToast() function is executed synchronously. What I need is to call the method SuperDuperComplexFunction(); in a aSync way (just like an AJAX Request), and when the method success take some action.
Any ideas?
One option is to use an Http Server in java code and then make the AJAX call on localhost. That way the Javascript call would be the exact same as any other AJAX call, and since you control the Http Server you can just have it call your SuperDuperComplexFunction()
I've used NanoHttpd in the past for something kind of similar but not quite the same.
The scenario is I have a list of items in HTML; when I click on an item I use JS to dynamically create the HTML to load a silverlight app passing in the specific item # (using initParams); and my silverlight app visualizes this in a nice way. I do this on the same page rather than loading a new webpage, and the transition is smooth.
I know it is possible to have silverlight call a JS function on my page (opposite to what I need). I'm thinking it is also possible for my JS function to raise an event/call a method in silverlight, but not exactly sure how - has anyone tried this? While a workaround would be to recreate the silverlight app each time, just raising an event in existing, loaded SL app would would be the perfect solution to my problem.
regards
ewart.
You can call a method in your Silverlight application from JavaScript.
See this blog post
You just need to create a class in your silverlight app that registers itself as callable from JS:
[ScriptableType]
public partial class SomeClass
{
private bool mouseHeldDown = false;
private Point moveMeOffset = new Point();
public SomeClass()
{
HtmlPage.RegisterScriptableObject("SilverlightObject", this);
}
[ScriptableMember]
public void DoThing(int x)
{
//do some stuff
}
}
Then you can call this from JS
document.getElementById("mySilverlightControl").content.SilverlightObject.DoThing(5);