I have a webview that I display some html texts (I have them in assets). I'd like to allow users to highlight some parts of it.
I was thinking in some solutions:
try to put the texts user hightlight in a shared pref and use:
webview.findAllAsync(shared_pref_string);
webview.setFindListener(new FindListener() {
#Override
public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting) {
// try to select the texts.
}
});
The problem I see is, user can select one word, like "what", and this code will select all "whats" the text has.
Use javascript:
public static String Highlightscript = " <script language="javascript">" +
"function highlightSelection(){" +
"var userSelection = window.getSelection();" +
"for(var i = 0; i < userSelection.rangeCount; i++)"
+ " highlightRange(userSelection.getRangeAt(i));" +
"}" +
"function highlightRange(range){"+
"span = document.createElement(\"span\");"+
"span.appendChild(range.extractContents());"+
"span.setAttribute(\"style\",\"display:block;background:#ffc570;\");"+
"range.insertNode(span);}"+
"</script> ";
webView.loadUrl("javascript:highlightSelection()");
But this 2 solutions not seems nice to me, any other best way to do this and more modern?
this android library is implemented what you need:
https://github.com/FolioReader/FolioReader-Android
they are using this javascript library https://github.com/timdown/rangy, maybe this will make sense.
Related
HTML
<div id='one'>
<button id='two'>I am a button</button>
<button id='three'>I am a button</button>
I am a div
</div>
Code
driver.findElement(By.id('one')).getText();
I've seen this question pop up a few times in the last maybe year or so and I've wanted to try writing this function... so here you go. It takes the parent element and removes each child's textContent until what remains is the textNode. I've tested this on your HTML and it works.
/**
* Takes a parent element and strips out the textContent of all child elements and returns textNode content only
*
* #param e
* the parent element
* #return the text from the child textNodes
*/
public static String getTextNode(WebElement e)
{
String text = e.getText().trim();
List<WebElement> children = e.findElements(By.xpath("./*"));
for (WebElement child : children)
{
text = text.replaceFirst(child.getText(), "").trim();
}
return text;
}
and you call it
System.out.println(getTextNode(driver.findElement(By.id("one"))));
Warning: the initial solution (deep below) won't workI opened an enhancement request: 2840 against the Selenium WebDrive and another one against the W3C WebDrive specification - the more votes, the sooner they'll get enough attention (one can hope). Until then, the solution suggested by #shivansh in the other answer (execution of a JavaScript via Selenium) remains the only alternative. Here's the Java adaptation of that solution (collects all text nodes, discards all that are whitespace only, separates the remaining by \t):
WebElement e=driver.findElement(By.xpath("//*[#id='one']"));
if(driver instanceof JavascriptExecutor) {
String jswalker=
"var tw = document.createTreeWalker("
+ "arguments[0],"
+ "NodeFilter.SHOW_TEXT,"
+ "{ acceptNode: function(node) { return NodeFilter.FILTER_ACCEPT;} },"
+ "false"
+ ");"
+ "var ret=null;"
+ "while(tw.nextNode()){"
+ "var t=tw.currentNode.wholeText.trim();"
+ "if(t.length>0){" // skip over all-white text values
+ "ret=(ret ? ret+'\t'+t : t);" // if many, tab-separate them
+ "}"
+ "}"
+ "return ret;" // will return null if no non-empty text nodes are found
;
Object val=((JavascriptExecutor) driver).executeScript(jswalker, e);
// ---- Pass the context node here ------------------------------^
String textNodesTabSeparated=(null!=val ? val.toString() : null);
// ----^ --- this is the result you want
}
References:
TreeWalker - supported by all browsers
Selenium Javascript Executor
Initial suggested solution - not working - see enhancement request: 2840
driver.findElement(By.id('one')).find(By.XPath("./text()").getText();
In a single search
driver.findElement(By.XPath("//[#id=one]/text()")).getText();
See XPath spec/Location Paths the child::text() selector.
I use a function like below:
private static final String ALL_DIRECT_TEXT_CONTENT =
"var element = arguments[0], text = '';\n" +
"for (var i = 0; i < element.childNodes.length; ++i) {\n" +
" var node = element.childNodes[i];\n" +
" if (node.nodeType == Node.TEXT_NODE" +
" && node.textContent.trim() != '')\n" +
" text += node.textContent.trim();\n" +
"}\n" +
"return text;";
public String getText(WebDriver driver, WebElement element) {
return (String) ((JavascriptExecutor) driver).executeScript(ALL_DIRECT_TEXT_CONTENT, element);
}
var outerElement = driver.FindElement(By.XPath("a"));
var outerElementTextWithNoSubText = outerElement.Text.Replace(outerElement.FindElement(By.XPath("./*")).Text, "");
Similar solution to the ones given, but instead of JavaScript or setting text to "", I remove elements in the XML and then get the text.
Problem:
Need text from 'root element without children' where children can be x levels deep and the text in the root can be the same as the text in other elements.
The solution treats the webelement as an XML and replaces the children with voids so only the root remains.
The result is then parsed. In my cases this seems to be working.
I only verified this code in a environment with Groovy. No idea if it will work in Java without modifications. Essentially you need to replace the groovy libraries for XML with Java libraries and off you go I guess.
As for the code itself, I have two parameters:
WebElement el
boolean strict
When strict is true, then really only the root is taken into account. If strict is false, then markup tags will be left. I included in this whitelist p, b, i, strong, em, mark, small, del, ins, sub, sup.
The logic is:
Manage whitelisted tags
Get element as string (XML)
Parse to an XML object
Set all child nodes to void
Parse and get text
Up until now this seems to be working out.
You can find the code here: GitHub Code
I am working an an JavaFX Webbrowser that can autologin to some sites, i know how to set the data to username and password fields but how to i make it execute the login button click?
This is what i got so far:
String email = "document.getElementsByName('email')[0].value='MY_EMAIL';";
String pass = "document.getElementsByName('pass')[0].value='MY_PASSWORD';";
String login = "";
webEngine.executeScript(email);
webEngine.executeScript(pass);
webEngine.executeScript(login);
and this is the javascript code of the button it should click:
<label class="uiButton uiButtonConfirm" id="loginbutton" for="u_0_c"><input value="Aanmelden" tabindex="4" type="submit" id="u_0_c"></label>
this is a concentrated, non-specialized example... uses the dom.w3c.Node.* package
HTMLInputElement element = (HTMLInputElement)myWebView.getEngine().getDocument().getElementsByTagName("input").item(0);
element.click();
Find a way to handle the object you're looking for, and it will work.
I haven't tried that, but it should work. The idea is add jQuery to the loaded page and then to use it to click the button. This post explains how to do this with JS: How Do I Add jQuery To Head With JavaScript? So with Java it should be (I've copied the first answer into a string and executing it with the web engine):
String script = "script = document.createElement('script');\n" +
"\n" +
"script.onload = function() {\n" +
" // jQuery is available now\n" +
"};\n" +
"var head = document.getElementsByTagName(\"head\")[0];\n" +
"\n" +
"script.type = 'text/javascript';\n" +
"script.src = 'https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js';\n" +
"\n" +
"head.appendChild(script);";
webEngine.executeScript(script);
This should be done right when WebView is initialized (via webEngine.getLoadWorker().stateProperty().addListener(...)). Note: jQuery is loaded asynchronously.
Now you should be able to click a button with jQuery:
webEngine.executeScript("$('#buttonId')[0].click()");
This post should help you with debugging JS in a WebView: JAVAFX / WebView / WebEngine FireBugLite or Some other debugger?
I have seen this question floating around the internet, but I haven't found a working solution yet. Basically, I want to load my app and press a button; the button action will then fill in a username and password in a website already loaded in the webview (or wait for onPageFinished). Finally, the submit button on the login page will be activated.
From what I understand this can be done by doing a java injection with the loadUrl(javascript), but I don't know what the java commands would be to fill in the fields. The same question was asked for iOS, but the commands are slightly different.
Is it possible to do what I am asking with javascript in a webivew, or do I have to do a http-post without a webview like this or this?
Thank you so much for any help you can give!
Thanks all for your answer, it helped me, but didn't work.
It was allways opening a white page until i found this :
https://stackoverflow.com/a/25606090/3204928
So here complete solution, mixing all infos found here and there :
1) first of all you have to enable DOM storage, if you don't do that, .GetElementByXXX will return nothing (you have to do it before loading the page)
myWebView.getSettings().setDomStorageEnabled(true);
2)Your last Javascript call on GetElementByXXX MUST store the result in a variable
Exemple 1 :
_webview.loadUrl("javascript:var uselessvar =document.getElementById('passwordfield').value='"+password+"';");
here only one call (only one semi-colon) so we immediatly store the result in 'uselessvar'
Example 2 : see user802467 answer
here there is 3 calls (one for login field, one for password field, one to submit button), only the last call need to be store, it's done in 'frms'
Javascript programmers should easily explain this behaviour...
hope this will help
You don't need to use "java commands"... but instead JavaScript... for instance:
String username = "cristian";
webview.loadUrl("javascript:document.getElementById('username').value = '"+username+"';");
So basically, what you have to do is a big string of JavaScript code that will get those fields and put values on them; also, you can enable/disable the submit button from JavaScript.
This worked for me to fill form values and submitting the form:
webView.loadUrl("javascript: {" +
"document.getElementById('username').value = '"+uname +"';" +
"document.getElementById('password').value = '"+password+"';" +
"var frms = document.getElementsByName('loginForm');" +
"frms[0].submit(); };");
Here is complete code which works for me (Bitbucket):
webView.getSettings().setJavaScriptEnabled(true);
webView.getSettings().setDomStorageEnabled(true);
webView.loadUrl("http://example.com/");
webView.setWebViewClient(new WebViewClient(){
#Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
final String password = "password";
final String username = "username";
final String answer = 5;
final String js = "javascript:" +
"document.getElementById('password').value = '" + password + "';" +
"document.getElementById('username').value = '" + username + "';" +
"var ans = document.getElementsByName('answer');" +
"ans[0].value = '" + answer + "';" +
"document.getElementById('fl').click()";
if (Build.VERSION.SDK_INT >= 19) {
view.evaluateJavascript(js, new ValueCallback<String>() {
#Override
public void onReceiveValue(String s) {
}
});
} else {
view.loadUrl(js);
}
}
});
I tried #user802467 solution.But there was a different behaviour in 2 things
If I stored ONLY the last javascript call in a variable, it was not filling in the fields. Instead if I stored all the three calls in variables, it did
For some reason my form was not being submitted using submit(). But instead of submitting the form, if I clicked on the submit button using button.click(), I didnot need to store all the three calls and everything worked perfectly!
Here is what I used (but didnt work)
view.loadUrl("javascript: var x = document.getElementById('username').value = '" + username + "';" +
"var y = document.getElementById('password').value = '" + password + "';" +
"var form1 = document.getElementById('loginform');" +
"form1[0].submit(); ");
Here is the code that worked for me
view.loadUrl("javascript: document.getElementById('username').value = '" + username + "';" +
" document.getElementById('password').value = '" + password + "';" +
"var z = document.getElementById('submitbutton').click();"
);
It works for me
webView.loadUrl("javascript:var uselessvar =document.getElementById('regno').value='"+mob+"';",null);
webView.loadUrl("javascript:var uselessvar =document.getElementById('passwd').value='"+pass+"';",null);
I am designing an emergency response page, which needs to display information across 3 different monitors. The first monitor will gather information about the caller, and then contain 2 links. The first link needs to display a different web page on the 2nd monitor, and the 2nd link needs to display a different web page on the 3rd monitor.
Is this possible?
Thanks for any help
The first link needs to display a different web page on the 2nd monitor, and the 2nd link needs to display a different web page on the 3rd monitor.
While, depending on your operating system, it is possible to control where a window appears, there are much fewer options for doing this using javascript / serverside code over HTTP / browsers.
The only sensible way to achieve this is by configuring the displays to be tiles of a larger display rather than independent screens (for *nix/BSD/Linux, check out xinerama).
The code below saves the size of a window - and would only need some simple changes to support x/y offset and multiple windows - I leave it to you as to how you differentiate between the windows.
A simpler approach would be to just have one huge window with frames whose borders align with the monitors.
if (document.getElementById && !document.all) { // NOT for MSIE
stickySizeOverloadOnload(stickySizeSetWindowSize);
stickySizeOverloadOnresize(stickySizeSaveWindowSize);
}
function stickySizeSaveWindowSize(event)
{
var expiry = new Date();
var path = document.location.pathname;
expiry.setDate(expiry.getDate()+500);
stickySizeSetCookie('windowSize', window.outerWidth + ',' + window.outerHeight, expiry, path);
}
function stickySizeSetWindowSize()
{
var saved=stickySizeGetCookie('windowSize');
var parts=new Array();
if (saved.length) {
parts = saved.split(',');
if ((parts[0]>100) && (parts[1]>100)) {
window.outerWidth=parts[0];
window.outerHeight=parts[1];
} else {
alert("invalid size - '" + saved + "'");
stickySizeDeleteCookie('windowSize');
}
}
}
function stickySizeOverloadOnload(func)
{
var oldhandler=window.onload;
if (typeof window.onload != "function") {
window.onload=func;
} else {
window.onload=function(event) {
oldhandler(event);
func(event);
}
}
}
function stickySizeOverloadOnresize(func)
{
var oldhandler=window.onresize;
if (typeof window.onresize != "function") {
window.onresize=func;
} else {
window.onresize=function(event) {
oldhandler(event);
func(event);
}
}
}
function stickySizeSetCookie(name, value, expires, path, domain, secure) {
var curCookie = name + "=" + escape(value) +
((expires) ? "; expires=" + expires.toGMTString() : "") +
((path) ? "; path=" + path : "") +
((domain) ? "; domain=" + domain : "") +
((secure) ? "; secure" : "");
document.cookie = curCookie;
}
function stickySizeGetCookie(name) {
var dc = document.cookie;
var prefix = name + "=";
var begin = dc.indexOf("; " + prefix);
if (begin == -1) {
begin = dc.indexOf(prefix);
if (begin != 0) return null;
} else
begin += 2;
var end = document.cookie.indexOf(";", begin);
if (end == -1)
end = dc.length;
return unescape(dc.substring(begin + prefix.length, end));
}
function stickySizeDeleteCookie(name, path, domain) {
if (stickySizeGetCookie(name)) {
document.cookie = name + "=" +
((path) ? "; path=" + path : "") +
((domain) ? "; domain=" + domain : "") +
"; expires=Thu, 01-Jan-70 00:00:01 GMT";
}
}
You can open the links in a different window with the attribute target="windowName".
You have to set up the three windows manually, so assign them manually to the three screens. When you open a link again in a window it is still on the same screen.
Have a look at Java: Getting resolutions of one/all available monitors (instead of the whole desktop)?
(The answer discuss the GraphicsEnvironment.getLocalGraphicsEnvironment() call)
If you really want the windows to be locked to a specific monitor, you will need to implement this client side. Here is a link describing how to detect which monitor a window is on in Java, so you can move it to the proper monitor and maximize the window if you desire. Obviously you can implement the rest of the system server side and just display pages inside the windows you have created.
Calling All DWR Gurus!
I am currently using reverse Ajax to add data to a table in a web page dynamically.
When I run the following method:
public static void addRows(String tableBdId, String[][] data) {
Util dwrUtil = new Util(getSessionForPage()); // Get all page sessions
dwrUtil.addRows(tableBdId, data);
}
The new row gets created in my web page as required.
However, in order to update these newly created values later on the tags need to have an element ID for me to access.
I have had a look at the DWR javadoc and you can specify some additional options see http://directwebremoting.org/dwr/browser/addRows , but this makes little sense to me, the documentation is very sparse.
If anyone could give me a clue as to how I could specify the element id's for the created td elements I would be most grateful. Alternatively if anyone knows of an alternative approach I would be keen to know.
Kind Regards
Karl
The closest I could get was to pass in some arguments to give the element an id. See below:
public static void addRows(String tableBdId, String[] data, String rowId) {
Util dwrUtil = new Util(getSessionForPage()); // Get all page sessions
// Create the options, which is needed to add a row ID
String options = "{" +
" rowCreator:function(options) {" +
" var row = document.createElement(\"tr\");" +
" row.setAttribute('id','" + rowId + "'); " +
" return row;" +
" }," +
" cellCreator:function(options) {" +
" var td = document.createElement(\"td\");" +
" return td;" +
" }," +
" escapeHtml:true\"}";
// Wrap the supplied row into an array to match the API
String[][] args1 = new String[][] { data };
dwrUtil.addRows(tableBdId, args1, options);
Is this line of your code really working??
dwrUtil.addRows(tableBdId, data);
The DWR addRows method needs at least 3 parameters of 4 to work, they are:
id: The id of the table element (preferably a tbody element);
array: Array (or object from DWR 1.1) containing one entry for each row in the updated table;
cellfuncs: An array of functions (one per column) for extracting cell data from the passed row data;
options: An object containing various options.
The id, array and cellfuncs are required, and in your case, you'll have to pass the options also because you want to customize the row creation and set the TD id's. check it out:
Inside the options argument, you need to use one parameter called "cellCreator" to inform your own way to create the td html element.
Check it out:
// Use the cellFuncs var to set the values you want to display inside the table rows
// the syntax is object.property
// use one function(data) for each property you need to show on your table.
var cellFuncs = [
function(data) { return data.name_of_the_first_object_property ,
function(data) { return data.name_of_the_second_object_property; }
];
DWRUtil.addRows(
tableBdId,
data,
cellFuncs,
{
// This function is used for you customize the generated td element
cellCreator:function(options) {
var td = document.createElement("td");
// setting the td element id using the rowIndex
// just implement your own id bellow
td.id = options.rowIndex;
return td;
}
});