Transfer large data from Android Activity to WebView Javascript - 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();

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.

AngularJS 400 Bad request

I'm trying to implement some post functionality in my app.
I have got the following post method:
restrictLoginAttemptsFromSingleIp: function (id, userId) {
var serviceUri = baseServicesUrlService.getBaseServicesUrl() + "/employee-service/restrict-single-ip";
return $http.post(serviceUri, {restrictLoginAttemptIp: {loginAttemptIds: [id]}, dataOwnerId: userId});
}
My server side is using RESTEasy 3.0.4 with Hibernate validation:
#POST
#Path("/restrict-single-ip")
public Response RestrictSingleIp(#Valid RestrictLoginAttemptIpRequest requestData, #Context HttpRequest request){
return Response.status(200).build();
}
The RestrictLoginAttemptIpRequest class inherits one field (dataOwnerId) of type Long from PostBase:
public class RestrictLoginAttemptIpRequest extends PostBase {
private RestrictLoginAttemptIp restrictLoginAttemptIp;
public RestrictLoginAttemptIp getRestrictLoginAttemptIp() {
return restrictLoginAttemptIp;
}
public void setRestrictLoginAttemptIp(RestrictLoginAttemptIp restrictLoginAttemptIp) {
this.restrictLoginAttemptIp = restrictLoginAttemptIp;
}
}
The RestrictLoginAttemptIp class:
package blah;
import org.hibernate.validator.constraints.NotEmpty;
import java.util.List;
public class RestrictLoginAttemptIp {
#NotEmpty(message = "blah")
private List<Long> loginAttemptIds;
public List<Long> getLoginAttemptIds() {
return loginAttemptIds;
}
public void setLoginAttemptIds(List<Long> loginAttemptIds) {
this.loginAttemptIds = loginAttemptIds;
}
}
I get the following data string from the POST request which seems to be ok:
{restrictLoginAttemptIp={loginAttemptIds=[328]}, dataOwnerId=8}
Can someone please explain me why I get an 400 Bad request error when I invoke that function?
Is this because of Long datatypes? Should I somehow mark them in Javascript to be Longs?
Ok after 4 hours I figured out the problem.
The case is, that I'm reading the POST data (solving permission questions) in a security interceptor. Reading POST data in RESTEasy is a little bit tricky. To create a LinkedHashMap I use Apache IOUtils (https://commons.apache.org/proper/commons-io/javadocs/api-release/org/apache/commons/io/IOUtils.html) like it is figured out in the next code snippet
String result = IOUtils.toString(requestContext.getEntityStream());
ObjectMapper mapper = new ObjectMapper();
Object obj = mapper.readValue(result, Object.class);
I looked up in my AngularJS interceptor (used for example for putting something in the header of every request) and figured out, that the server cannot read the input stream: java.io.ioexception no content to map to object due to end of input.
At the end the problem was, that after I once read the EntityStream of the ContainerRequestContext it became empty. The solution was to repopulate it after reading POST data. Something like this:
private LinkedHashMap getPostData(ContainerRequestContext requestContext) {
Object obj = null;
try {
String result = IOUtils.toString(requestContext.getEntityStream());
ObjectMapper mapper = new ObjectMapper();
obj = mapper.readValue(result, Object.class);
//IMPORTANT: After you can get the entity stream only once. After reading the entity stream is empty
//so the JSON parser cannot convert EMPTY entity stream into any object. To avoid strange errors (like 400 Bad Request)
//you have to convert the string back to input stream and rewrite the empty entity stream.
InputStream stream = IOUtils.toInputStream(result);
requestContext.setEntityStream(stream);
System.out.println(obj);
} catch (IOException e) {
e.printStackTrace();
}
return (LinkedHashMap) obj;
}
P. S. ObjectMapper comes from Jackson

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

Android Phonegap - Passing JSONObject to javascript

I'm trying to pass some information from an Android native class to the javascript.
I'm taking a bundle, converts it to JSONObject and passing the string representation of it.
But when trying to parse it in the JS, it fails.
This is what I do:
JSONObject jsonObject = new JSONObject();
for (String key : bundle.keySet()) {
Object value = bundle.get(key);
try {
jsonObject.put(key, value.toString());
} catch (JSONException e) {
// Do nothing
}
}
final String jsStatement = String.format(
"window.doSomething('%s');", jsonObject.toString());
cordova.getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
webView.loadUrl("javascript:" + jsStatement);
}
});
Can you tell me why it's not being parsed in the JS and how can I solve it?
Look into the Javascript Interface annotation which can be used to pass in / access the values from Java directly

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