Passing a Function as a String in JavaScript - java

Apologies if this is a bit of a weird one...
I have a program written in Java which utilises the ScriptEngine to process user provided JavaScript to extend my application. However, this specific question is related to general JavaScript as opposed to Java or it's ScriptEngine, but I am just explaining this to set the context.
I have a function which returns a string when called - let's call it a() as defined below:
var a = function() {
return "this is a";
};
When the user calls this function using a() it works fine and outputs "this is a". However, if the user forgets to include the parenthesis then it outputs my actual function definition - expecting this as I am no longer calling the function.
To catch this I have redefined the toString method of my Object to the following:
a.toString = function() {
return a();
};
This works fine when I use a in a string context as it calls the toString method implicitly, but if I attempt to pass it to a function then it doesn't call toString and I am left with a sun.org.mozilla.javascript.internal.InterpretedFunction.
I have looked at the other Function.prototype methods (i.e. apply, bind, constructor, etc) to try and override the method which is called as the function is passed to another function but none of them fitted the bill. I am basically looking for a way of converting a Function to a string type object whenever it is used without the parenthesis - i.e a === a(). For people who might ask why don't I define a as a string to start with, my function returns a string constructed from other information the user has provided.
Maybe the solution is to make my users write syntactically correct JavaScript, but my users are far from programmers. I could also add some form of pre-parsing which checks for missing parenthesis and adds them in dynamically before I execute it using the ScriptEngine. However, although both of these options will work, I am looking for an easier way.

Neither a.toString nor a.prototype.toString will allow you to forget the parenthesis. .toString allow you to do:
var a = function() {
return "this is a";
};
a.prototype.toString = function () {
return "something";
};
var A = new a;
alert(A + ''); // something
A.toString = function () {
return "something else";
};
alert(A + ''); // something else
You shouldn't want in your code both a() and a return the same thing, this looks like a very bad idea.
An option to get myObject.myVar return a custom dynamic string is defineGetter https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Object/defineGetter

Related

In Karate, what is the advantage of wrapping a Java function in a JavaScript function?

I can wrap a Java function like this:
* def myJavaMethod =
"""
function() {
var Utils = Java.type('Utils');
// use Number type in constructor
var obj = new Utils(...);
return obj.myJavaMethod();
}
"""
But why would I? I can use Java functions straight in the test scenarios, like this:
Scenario: Test exec and error value
* def Utils = Java.type('Utils');
* def ret = Utils.exec('echo "From Java" > /home/richard//karate-test/karate-0.9.6/out.txt');
* match read('out.txt') == "From Java\n";
* match Utils.exec('exit 123') == 123
Note: exec is a static method that uses a shell to execute the command given.
At least I tested this for static methods and that works fine without the JavaScript detour.
It seems that the JavaScript wrapper only adds an extra layer of complication.
Besides that, with the 'call' syntax I can only pass one parameter (that admittedly can be an entire object or array).
But, I can pass parameters straight to a Java function (using normal syntax) and even use the result in a match.
(I assume parameters and results are implicitly converted to a JavaScript value, that could be a JSON object or array).
So far, I miss to see the advantage of explicitly wrapping Java code inside JavaScript wrappers. I assume the problem is me, that is: I am missing some important point here?
The only advantage is to reduce typing and when you have a lot of code re-use. For example, instead of:
* def foo = java.util.UUID.randomUUID() + ''
You define it once:
* def uuid = function(){ return java.util.UUID.randomUUID() + '' }
And then call it wherever, and it is super-concise:
* def json = { foo: '#(uuid())' }

adding function to jsonjava object and calling it from xpages control

I am trying to add a function to a JSONJavaObject and calling it from a control on an xpage.
so far I have:
json = (JsonJavaObject) JsonParser.fromJson(factory, colJson);
String func = "function () { alert('you clicked?'); }";
json.put("onClick", new JsonReference(func) );
In the first line I add key-value pairs from a column in a Notes view.
In the second line I define the function as a string.
In the last line I place the converted string as function in the jsonjava object.
I read about this in the following blog post:
http://camerongregor.com/2016/01/19/doublequoteavoidance/
In the next step I bind the function to e.g. a button control as followed:
<xp:button value="Label" id="button1">
<xp:eventHandler event="onclick" submit="false">
<xp:this.script><![CDATA[obj.onClick]]></xp:this.script>
</xp:eventHandler>
</xp:button>
obj is the respresentation of the JSONJava object in SSJS.
But without success. Anyone know how I can call the function in the object?
I hope I will make sense here, let me know if anything to clarify.
If you are simply trying to dynamically output the client side script of a button event, then you don't need to use JsonReference at all. You can just use a String.
In my blog article I might not have make it clear why I needed to use JsonReference. I was using it in the process of rendering a custom UIComponent, part of this process required generating a Json object client side. To do this I created the JsonJavaObject as you did and then asked it to be turned into a string with the 'toJson' method. My problem was that when I asked the whole object to become a string, every property of that object that was a String, would begin and end with a double quote. I needed to ensure that the properties which were intended to be functions did not begin and end with "". By using the JsonReference the JsonGenerator became aware of my intention not to include these double quotes.
In your case, it looks as though you are just trying to dynamically determine what happens with onClick. To do this you could simply use a String instead of the JsonReference. The inclusion of the 'function() {}' is unnecessary as this will be generated when the event handler is rendered at the end of the page.
For Example here would be the Json Java Object
JsonJavaObject obj = new JsonJavaObject();
String func = " alert('you clicked?'); ";
obj.put("onClick", func);
return obj;
And here would be the button:
<xp:button id="button1" value="Alert Me">
<xp:eventHandler event="onclick" submit="false"
script="#{javascript: myBean.myObject.get('onClick')}">
</xp:eventHandler>
</xp:button>
This should give you the end result of seeing 'you clicked?' alert.
You can also inspect how this has all been generated in the script block near the end of the page using 'view Source' or your favourite web browser developer tools:
function view__id1__id2_clientSide_onclick(thisEvent) {
alert('you clicked?');
}
XSP.addOnLoad(function() {
XSP.attachEvent("view:_id1:_id2", "view:_id1:button1", "onclick",
view__id1__id2_clientSide_onclick, false, 2);
});
Let me know if anything isn't clear, hope it helps!
Does obj.onClick already give you a handle to the function returned by the Java class? If it does then you should be able to call it using the call or apply methods that are available in JavaScript:
obj.onClick.call();
obj.onClick.apply();
More details about those two methods can be found here: What is the difference between call and apply?

Redirect with anchor in play 2

I'm looking for possibility to add anchor to url returned in controller:
public static Result open(String id) {
// here I want to add acnhor like #foo to the produced url
return redirect(routes.MyPage.show(id));
}
I found that it was possible in play 1 using addRef method, but I couldn't find any replacement of the method in play 2.
Of course I can use concatenation like:
public static Result open(String id) {
// here I want to add acnhor like #foo to the produced url
return redirect(routes.MyPage.show(id).url + "#foo");
}
But it seems ugly.
Thank you for any help! Have a good day!
Before trying to answer that question.
I should recommend you change whatever behavior you're currently setting.
Because, an URL fragment's purpose is client side only. Such fragment is never sent to the server, so that it's cumbersome to do the opposite.
However, here is the embryo of a (quite?) elegant solution that you could follow.
What I'll try to do is to leave the browser deal with the fragment, in order to keep potential behaviors (f.i. go to ID or even deal with history...).
To do so, you could add an implicit parameter to your main template which will define the fragment that the URL should have:
#(title: String)(content: Html)(urlFragment:Option[UrlFragment] = None)
As you can see I wrapped the parameter in an Option and default'ed it to None (in order to avoid AMAP pollution).
Also, it simply wraps a String but you could use String alone -- using a dedicated type will enforce the semantic. Here is the definition:
case class UrlFragment(hash:String)
Very simple.
Now here is how to tell the browser to deal with it. Right before the end of the head element, and the start of body, just add the following:
#urlFragment.map { f =>
<script>
$(function() {
//after everything is ready, so that other mechanism will be able to use the change hash event...
document.location.hash = "#Html(#f.hash)";
});
</script>
}
As you can see, using map (that is when the urlFragment is not None) we add a script block that will set the hash available in urlFragment.
This might be a start, however... Think about another solution for the whole scenario.
As of Play 2.4, it's possible to use Call.withFragment().
routes.Application.index.withFragment("some-id").absoluteURL(request)
This was added by PR #4152.

mockito callbacks and getting argument values

I'm not having any luck getting Mockito to capture function argument values! I am mocking a search engine index and instead of building an index, I'm just using a hash.
// Fake index for solr
Hashmap<Integer,Document> fakeIndex;
// Add a document 666 to the fakeIndex
SolrIndexReader reader = Mockito.mock(SolrIndexReader.class);
// Give the reader access to the fake index
Mockito.when(reader.document(666)).thenReturn(document(fakeIndex(666))
I can't use arbitrary arguments because I'm testing the results of queries (ie which documents they return). Likewise, I don't want to specify a specific value for and have a line for each document!
Mockito.when(reader.document(0)).thenReturn(document(fakeIndex(0))
Mockito.when(reader.document(1)).thenReturn(document(fakeIndex(1))
....
Mockito.when(reader.document(n)).thenReturn(document(fakeIndex(n))
I looked at the callbacks section on the Using Mockito page. Unfortunately, it isn't Java and I couldn't get my own interpretation of that to work in Java.
EDIT (for clarification):
How do I get get Mockito to capture an argument X and pass it into my function? I want the exact value (or ref) of X passed to the function.
I do not want to enumerate all cases, and arbitrary argument won't work because I'm testing for different results for different queries.
The Mockito page says
val mockedList = mock[List[String]]
mockedList.get(anyInt) answers { i => "The parameter is " + i.toString }
That's not java, and I don't know how to translate into java or pass whatever happened into a function.
I've never used Mockito, but want to learn, so here goes. If someone less clueless than me answers, try their answer first!
Mockito.when(reader.document(anyInt())).thenAnswer(new Answer() {
public Object answer(InvocationOnMock invocation) {
Object[] args = invocation.getArguments();
Object mock = invocation.getMock();
return document(fakeIndex((int)(Integer)args[0]));
}
});
Check out ArgumentCaptors:
https://site.mockito.org/javadoc/current/org/mockito/ArgumentCaptor.html
ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
Mockito.when(reader.document(argument.capture())).thenAnswer(
new Answer() {
Object answer(InvocationOnMock invocation) {
return document(argument.getValue());
}
});
You might want to use verify() in combination with the ArgumentCaptor to assure execution in the test and the ArgumentCaptor to evaluate the arguments:
ArgumentCaptor<Document> argument = ArgumentCaptor.forClass(Document.class);
verify(reader).document(argument.capture());
assertEquals(*expected value here*, argument.getValue());
The argument's value is obviously accessible via the argument.getValue() for further manipulation / checking or whatever you wish to do.
With Java 8, this could be something like this:
Mockito.when(reader.document(anyInt())).thenAnswer(
(InvocationOnMock invocation) -> document(invocation.getArguments()[0]));
I am assuming that document is a map.

Calling JS from an applet works in Firefox & Chrome but not Safari

I have the following code in an applet to call some Javascript (it's a bit convoluted because the fn that's called gets an object from the DOM identified by divId and calls a function on it).
#Override
public final void start() {
System.err.println("start() method called");
this.javascript = JSObject.getWindow(this);
this.jsObjectDivId = getParameter("parent_div_id");
this.initCallbackFnName = getParameter("init_callback");
Object args[] = {this.jsObjectDivId, this.initCallbackFnName};
System.out.print("Calling init_callback\n");
this.javascript.call("callJS", args);
}
The callJS function is:
window.callJS = function(divId, functionName, jsonArgString) {
var args, obj;
obj = $(divId).data('neatObject');
args = eval(jsonArgString);
return obj[functionName](args);
};
In Firefox/Chrome the divId and functionName arguments contain valid strings and everything works fine; the desired function is called on the object hanging off the specified DIV data.
In Safari, the divId and functionName arguments are both reported as a JavaRuntimeObject with values of true.
> divId
JavaRuntimeObject
true
What gives?
LiveConnect is not fully supported in all browsers. Especially, Safari doesn't convert Java Strings to the prober JS equivalent when using call. In your case you can just use eval at the Applet side instead of call and put in a JSON string object with the arguments. Something like:
javascript.eval(callback + "({\"id\":\"" + id + "\",\" ... })")
Basically, you need to know the cross-browser compatible subset of LiveConnect that works.
I've written a blog post that describes the subset: http://blog.aarhusworks.com/applets-missing-information-about-liveconnect-and-deployment/
It comes with a LiveConnect test suite which runs in the browser: http://www.jdams.org/live-connect-test
I had a similar issue with calling a method on an applet in Safari. It was returning a JavaRuntimeObject that I caused an exception when it was user later on.
As pointed out by #edoloughlin I had to use (applet.getMethod() + "") after which point the proper string was evaluated.
The comment saved me a bunch of time so I thought it useful to add here as I can't comment above.

Categories