Calling Javascript functions running inside Rhino from Java is easy enough - that after all is why Rhino was created. The thing I am having trouble establishing is this:
Context: I have a Phonegap CLI (v 6.3.3) Android project (API 19+) where I do a great deal of processing via loadable JavaScript running inside rhino
A Phonegap plugin - which I am creating at the same time as the actual Phonegap app - contains class called Storage which provides public, static, methods such as readFromFile(String fileName), writeToFile(String fileName,String data) etc.
What I want to be able to do is to call Storage.readFromFile etc from my loaded JavaScript code in Rhino.
Just how this should be done is not too clear to me. From the searches I have done thus far it involves using ScriptableObject.putProperty to pass the Java class in question, Storage in my case to JavaScript. However, how this should be done and then how it should be used at the JS end leaves me rather confused.
I would be most grateful to anyone here who might be able to point me in the right direction
Given that Rhino has less than 100 followers here it should perhaps come as little surprise that this question was not answered. In the mean time I have managed to find the solution myself and it turns out to be very simple. I share it below for the benefit of anyone else running into this thread.
My Storage class is very simple. It goes something like this
public class Storage
{
public static boolean haveFile(){}
public static boolean readFromFile(String fname){}
...
}
When I call Javascript from Java via Rhino I simply pass a new instance of the Storage class as the last of my function parameters
Context rhino = Context.enter();
Object[] functionParams = new Object[] {"Other parameters",new Storage()};
rhino.setOptimizationLevel(-1);
try
{
Scriptable scope = rhino.initStandardObjects();
String rhinoLog = "var log = Packages.io.vec.ScriptAPI.log;";
String code = /*Javascript code here* as shown separately below/;
rhino.evaluateString(scope, rhinoLog + code, "ScriptAPI", 1, null);
Function function = (Function) scope.get("jsFunction", scope);
Object jsResult = function.call(rhino,scope,scope,functionParams);
}
where the Javascript code is
function jsFunction(a,s)
{
//a - or a,b,c etc - here will be the "other" parameters
//s - will be the instance of the Java side Storage class passed above
//now you can do things like
s.writeToFile('fileName','fileData');
var fd = s.readFromFile('fileName');
s.dropFile('fileName');
...
}
Related
I'm developing my project with Blazor Server-side.
While I develop, I used javascript code to implement things that hard to implement by C#.
However, I'm facing something weird situation. (I guess it is problem for javascript)
Suppose there are 2 users(A, B). When 'A' user do some action that call javascript code, if 'B' user into same page, 'A' users action affects to 'B' user.
I implemented web page that have 3d scene with threejs. As I explained above, when User 'A' move some object with mouse event(mousemove, mousedown..), if User 'B' accesses the same page, 3d objects of B are moved to the location where User 'A' moved.
Originally, when user access to web page I developed, 3d objects's position should be 0,0,0.
My Guess
I don't use prototype or class(use variable and functions globally. I'm new to javascript.. )
Javascript runs on server-side(share resources??, If then, how can I solve it)
I'm guessing the javascript would be problem, but if you have any other opinions, would you please share?
Edited
I've solved this problem using DotNetObjectReference.Create(this);
C#
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
//send created instance to javascript
var dotNetObjRef = DotNetObjectReference.Create(this);
await JSRuntime.InvokeVoidAsync("SetObjectRef", dotNetObjRef);
}
await base.OnAfterRenderAsync(firstRender);
}
[JSInvokable]
public async Task enableSomething(bool bEnable)
{
var something = bEnable;
}
//== before edit
//[JSInvokable]
//public static async Task enableSomethingStatic(bool bEnable)
//{
// var something = bEnable;
//}
Javascript
var objectRef;
function SetObjectRef(ref) {
objectRef = ref;
}
//call c# function
objectRef.invokeMethodAsync("enableSomething", true);
It was problem of 'static' method as I guessed.
If you declare C# method called from javascript as 'static' and this method changes something of UI variable, this method can affect another users.
So I create instance of current page and send it javascript and when I need to call C# methods from javascript, I call methods using created instance.
Is there any problem or issue, please share it.
Sorry for my bad English.
JavaScript runs client side only. I don't see how two windows, let alone two users, would share data.
Almost for sure, the problem is that you are injecting a singleton service-- which means the server will use one instance for all users.
If so, you have two choices:
(1) add logic to your singleton service to incorporate users. (For example, a dictionary with UserID/Property name for key, and a column for Value)
(2) go to Startup.cs and change the suspect singleton service to .AddScoped(), which will create a new instance for each user.
For right now, I think the latter solution will solve your problem immediately. However, don't underestimate the value of Singletons-- they'll be very useful for other things.
I have a C# file that pulls various config settings from different config files. A JavaScript application I'm writing needs some of those settings to run. I'm having trouble bringing the C# variables into the js file. Not sure what is the best approach. They need to end up as a JS object on the page. Would adding them to a JSon object in the C# file & calling that from the JS file work? Not even sure how to do that to be honest. Any help appreciated.
You can just declare a global variable in one of your root pages (say, _layout.cshtml):
<script>
var settings = {
foo: #(IsFoo ? "true", "false"),
bar: #SomeNumber,
baz: "#ImportantString"
}
</script>
Or, if you're writing a Web API, you can just add a /settings endpoint you can query like so (Assuming you're using jQuery):
$.get("/settings", response => {
// Store the `response` in a global variable.
});
If it's an independent javascript project and .net is more of an API, you need to make an API call which will send you those config settings.
If your javascript is part of .net MVC application, i.e., you are adding your javascript through an tag at then end of a .cshtml/.aspx file, you can easily pass it through a global variable, but even then the C# code need to pass those values through Modal to the .cshtml/aspx file.
what you can do is:
var pageConfig = pageConfig || {};
pageconfig.settings = #Html.Raw(Model.Settings); // This model object is part of the C# code and my assumption is that Settings will have array of configurations.
This can also be done globally, depending upon how the C# code is written.
Assuming that when you say 'various config settings' you're referring to .NET's ApplicationSettings (defined in Visual Studio's Properties > Settings), we've done something similar, in a generic manner, as follows:
public void WriteSettings(TextWriter writer)
{
// Declare the nameSpace for the DLL you want to pull settings from
var nameSpace = "foo.Bar"
ApplicationSettingsBase properties = (System.Configuration.ApplicationSettingsBase)Activator.CreateInstance(nameSpace, string.Format("{0}.Properties.Settings", nameSpace)).Unwrap();
foreach (SettingsProperty property in properties.Properties)
{
writer.Write(string.Format("{0}=\"{1}\", property.Name, properties[property.Name]);
}
}
I've ignored a few issues in your case:
I've not bothered trying to deal with non-strings; consider looking at property.PropertyType and casting your values appropriately
I've wrapped this in a function that accepts a TextWriter; you can pass this method Response.Output
I am currently trying to port an existing Android application that we have to an IOS app. The issue here is that a large portion of our Android app was made using a lot of webviews with custom Javascript code called from Android.
If anyone is not familiar with it, the code for android would go something like this:
mWebView.addJavascriptInterface(WebInterface(this), "Android");
// more settings or whatever
mWebView.loadUrl("yourUrl.html.whatever");
Then the interface to the Javascript code reads like this:
public class WebInterface {
Context mContext;
public WebInterface(Context c) { this.mContext = c;}
#JavascriptInterface
public String returnMessage() {
Toast.makeText(mContext, "This is being called from the interface", Toast.LENGTH_SHORT).show();
return "This is being called from the interface";
}
}
At this point any time we want to access the code for the returnMessage() method inside of the webview we simply call:
var getContentsFromIntefrace = Android.returnMessage()
// getcontentsFromInterface is now "This is being called from the interface"
These targeted calls(Android.returnMessage()) allow me to call the interface code only when I need to do so, in contrast, if I was to use Android APIĀ“s evaluate javascript methods I would have to watch for the code executing all over the place e.g if my evaluatejavascript Android API method sets a variable to something it will do so in any other part of the code where said variable exist(please do correct me if I am wrong, it seems fuzzy at first but it is an issue thus far)
===== The issue I am having with IOS
The above is merely an explanation of what I need to do, to show what I am attempting on IOS I will demonstrate the code:
In IOS if I want to se functions that call on swift code inside my controller I have to set it in JS as:
function testButton() {
webkit.messageHandlers.callbackHandlerTestSend.postMessage({action: "testButtonAction", data: {name: "E.A.P"}});
}
The above sends a named message('callbackHandlerTestSend') with the key value pairs of action and data which can be parsed on the appropriate swift delegate as:
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if (message.name == "callbackHandlerTestSend") {
guard let body = message.body as? [String:Any] else {
print("Unable to do connection")
return
}
for(key, value) in body {
print("\(key) : \(value)")
if(key == "action"){
if(value as! String == "testButtonAction") {
intCounter += 1
print("The testButtonAction message action has been called")
webView.evaluateJavaScript("receiverP.innerHTML = 'It works \(intCounter)'", completionHandler: nil)
webView.evaluateJavaScript("receiverFunction('Message test')", completionHandler: nil)
// FIXME: evaluate JS might call on any other instance of receiverP, this is an issue
}
}
}
}// body of if
}// end of delegate
This is currently the only way in which I have been able to send Swift code back to the webview, by injecting the Javascript code with webView.evaluateJavascript()
My problem is that I need to capture everything with the whole:
webkit.messageHandlers.callbackHandlerTestSend.postMessage({action: "testButtonAction", data: {name: "E.A.P"}});
And it becomes bothersome to continue to do it as well as to have to individually parse each call for the proper parameters to be set while at the same time trying to not inject javascript code without messing something else up. So calling javascript and sending javascript code seems extremely cumbersome at this point and I would like to know if there is something I could do for the code to look more like the Android counterpart.
Any advice on what cool external libs out there I could use or in how could I modify the code to this would be greatly appreciated, I have been trying to find something in the IOS docs but have not found anything that I could use.
-- Edit:
At this time I am seriously considering an alternative such as React Native since it seems as the fastest route to develop an application that requires heavy usage of web like content as well as having limited knowledge of IOS in contrast to Android. I would still want to know if there is a viable solution to the above.
I am currently creating a plugin for the Bukkit-Server, but i have a problem using the Nashorn scripting engine. I am evaluating the external javascript file from my Java-Plugin. I cant get my javascript to import the classes from my plugin, only standard java classes are working (like var JavaBool = Java.type('java.lang.Boolean');, but not var Holder = Java.type('io.github.advtest1.js.JSHolder');)
Whenever I try to load one of these I get the following error:
Caused by: java.lang.ClassNotFoundException: io.github.advtest1.js.JSHolder
After a bit of researching i found that it has something to do with my plugin-classes beeing in the classpath, but how can i add it to the classpath when Bukkit itself loads the plugin and i don't want any other startoptions for the server then java -jar bukkit.jar?
Feel free to ask, if you need additional information.
Thanks in advance!
Nashorn uses the thread context loader found at the time of engine creation to find Java classes from Java.type API.
Nashorn also uses another loader if you use "-classpath" nashorn command line option. Nashorn tries to load classes using a fresh loader created during engine creation initialized with that classpath specified. Note that nashorn command line options can be passed via "nashorn.args" Java System property. So, if you specific -Dnashorn.args="-classpath your_path" elsewhere (say, in your config), then Nashorn can access classes from that your_path specified.
If cannot pass Nashorn engine options via System property [or via command line in the case "jjs" tool use], you can set thread context loader to be appropriate loader as suggested by an earlier answer.
If that is not desired because of other application dependencies, you get java.lang.Class object of the desired class and expose the same as variable to the script (from your Java code, you could get Class object and call ScriptEngine.put method). The script then can access "static" property on that to get 'type' object. With type object, usual "new", static method calls etc. work as expected.
Example:
import javax.script.*;
public class Main {
public static void main(String[] args) throws Exception {
ScriptEngineManager m = new ScriptEngineManager();
ScriptEngine e = m.getEngineByName("nashorn");
e.put("Vec", java.util.Vector.class); // replace it with any Class object
e.eval("var T = Vec.static; var obj = new T()"); // create new Vector
e.eval("print(obj.getClass())");
}
}
As #wero mentioned, I needed to add
ClassLoader cl = plugin.getClass().getClassLoader();
Thread.currentThread().setContextClassLoader(cl);
before I invoke the js-function from java.
plugin stands for the main class (the class which extends JavaPlugin)
I have a similar problem to the person in this post; I'm trying to extend the cefsimple.exe app included with the chromium embedded framework binaries to include a V8 handler. I implemented the OnContextCreated() method and made sure to extend RenderProcessHandler in the SimpleHandler class. I'm trying to implement a simple window bound variable called test_string; here's what my code looks like;
void SimpleHandler::OnContextCreated(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefV8Context> context)
{
CefRefPtr<CefV8Value> object = context->GetGlobal();
object->SetValue("test_string", CefV8Value::CreateString("this is a test"), V8_PROPERTY_ATTRIBUTE_NONE);
}
But the program never arrives at any breakpoints I add within the method, and the variable is undefined on any webpages I load within the app. I saw that one of the solutions in the other thread is to enable the settings.single_process flag, which i've done, but my code still doesn't reach the breakpoint.
To be clear, I'm accessing the variable on pages with window.test_string.
Make sure that you are sending that CefApp to CefExecuteProcess.
CefRefPtr<SimpleApp> app(new SimpleApp);
// CEF applications have multiple sub-processes (render, plugin, GPU, etc)
// that share the same executable. This function checks the command-line and,
// if this is a sub-process, executes the appropriate logic.
int exit_code = CefExecuteProcess(main_args, app, sandbox_info);
if (exit_code >= 0) {
// The sub-process has completed so return here.
return exit_code;
}
Found this solution here
Have you read through the General Usage guide? Some key points below
https://bitbucket.org/chromiumembedded/cef/wiki/GeneralUsage#markdown-header-cefapp
https://bitbucket.org/chromiumembedded/cef/wiki/GeneralUsage#markdown-header-processes
The single_process mode is not supported so I've never used it. In general I'd avoid it. The multi process architecture means you need to attach the debugger to the process. The Chromium guide is relevant to CEF in this instance.
https://www.chromium.org/developers/how-tos/debugging-on-windows#TOC-Attaching-to-the-renderer
you need to ensure your App is derived from CefRenderProcessHandler
not SimpleHandler!!!
class SimpleApp : public CefApp
, public CefRenderProcessHandler
{
virtual void OnContextCreated(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefV8Context> context) OVERRIDE;
valdemar-rudolfovich says you need to pass instance of SimpleApp in
CefExecuteProcess