webEngine.executeScript(); Throwing Exception - javascript

I'm writing a JavaFX App, which Interacts with the JavaScript, Using WebView and WebEngine (.executeScript() Method).
Here, I have this part of code from Medow.java, which loads map.html(Contains JavaScript Code), And This Code Works Pretty well:
add_button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent ea5) {
// webEngine.executeScript("document.fun();"); // For Drawing Shapes
if (add == false) {
webEngine.executeScript("document.fun();"); // For Drawing Shapes
add = true;
}
// }
else {
webEngine.executeScript("document.reSet();"); // To remove Drawing Shapes
add = false;
}
}
});
In Here
webEngine.executeScript();
Is Invoking Appropriate JavaScript function's
But Now, I want my Java Code to Invoke Some JS function, when the Program Starts, So I'm directly writing :
webEngine.executeScript("document.draw();");
right Under/after the code, which loads the map.html file.
So, now as Both of the
webEngine.execute("document.fun();"); and webEngine.executeScript("document.draw();"); are nearly similar, I cannot Understand what Difference does, it makes to be inside the <button>.setOnAction block and to be outside it, Because both WebEngine and webView are declared as Global Variables.
cannot invoke document.draw() function using HTML's onLoad options, because i need to pass some Values To function draw from java.
The Exception Generated is :
netscape.javascript.JSException: TypeError: undefined is not a function (evaluating 'document.draw()')
how can i make this work? thank you
While Continuously trying to figure out whats the cause, I Discovered That the HTMLDocument Object, created using webEngine.load(), is for some reason visible only inside the handle method, And nowhere else, even though its been defined outside.

What happens here is that you want to call a JavaScript function before the content is loaded completely. Therefore the function draw is not defined. So the only way is to wait until the page is loaded. There are two ways that might prove useful:
Add a changelistener on the state and execute the JavaScript once the loading has succeeded:
String htmlURL = ...
webView.getEngine().load(htmlURL);
webView.getEngine().stateProperty().addListener(new ChangeListener<Worker.State>() {
#Override
public void changed(ObservableValue<? extends Worker.State> ov, Worker.State t, Worker.State t1) {
if (t1 == Worker.State.SUCCEEDED) {
// this will be run as soon as WebView is initialized.
webView.getEngine().executeScript("document.draw()");
}
}
});
The other way is more of a solution within JavaScript. You first have to register a bridge between Java and your html page (has to be done in the SUCCEEDED state change as well, see WebView callback from Javascript):
JSObject window = (JSObject) webEngine.executeScript("window");
window.setMember("app", this);
Now this JavaObject is referenced in your JavaScript. Let's say that you have a method on the class that is of the above type of this:
public void executeOnPageLoaded() {
...
}
Then you can call this method from Javascript. If you are using jQuery it could look like this:
$( document ).ready(function() {
console.log( "ready!" );
app.executeOnPageLoaded();
});
This second approach is more complex but in the long run may give you more flexibility.
When you start working with JavaScript in the WebView is is a good idea to have Firebug lite in there as well, so investigate what is happening but mainly to have a means to seed the console output of JavaScript. See Java FX application onload event

Related

Send event to js from swift or objective-c

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

How to call existing javascript function from android activity

I'm building an ebook reader, so I have a webview that is loading a page stored locally on the device which it retrieved from the ebook itself. On this page, it has a javascript function controls.nextPage() that loads and runs just fine; it's used to not actually navigate to new web pages, but instead redraw virtual pages using javascript. I have this function bound to a button on the web page itself so that I can manually click it to test, again, works just fine when I touch the button on my webview.
Now I am trying to trigger this exact function from within my app. Ideally, I want to do this from a gesture swipe but that is too complicated for this specific question, as I have other issues with the gestures I need to solve first. For now, I've set up a button in my navigation drawer to trigger it and test it:
NavigationView navigationViewRight = (NavigationView) findViewById(R.id.nav_view_webview_right);
navigationViewRight.setNavigationItemSelectedListener(
new NavigationView.OnNavigationItemSelectedListener() {
#SuppressWarnings("StatementWithEmptyBody")
#Override
public boolean onNavigationItemSelected(MenuItem item) {
// Handle navigation view item clicks here.
int id = item.getItemId();
if (id == R.id.nav_camera) {
// *** - Focus here - *** //
runOnUiThread(new Runnable() {
#Override
public void run() {
mWebview.evaluateJavascript("controls.pageNext()", null);
}
});
} else if (id == R.id.nav_share) {
}
drawer.closeDrawer(GravityCompat.END);
return true;
}
}
);
Note, I've also tried calling window.controls.pageNext() to no avail.
So my page is loaded, and I've hit my in-page button to test the function; works. I go to hit my navigation drawer button in the app? Error (when using window.controls.pageNext():
[INFO:CONSOLE(1)] "Uncaught TypeError: Cannot read property 'pageNext' of undefined", source: (1)
So it seems to be that evaluateJavascript() is being run in a fresh environment/thread. How can I tell it not to?
To get around this,I've tried to create an empty javascript interface in the hopes that I could simply initialize my page javascript into it and thus be able to call it from Android.
mWebview.addJavascriptInterface(new TestInterface(), "TestInterface");
public class TestInterface {
#JavascriptInterface
public void test() {
runOnUiThread(new Runnable() {
#Override
public void run() {
mWebview.evaluateJavascript("console.log('test')", null);
}
});
}
}
From my webapp, the javascript can call the interface just fine. calling TestInterface.test(); yields:
[INFO:CONSOLE(1)] "test", source: (1)
But when I tried to assign a new function to that interface from my webapp:
TestInterface.testTwo = function() {
console.log('testTwo');
};
TestInterface.testTwo();
Android wouldn't have it:
[INFO:CONSOLE(674)] "testTwo", source: http://127.0.0.1:8080/js/main.js (674)
What's weird is that it's not really giving me much info to go on. I do know that the rest of my page has issues loading after the testTwo() attempt that test() did not, so I'm assuming failure to load the rest of the script.
Lastly, out of curiousity, I changed my navigation drawer button to try and run the new function like this:
mWebview.evaluateJavascript("TestInterface.testTwo()", null);
Log:
[INFO:CONSOLE(1)] "Uncaught TypeError: TestInterface.testTwo is not a function", source: (1)
Yes but is it something else? I dunno. Thoughts? Thank you.
So I figured out in the end what my issue was. By running mWebview.evaluateJavascript("window.location", null); I realized that I in fact was not actually on the page I thought I was. My ebook page was being loaded into an iframe or some other type of skeleton/wrapper in such a way that the webapp functions were not in fact available when running evaluateJavascript().
So once figured that out, I can confirm some things that I originally questioned above:
So it seems to be that evaluateJavascript() is being run in a fresh
environment/thread. How can I tell it not to?
It does not. Just make sure you know what page is actually loaded.
To get around this,I've tried to create an empty javascript interface in the hopes that I could simply initialize my page javascript into it and thus be able to call it from Android.
This does in fact work. I'm not sure what my mistake was before, but if I create a javascript interface in Android, it's functions are available to the webapp AND I can in fact write new objects to the interface object from within the webapp. So, initializing new objects into TestInterface from within the webapp can be run within the Android app via:
mWebview.evaluateJavascript("TestInterface.someNewFunctionFromWebapp()", null);
I can NOT however overwrite any existing objects/properties of that javascript interface object. so TestInterface.test() is immutable.

ActiveX / COM object event handler for Hardware Device

I am creating an ActiveX control for a hardware (card swiper) device. I have understanding of ActiveX development and working, but I am little stuck with my specific scenario. Device SKD (dll files) have method to activate swiper/chip reader in order to be in listening state. Assume the following:
SDK sdk = new SDK();
sdk.RegisterCallback(GlobalCallback);
sdk.ActivateSwiper();
public void GlobalCallback(op, .., ..)
{
switch(op) {
case Swiped:
readCardData();
break;
case TransactionData:
readData();
break;
//. . . .
}
Above code is just for example. SDK has a global callback method for any event triggered through device (eg, card swiped, ICC seated, etc). If the SDK is being used in WPF/WinForm app, it is super easy get it working. But in my case, I must need to handle the raised event in javascript on my web page.
So, I have exposed a method in ActiveX to turn on the device, which works perfect:
[Guid("4794D615-BE51-4a1e-B1BA-453F6E9337C4")]
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces(typeof(IComEvents))]
public class MyComObject : IComObject
{
[ComVisible(true)]
public string ActivateDevice()
{
SDK sdk = new SDK();
sdk.RegisterCallback(GlobalCallback);
string resultCode = sdk.ActivateSwiper();
return resultCode;
}
//. . <other code> . . .
}
and use it in webpage as:
<object id="myComComponent" name="myComComponent" classid="clsid:4794D615-BE51-4a1e-B1BA-453F6E9337C4"></object>
<script language="javascript" type="text/javascript">
var returnCode = myComComponent.ActivateDevice(); //or by creating ActiveXObject
</script
I have also got the idea about how to handle events using ActiveX on webpage. I learnt it from HERE. But the event is handled by calling the exposed method to raise event. And in my case, when card is swiped, the call will go to GlobalCallback() method.
Questioins:
Is there any workaround, I can implement handler in my scenario, to make it usable on javascript?
I am thinking of something like PropertyChanged event, bound to a string property, which holds the derived card data. and the handlers returns this property value. Is there any workaround possible like this? I need little help with this.
I am thinking this as:
public static string CARD_DATA;
public void GlobalCallback(op, .., ..)
{
switch(op) {
case Swiped:
CARD_DATA = readCardData();
//and CARD_DATA is bound to property-changed event, and its handler returns its value.
//other code
Is this even possible? If so, What to be exposed? and How to use it? as this property will be changed internally, when card is swiped, and case Swiped: is executed. Is there any workaround?
Other Info:
Web App is MVC 4 famework based
Device is The Augusta (IDTech). (they don't have actieX/plugin for web)

Does JavaFX8 WebEngine's executeScript("window") method refer to the JavaScript window object?

I'm wondering about this code snippet I'm using:
WebView webView = new WebView();
JSObject jsobj;
webEngine = webView.getEngine();
try {
webEngine.load(getClass().getResource("index.html").toExternalForm());
} catch (Exception e) {
e.printStackTrace();
}
try {
jsobj = (JSObject) webEngine.executeScript("window");
jsobj.setMember("java", new DataModel());
} catch (Exception e) {
e.printStackTrace()
}
This line webEngine.load(getClass().getResource("index.html").toExternalForm()); loads my index.html into the WebView. Since it is a single page application, that's all I need and there are no more questions about it.
Now to get back to the question in the title:
Does this line: jsobj = (JSObject) webEngine.executeScript("window"); set the JSObject reference to the "window object" as it's defined by w3school?Meaning that jsobj is now equal to the window object, that represents the open window in the browser?
If so: Is this likely to be the reason why my application won't work if I use "reload page" over the context menu in the WebView, since it would generate a new window object?
Bonus question: In my JavaScript I can acces the model using java.methodName(); is "java" here an identifier or how would I call it? It's not exactly an instance, so I wouldn't call it like this.
Edit:
This is my Java class:
public class DataModel{
public void alert(String msg) {
System.out.println(msg);
}
}
From my JavaScript, which is embedded in index.html I can call java.alert("hello world"); to print hello world. Since I'm passing the model reference to the window object every function in my JavaScript has access to the DataModel.class. From the JavaScript point of view: Is "java." an identifier, reference, instance, ...?
According to this site, webEngine.executeScript("window") would in fact return the JavaScript Window object. There's also a whole bunch of other code snippet goodies to look!
java in this case is a handler that can be used by your JavaScript, within the Window object. So your JavaScript, when referencing java can expect to access the DataModel object.
Also, you normally want to refresh your page with location.reload() in JavaScript.
If you're looking to save state between reloads, you'll want to capture the current HTML and then use the WebView's loadContent functionality when the reload is complete. You could also get the WebView's document so you can directly modify nodes at any level.

passing parameters to running silverlight application

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);

Categories

Resources