Using deployJava.runApplet to target specific element - java

After many years of successfully maintaining an applet that uses the good old:
<script src="foo.js"></script>
method of embedding a Java applet, we're unable to cover our ears and sing "La la la!" anymore.
It's time to be using:
deployJava.runApplet()
When I fire this method using a click handler (here using an event listener on a button via jQuery, but it doesn't matter):
$('#button').click(function() {
deployJava.runApplet(attributes, parameters, version);
});
...it wipes out the entire existing document and replaces it with the applet. All I need to know is how to target a specific DOM element to be the container for the applet, so that my page doesn't get wiped.
It seems like it would be an attribute I could pass in the form of target: someElement where "someElement" is either a DOM object or the element's ID as a string. But alas, I can't find documentation for such an attribute.
For the sake of being complete, here's what's being passed:
/*here is where I imagine there might be an applicable attribute */
var attributes = {
name: "SomeName",
code: "some.class",
archive: "some.jar",
width: 640,
height: 400
};
var parameters = {
someParameter: someValue
};
var version = "1.5";
I can document.write everything I need to rebuild a document, but I'm sure you can all well imagine how hideous that prospect seems to me.
Any pointers would be greatly appreciated.

As alternative to Christophe Roussy solution you may override document.write while running deployJava.runApplet.
Like so:
function docWriteWrapper(func) {
var writeTo = document.createElement('del'),
oldwrite = document.write,
content = '';
writeTo.id = "me";
document.write = function(text) {
content += text;
}
func();
writeTo.innerHTML += content;
document.write = oldwrite;
document.body.appendChild(writeTo);
}
An then:
docWriteWrapper(function () {
deployJava.runApplet(attributes, parameters, "1.6");
});
A little bit hackish but works like a charm;)

So the core of the problem is this: deployJava.js uses document.write. If you use this method AFTER page render (vs. as a part of the initial page render) it will first clear the document. Which has obvious negative repurcussions.
Although intended for JavaFX, people have reported success with dtjava.js, and I have every reason to believe it's a viable alternative.
However, other stakeholders on my team have already done work surrounding deployJava.js and are unwilling to throw away that work, which meant I needed to stick to deployJava.js. There's only one way to do this: make sure that deployJava is called during page render, not via Ajax, event, or other delayed trigger.
In the end, we are collecting our information, and passing it to a second page which will render the applet as expected. It works, and in most scenarios our clients will be doing this anyhow (collecting information, passing it server-side, and getting a redirect response), so it didn't make sense to force the issue. We are passing information via query string but you could probably use cookies and/or localstorage API instead, if you wanted the window.location to stay cleaner-looking.
Thanks for the replies, even though they were in the comment area. Other replies are still being taken on board if someone has a better way of doing it!

If you are using jQuery and want to target a specific dom element, rather than just appending:
function docWriteWrapper(jq, func) {
var oldwrite = document.write, content = '';
document.write = function(text) {
content += text;
}
func();
document.write = oldwrite;
jq.html(content);
}
docWriteWrapper($('#mydiv'), function () {
deployJava.runApplet(attributes, parameters, version);
});

To solve this annoying issue I downloaded and hacked deployJava.js at line 316, replaced the line by my own:
// document.write(n + "\n" + p + "\n" + r);
myDiv.append(n + "\n" + p + "\n" + r);
Where myDiv is a js global variable set to the desired div before calling runApplet:
myDiv = jQuery('#someDiv');
If you find a less intrusive solution let me know...

Related

How to speed up page parsing in Selenium

What can I do in case if I load the page in Selenium and then I have to do like 100 different parsing requests to this page?
At this moment I use different driver.findElement(By...) and the problem is that every time it is a http (get/post) request from java into selenium. From this case one simple page parsing costs me like 30+ seconds (too much).
I think that I must get source code (driver.getPageSource()) from first request and then parse this string locally (my page does not change while I parse it).
Can I build some kind of HTML object from this string to keep working with WebElement requests?
Do I have to use another lib to build HTML object? (for example - jsoup) In this case I will have to rebuild my parsing requests from webelement's and XPath.
Anything else?
When you call findElement, there is no need for Selenium to parse the page to find the element. The parsing of the HTML happens when the page is loaded. Some further parsing may happen due to JavaScript modifications to the page (like when doing element.innerHTML += ...). What Selenium does is query the DOM with methods like .getElementsByClassName, .querySelector, etc. This being said, if your browser is loaded on a remote machine, things can slow down. Even locally, if you are doing a huge amount of round-trip to between your Selenium script and the browser, it can impact the script's speed quite a bit. What can you do?
What I prefer to do when I have a lot of queries to do on a page is to use .executeScript to do the work on the browser side. This can reduce dozens of queries to a single one. For instance:
List<WebElement> elements = (List<WebElement>) ((JavascriptExecutor) driver)
.executeScript(
"var elements = document.getElementsByClassName('foo');" +
"return Array.prototype.filter.call(elements, function (el) {" +
" return el.attributes.whatever.value === 'something';" +
"});");
(I've not run the code above. Watch out for typos!)
In this example, you'd get a list of all elements of class foo that have an attribute named whatever which has a value equal to something. (The Array.prototype.filter.call rigmarole is because .getElementsByClassName returns something that behaves like an Array but which is not an Array so it does not have a .filter method.)
Parsing locally is an option if you know that the page won't change as you examine it. You should get the page's source by using something like:
String html = (String) ((JavascriptExecutor) driver).executeScript(
"return document.documentElement.outerHTML");
By doing this, you see the page exactly in the way the browser interpreted it. You will have to use something else than Selenium to parse the HTML.
Maybe try evaluating your elements only when you try to use them?
I dont know about the Java equivalent, but in C# you could do something similar to the following, which would only look for the element when it is used:
private static readonly By UsernameSelector = By.Name("username");
private IWebElement UsernameInputElement
{
get { return Driver.FindElement(UsernameSelector); }
}

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.

Include page javascripts conflicting with outer page javascripts

My problem is a little difficult to explain, but I will try.
I have 2 jsp pages Outer.jsp and Inner.jsp
Outer.jsp
Script: src="tabs.js"
var PageTabs = "Tab1"
#include "Inner.jsp"
Inner.jsp
Script: src="tabs.js"
var PageTabs = "Tab2~Tab3~Tab4"
Both the jsp pages use the same tabs.js to render some tab elements on the page. The "PageTabs" variable is one of the many common variables that are used by tabs.js. So what happpens is while rendering, the tabs.js takes the latest "PageTabs" variable i'e var PageTabs = "Tab2~Tab3~Tab4" even while rendeing tabs of Outer.jsp.
Note: The page variables and tabs.js are part of standard elements recieved from client. So they have to be used to give the same look and feel for application.
What I need is a way to isolate the "Inner.jsp" from accessing scripts of "Outer.jsp". This will prevent the tabs element from being confused over which variables to use.
I hope I am somewhat clear. Please let me know if I need to provide any more clarifications. Thanks.
JavaScript is interpreted top-to-bottom inside a page, so your second PageTabs value overrides the first one. One option is to use a different name for the variable (and parameterize tabs.js functions rather than rely on global vars.)
When using jsp include, one of a very useful skill is to make your .js code modular . This is also an important method to encapsulate code and avoid conflict.
tab.js:
var tabModule = (function(my){
var model;
return {
setModel: function(model){/*....*/}
//other api functions
}
})(tabModoule||{});
Outer.jsp:
tabModule.setModel({PageTabs : "Tab1"});
Innder.jsp:
tabModule.setModel({PageTabs : "Tab2~Tab3~Tab4"});

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.

How can I embed a Java applet dynamically with Javascript?

I want to be able to insert a Java applet into a web page dynamically using a Javascript function that is called when a button is pressed. (Loading the applet on page load slows things down too much, freezes the browser, etc...) I am using the following code, which works seamlessly in FF, but fails without error messages in IE8, Safari 4, and Chrome. Does anyone have any idea why this doesn't work as expected, and how to dynamically insert an applet in a way that works in all browsers? I've tried using document.write() as suggested elsewhere, but calling that after the page has loaded results in the page being erased, so that isn't an option for me.
function createPlayer(parentElem)
{
// The abc variable is declared and set here
player = document.createElement('object');
player.setAttribute("classid", "java:TunePlayer.class");
player.setAttribute("archive", "TunePlayer.class,PlayerListener.class,abc4j.jar");
player.setAttribute("codeType", "application/x-java-applet");
player.id = "tuneplayer";
player.setAttribute("width", 1);
player.setAttribute("height", 1);
param = document.createElement('param');
param.name = "abc";
param.value = abc;
player.appendChild(param);
param = document.createElement('param');
param.name = "mayscript";
param.value = true;
player.appendChild(param);
parentElem.appendChild(player);
}
document.write()
Will overwrite your entire document. If you want to keep the document, and just want an applet added, you'll need to append it.
var app = document.createElement('applet');
app.id= 'Java';
app.archive= 'Java.jar';
app.code= 'Java.class';
app.width = '400';
app.height = '10';
document.getElementsByTagName('body')[0].appendChild(app);
This code will add the applet as the last element of the body tag. Make sure this is run after the DOM has processed or you will get an error. Body OnLoad, or jQuery ready recommended.
I would have suggested doing something like what you're doing; so I'm baffled as to why it's not working.
Here's a document that looks pretty authoritative, coming from the horse's mouth as it were. It mentions the idiosyncrasies of different browsers. You may end up needing to do different tag soups for different implementations.
But maybe there's something magic about applet/object tags that keeps them from being processed if inserted dynamically. Having no more qualified advice, I have a crazy workaround to offer you: Howzabout you present the applet on a different page, and dynamically create an IFRAME to show that page in the space your applet should occupy? IFRAMEs are a bit more consistent in syntax across browsers, and I'd be surprised if they were to fail the same way.
Maybe you should use your browser's debugging tools to look at the DOM after you swap in your applet node. Maybe it's not appearing where you think it is, or not with the structure you think you're creating. Your code looks OK to me but I'm not very experienced with dynamic applets.
There is a JavaScript library for this purpose:
http://www.java.com/js/deployJava.js
// launch the Java 2D applet on JRE version 1.6.0
// or higher with one parameter (fontSize)
<script src=
"http://www.java.com/js/deployJava.js"></script>
<script>
var attributes = {code:'java2d.Java2DemoApplet.class',
archive:'http://java.sun.com/products/plugin/1.5.0/demos/plugin/jfc/Java2D/Java2Demo.jar',
width:710, height:540} ;
var parameters = {fontSize:16} ;
var version = '1.6' ;
deployJava.runApplet(attributes, parameters, version);
</script>
I did something similar to what Beachhouse suggested. I modified the deployJava.js like this:
writeAppletTag: function(attributes, parameters) {
...
// don't write directly to document anymore
//document.write(startApplet + '\n' + params + '\n' + endApplet);
var appletString = startApplet + '\n' + params + '\n' + endApplet;
var divApplet = document.createElement('div');
divApplet.id = "divApplet";
divApplet.innerHTML = appletString;
divApplet.style = "visibility: hidden; display: none;";
document.body.appendChild(divApplet);
}
It worked ok on Chrome, Firefox and IE. No problems so far.
I tried at first to have a div already created on my html file and just set its innerHTML to the appletString, but only IE were able to detect the new applet dynamically. Insert the whole div direclty to the body works on all browsers.
Create a new applet element and append it to an existing element using appendChild.
var applet = document.createElement('applet');
applet.id = 'player';
...
var param = document.createElement('param');
...
applet.appendChild(param);
document.getElementById('existingElement').appendChild(applet);
Also, make sure the existing element is visible, meaning you haven't set css to hide it, otherwise the browser will not load the applet after using appendChild. I spent too many hours trying to figure that out.
This worked for me:
// my js code
var app = document.createElement('applet');
app.code= 'MyApplet2.class';
app.width = '400';
app.height = '10';
var p1 = document.createElement('param');
p1.name = 'sm_UnwindType';
p1.value='200';
var p2 = document.createElement('param');
p2.name = 'sm_Intraday';
p2.value='300';
app.appendChild(p1);
app.appendChild(p2);
var appDiv = document.getElementById('applet_div');
appDiv.appendChild(app);
-----html code:
<div id="applet_div"></div>

Categories