Android calling JavaScript functions in WebView with API <= 18 - javascript

This question isn't new here, but still I cannot get it where I go wrong. I just want to evaluate some javascript.
My simple code looks like:
WebView wv = new WebView(context);
WebSettings settings = wv.getSettings();
settings.setJavaScriptEnabled(true);
JavaBridgeToJS bridgeToJS = new JavaBridgeToJS();
wv.addJavascriptInterface(bridgeToJS, "javaCallback");
String src = "javascript: var result= 3 + 3; window.javaCallback.resultOfAdd(result)";
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
Log.d(TAG, "Newer than JellyBean.");
ValueCallback<String> result = new ValueCallback<String>() {
#Override
public void onReceiveValue(String value) {
Log.d(TAG, "Got in onReceiveValue: " + value);
}
};
wv.evaluateJavascript(src, result);
}else {
Log.d(TAG, "Older than Kitkat");
wv.loadUrl(src);
}
and
public class JavaBridgeToJS {
#JavascriptInterface
public void resultOfAdd(String result) {
Log.d(TAG, "Here is: " + result);
}
}
It is working good for API 19+, even without if with checking of version (I mean that it works with loadUrl as well as with evaluateJavascript)
But when I try API 18, the callback in java is not called.
I have looked around (remove line break, or quotes missing (possibly infected, watched on phone), and so on...)
So far I have no luck. Any idea?
EDIT
Well, I have added code for handling errors inside of JS in case, that I have missed something.
wv.setWebChromeClient(new CustomWebChromeClient());
as
public class CustomWebChromeClient extends WebChromeClient {
private CustomWebChromeClient() {
super();
}
#Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
UILog.d(TAG, consoleMessage.message());
return super.onConsoleMessage(consoleMessage);
}
}
and I was suprised with:
E/dalvikvm: Could not find class 'android.webkit.PermissionRequest', referenced from method com.example.WebViewDoll$CustomWebChromeClient.access$super
VFY: unable to resolve check-cast 1896 (Landroid/webkit/PermissionRequest;) in Lcom/example/WebViewDoll$CustomWebChromeClient;
E/dalvikvm: Could not find class 'android.webkit.WebChromeClient$FileChooserParams', referenced from method com.example.WebViewDoll$CustomWebChromeClient.access$super
VFY: unable to resolve check-cast 1899 (Landroid/webkit/WebChromeClient$FileChooserParams;) in Lcom/example/WebViewDoll$CustomWebChromeClient;
E/dalvikvm: Could not find class 'android.webkit.PermissionRequest', referenced from method com.example.WebViewDoll$CustomWebChromeClient.access$super
D/WebViewDoll: Uncaught TypeError: Cannot call method 'resultOfAdd' of undefined
E/Web Console: Uncaught TypeError: Cannot call method 'resultOfAdd' of undefined at null:1

I did not find an answer why this is not working, but with help of this using js in webview I was able to make a workaround ...
I have to inject my js into a html page. So my wrapper arround js looks like:
String src = "<html><head><script type=\"text/javascript\">" +
"function myFunction()" +
"{" +
" javaCallback.resultOfAdd(3+3);" +
"}" +
"</script></head><body onload=\"myFunction()\"/>" +
"</html>";
and instead of wv.loadUrl I have used:
wv.loadData(src, "text/html", "UTF-8");
This combination have same result as needed.

Related

What is the correct url to access an file via AJAX?

To contextualize:
There is a remote directory, clearing-dit\logs, which has a series of logs (portal.log, test.log, ...). This directory is mapped to an HTML page, where all your .log's are displayed. Once one of them is clicked, its respective content is displayed.
Exemple
Currently, I'm using thymeleaf to show the content:
<html xmlns:th="http://www.thymeleaf.org" th:include="layout :: page">
...
<div ...>
<p th: utext = "$ {log.content}"> Log content </ p>
</div>
The problem is that this content is displayed in a static way, and I need it to be continue shown as the file is getting updates. I went searching and saw that I can/need to do it through an AJAX, but the concept of AJAX is quite vague to me.
Currently, I'm trying to do it in a very simple way:
$.ajax({
url : "/log",
type : "post",
success : function(data) {
document.getElementById('content').innerHTML = data;
}
});
And (to set the log content):
#RequestMapping(value = "/log", method = RequestMethod.POST)
public String logContent(#Valid Log log, BindingResult bindingResult, Map<String, Object> model) {
if (log.getInitLine() == 0 && log.getFinalLine() == 0) {
try {
fileNumberLines(log);
log.setContent(getLogContentByRange(0, log.getInitLine(), log.getFinalLine(), logsDir + "/" + log.getFilename()));
} catch (IOException e) {
logger.error(e.getMessage());
}
} else {
log.setContent(getLogContentByRange(0, log.getInitLine(), log.getFinalLine(), logsDir + "/" + log.getFilename()));
}
model.put("path", logsDir);
model.put("log", log);
model.put("currentPage", "logs");
model.put("root", root);
return "log";
}
But instead of the contents of the file, I'm getting the page itself.
Return AJAX call
What makes sense, since I'm passing the url of the page itself. So, my question is: How do I access log content through the url? What is the correct url?
You should not return the name of the view from the Controller method if you are using it for AJAX. You need to return data (an Object) from the method. You will need the data to be converted to JSON (which can be used in Javascript) so you need to mark the method with #ResponseBody. You will also need jackson-databind on the classpath (com.fasterxml.jackson.core jackson-databind) for the JSON conversion.
#RequestMapping(value = "/log", method = RequestMethod.POST)
#ResponseBody
public String logContent(#Valid Log log, BindingResult bindingResult, Map<String, Object> model) {
if (log.getInitLine() == 0 && log.getFinalLine() == 0) {
try {
fileNumberLines(log);
log.setContent(getLogContentByRange(0, log.getInitLine(), log.getFinalLine(), logsDir + "/" + log.getFilename()));
} catch (IOException e) {
logger.error(e.getMessage());
}
} else {
log.setContent(getLogContentByRange(0, log.getInitLine(), log.getFinalLine(), logsDir + "/" + log.getFilename()));
}
model.put("path", logsDir);
model.put("log", log);
model.put("currentPage", "logs");
model.put("root", root);
return log;//return an Object containing data (or your own Value Object)
}
You're getting the file itself, because you're returning a view name from the method. So, Spring doesn't know if you're asking it to return only the data. One way to get this done is to use #ResponseBody annotation.
This allows you to send any arbitrary data to the client. So, you may want to change your method to something like this:
#RequestMapping(value = "/log", method = RequestMethod.POST)
#ResponseBody
public String logContent(#Valid Log log, BindingResult bindingResult, Map<String, Object> model) {
// Code truncated for brevity
return log;
}
What we're doing here is instructing Spring to return the contents of log object directly to the client, instead of rendering a view. Hope this helps.

Asynchronous JSON String loading JavaScript

I'm using the d3 force directed graph to display some data I get from an API. Before I can display it, it runs through a java class, which does write it into the right json format.
Since the programm runs in a JavaFX WebView I have a bridge class, that does have a getter method I can call from the JavaScript.
In my Main class I create a WebView and assign the bridge to it. I initialize my JSON translator and pass the bridge to it.
#Override
public void start(Stage stage) {
try {
new JsonTranslator(individual, depth, bridge);
Scene scene = createScene();
[...]
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
private Scene createScene() {
[...]
try {
JSObject jsobj = (JSObject) webEngine.executeScript("window");
jsobj.setMember("java", bridge);
} catch (Exception e) {
e.printStackTrace();
}
[...]
}
In my JSONTranslator class I write the json and pass it to the bridge
private void writeFile() {
try {
bridge.setJSONObject(obj.toJSONString());
FileWriter file = new FileWriter(
"C://path/to/some/file.json"
file.write(obj.toJSONString());
file.flush();
file.close();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("Object:" + obj);
}
I also write it to a file. The data gets printed as expected. Now in my bridge the data is available throughout the getter / setter. In JSToFXBridge.java:
public String getJSONObject(){
System.out.println("get request: " + json);
return json;
}
public void setJSONObject(String string){
this.json = string;
}
Now I call it from my JavaScript
[...]
var draw = function(json, callback) {
[...]
callback.call(data);
};
var data = java.getJSONObject();
draw(data);
However it does print get request: -my json data- on the console, the json string is compleatly fine. If I copy & paste it from the console to be like this var data = -my json data- in the code it works. Only to asign it directly from the method won't work. I can't figure out why since I try to load it asynchronously. Based on this tutorial. Do I make a mistake in laoding the string? Or is it even a wrong way to do so?
Good answer / tutorial to asynchronous JavaScript callbacks can be found here. Solution, which created a new problem [ solved as well ], provided here.
In general think of this pattern:
function addOne(thenRunThisFunction) {
waitAMinuteAsync(function waitedAMinute() {
thenRunThisFunction()
})
}
addOne(function thisGetsRunAfterAddOneFinishes(){})
Explains it very well

Transfer large data from Android Activity to WebView Javascript

I know there were a lot of related posts, but non of them contains clear answer how it would be possible to transfer huge data from Android to JS or vice versa. The problem is when I try to do that, on the JS side the string is cut and it is not complete. After this line I get only part of the string on Javascript side
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
webView.evaluateJavascript("javascript: " + jsToExecute, null);
} else {
webView.loadUrl("javascript: " + jsToExecute);
}
What could be a possible solution for this?
Thanks in advance for the answers or for the ideas.
You may try to save the data into a temporary file and then read the data using File plugin. But not sure if this have any limitations.
Or you can create own JS function that will return the data:
//this is an Activity
this.appView.addJavascriptInterface(new MyJsHandler(this), "myHandler");
private class MyJsHandler {
private CordovaActivity activity;
public MyJsHandler(CordovaActivity activity) {
this.activity = activity;
}
#SuppressWarnings("unused")
#JavascriptInterface
public String getData() {
if (this.activity.data) {
return this.activity.data;
}
return "";
}
}
In JavaScript:
var data = window.myHandler.getData();

android-javascript: using prototype to object injected in webview by add javascriptinterface

Im using addJavaScriptInterface to inject my object "myObj" into webview.
Here part of code
private void portalInit() {
CookieSyncManager.createInstance(this);
CookieSyncManager.getInstance().startSync();
wv.getSettings().setJavaScriptEnabled(true);
wv.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
wv.getSettings().setAllowContentAccess(true);
wv.setWebChromeClient(new WebChromeClient() {
#Override
public boolean onConsoleMessage(ConsoleMessage cm) {
Log.d("From JavaScript",
cm.message() + " -- From line " + cm.lineNumber()
+ " of " + cm.sourceId());
return true;
}
});
wv.setWebViewClient(new WebViewClient() {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
});
wv.addJavascriptInterface(new myObj(this, wv), "myOBJ");
wv.loadUrl("http://dl.dropbox.com/u/16515769/gradient.html");
wv.requestFocus(View.FOCUS_DOWN);
}
And i have HTML page that i can't change, where onload doing some crazy stuff
<script>
function f1(){
this.do1 = function(par){
console.log("1: "+par);
};
this.do2 = function(num){
console.log("2: "+num);
};
}
function f2(){
this.do3 = function(par){
console.log("3: "+par);
};
this.do4 = function(num){
console.log("4: "+num);
};
}
var myobj;
var myobj_e = myOBJ;
f1.prototype = myobj_e;
f2.prototype = new f1();
myobj = new f2();
function keydown(e){
var key = e.keyCode || e.which;
switch(key){
case 13:
myobj.Debug("turn it on!!!!");
break;
case 27:
myobj.do1("turn it on!!!!");
break;
case 49:
myobj.do4("fsdfd")
break;
}
}
this constuction work fine in our hardware device where object injected to real webkit browser, but not work in android webview, and accesing to myobj.Debug post error in logcat (Javascript error)
Uncaught ReferenceError: NPMethod called on non-NPObject -- From line 36 of http://dl.dropbox.com/u/16515769/gradient.html
Note: if i call in javascript myOBJ.Debug("something"); it work fine, so i think that part "f1.prototype = myobj_e;" not working.
I will be very glad to get some help.
So i find a workaround. Its not a solution, but i cant do it in other way.
We can not change object that was added to webview by addJavascriptInterface in webpage side.
So I need to make my own object, which will retranslate calls to interface. I injected javascript onPageStarted and changeed object name to myOBJ_tmp
wv.setWebViewClient(new WebViewClient() {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
#Override
public void onPageStarted (WebView view, String url, Bitmap favicon){
String script= "javascript:var func_list = ['Debug'];";
script+="var myOBJ = {};"
script+="func_list.map(function(id){"
script+="myOBJ[id]= function() {"
script+="try{return myOBJ_tmp[id].apply(myOBJ_tmp, arguments);}"
script+="catch(e) { console.log('ERROR: ' + e + ', method ' + id);"
script+="return false;}}})"
view.loadUrl(script);
}
});
Hope this will help somebody.

Wicket 6.2 AbstractDefaultAjaxBehavior getCallbackUrl no longer resolves JS variables

Recently I have been working on upgrading a big web application that was using wicket 1.4.18 to 6.2. We had a situation where we would create javascript variables to keep track of positioning within a drag and drop list. This is just the wicket side of the code since the js has always worked and has not been changed.
ListItem.add(new AbstractDefaultAjaxBehavior()
{
private static final long serialVersionUID = 1L;
#Override
public void onComponentTag(ComponentTag tag)
{
tag.put("ondrop", "var value = $(ui.item[0]).attr('hiddenvalue');"
+ this.getCallbackScript());
}
#Override
public final CharSequence getCallbackUrl()
{
return super.getCallbackUrl() + "&hiddenvalue' + value + '";
}
}
However the problem I am running into is the javascript variables are not resolving to values and are now being taken as literal strings (Ex: 'value' instead of 5) in the getCallbackUrl. This was not the case in wicket 1.4.18 and I don't believe this problem originated in our migration to 1.5.8.
In the end we just want to be able to pull the value out using
#Override
protected void respond(AjaxRequestTarget target)
{
getRequest().getRequestParameters().getParameterValue("hiddenvalue");
}
Any advice on this? I hope I have provided enough information.
Thanks in advance for any help. Some of this is a little beyond my knowledge and can be intimidating not knowing where to look.
Wicket Ajax has been completely rewritten for Wicket 6. See this page for a detailed description.
In your case, you should use the new AjaxRequestAttributes like that:
#Override
protected void updateAjaxAttributes(final AjaxRequestAttributes attributes) {
super.updateAjaxAttributes(attributes);
attributes.getExtraParameters().put("hiddenvalue", "value");
}
Retrieval of the value from the request still works the same as before.
#Override
protected void respond(AjaxRequestTarget target)
{
getRequest().getRequestParameters().getParameterValue("hiddenvalue");
}
Another cleaner approach is to use the callback function
AbstractDefaultAjaxBehavior ajaxBehavior = new AbstractDefaultAjaxBehavior() {
#Override
protected void respond(AjaxRequestTarget target) {
String param1Value = getRequest().getRequestParameters().getParameterValue(AJAX_PARAM1_NAME).toString();
String param2Value = getRequest().getRequestParameters().getParameterValue(AJAX_PARAM2_NAME).toString();
System.out.println("Param 1:" + param1Value + "Param 2:" + param2Value);
}
#Override
public void renderHead(Component component, IHeaderResponse response) {
super.renderHead(component, response);
String callBackScript = getCallbackFunction(CallbackParameter.explicit(AJAX_PARAM1_NAME), CallbackParameter.explicit(AJAX_PARAM2_NAME)).toString();
callBackScript = "sendToServer="+callBackScript+";";
response.render(OnDomReadyHeaderItem.forScript(callBackScript));
}
};
add(ajaxBehavior);
Define a variable for the function in your javascript
var sendToServer;
It will be initialized on dom ready event by wicket with the callback function
Call sendToServer(x,y) from javascript to pass the parameters to the server.
private static final String MY_PARAM = "myparam";
public static class SampleCallbackBehavior extends AbstractDefaultAjaxBehavior {
#Override
public void renderHead(Component component, IHeaderResponse response) {
super.renderHead(component, response);
response.render(OnDomReadyHeaderItem.forScript("var myfunction : " + getCallbackFunction(CallbackParameter.explicit(MY_PARAM))));
}
#Override
protected void respond(AjaxRequestTarget target) {
StringValue paramValue = getComponent().getRequest().getRequestParameters().getParameterValue(MY_PARAM);
//TODO handle callback
}
}
After this, you should only call the function from javascript
myfunction("paramValue");

Categories

Resources