CefSharp RegisterJsObject and non-static context - javascript

I have a window which contains a ChromiumWebBrowser.Now I want to get a message from that Browser by a JavaScriptObject and when I get it, I want to look inside my list, what image, video or hyperlink I shall show next and then do exactly that.There is only the problem of the static and non-static methods. the complete class and all methods inside where the browser is made filled are non-static. The JavascriptObject is static.There are many examples out there that show how to create this object but not how to use it.Now my question: "Is there any way of calling the Javascript inside the HTML to access non-static objects?"
edit:
Ok, I try to be more precise and detailed.
I have an app, which creates numerous windows, which are inheriting a ChromiumWebBrowser-object, an Image-object and a MediaElement-object.
When the window is created (and every few minutes afterwards) I read a XML-file (which can be edited by another user) that tells me what I shall show in my window. Some things are easy to do, like at the end of a video I look for the next item to show. But on websites its a little bit harder. So I searched and came up with the JavaScriptObject of the ChromiumWebBrowser. I implement it in the window right after the InitializeComponent() by this code:
FirstBrowser.RegisterJsObject("callbackObject", new JavaScriptCommunicationObject());
SecondBrowser.RegisterJsObject("callbackObject", new JavaScriptCommunicationObject());
Now I can use the follwing JavaScript to communicate with my app:
<script type="text/javascript">
callbackObject.callNextMedia('NextMedia');
</script >
Inside the window I declared a new class and a method. But this method is static, so I cannot call any methods from the mainclass:
public class JavaScriptCommunicationObject
{
public void CallNextMedia(string message)
{
if (message.Equals("NextMedia"))
{
MixedMediaWindow.FileCounter++;
MixedMediaWindow.ShowNextMedia();
}
}
}
I could make the FileCounter static, so I could change it from the second one. But I cannot call the ShowNextMedia(). So I wanted to ask if there is a way to call it by any direct means. I could set a static flag (boolean) and check it every few milliseconds for a change and if it was changed, so I could call the ShowNextMedia()-method from inside the DispatcherTimer. But I wanted to avoid too much Timers, a direct method-call would be much nicer.

Related

How can I change Vaadin Components in Java through Javascript

I implemented Shepherd in my Vaadin Project, so i can guide users in tours through my web application.
But, i need to get access from the javascript on the Accordion Components in Vaadin, to open or close specific tabs. For this, i need to have access on the open() and close() method for the Accordion Components. So how can i access them through Javascript?
Already seen the Tutorial on the Website of them:
Vaadin calling java from javascript,
but sadly nothing over there, what could help me.
I already tried to use something like this:
UI.getCurrent().getPage().executeJs("window.startTour($0, $1)", this, Accordion1.getElement());
But when i try to bind it in javascript through:
window.startTour = (element, accordion) => { ... }
and in this window:
beforeShowPromise: function () {
return new Promise(function(resolve) {
element.$server.openAccordion(accordion.$server, 1);
resolve();
});
},
with the following method in java:
#SuppressWarnings("unused")
#ClientCallable
public void openAccordion(Object object, int index) {
Accordion accordion = (Accordion) object.get(this);
accordion.open(index);
}
i only get the following error message:
Class '...' has the method 'openAccordion' whose parameter 0 refers to unsupported type 'java.lang.Object'
No matter what i use as first parameter, everythin that extends Object doesnt work and i dont know why.
I found a recent post with the same question, but it was not helpful for me:
Unable to send a new bean instance to the server
Im using Intellij and in my Project: Java, Spring, Vaadin and Shepherd
Already tried to use different parameters, but only the int parameter is working, Object doesnt work.
The Problem is, i cant change the opened Tab of the Accordion from the Javascript over the Java, because of this error, so i have to implement for each Accordion 2 methods to open and close it.
Maybe somebody can help me with it or knows some tricks to master this.
Thanks
When using #ClientCallable you can pass only json or primitive types from JavaScript call to server. There is a real systems boundary here. Object is not supported and furthermore, you can cast that parameter to Java object.

Identify Unique JS Object on page

This is a little hard to describe but here goes. I'm building a page that looks something like the back end to Azure where the user chooses from a menu on the left that loads an item, call it AllUsers for now, to the right of it. From AllUsers there are options for that item and some of those might load another to the left of it, we will call that User. From User you could open more items to the right.
I have it setup nicely with a JS object something like
var AdminPage=function(){
this.con="../controllers/admin/admin_con.php";
this.pageName=null;
this.childPage=null;
this.page=null;
this.deleteItem=function(){
//Recursively deletes pages to the right
if(this.childPage){
this.childPage.delete();
this.childPage=null;
}
if(this.page){
this.page.remove();
}
return this;
}
this.add=function(pageData){
if(this.childPage){
this.deleteItem();
}
this.childPage=new AdminPage();
this.childPage.getPage(pageData);
return this;
};
this.getPage=function(pageData){
var that=this;
this.pageName=pageData.page;
$.post(this.con, pageData,
function(data,status){
if(status==='success'){
$("#rightCol").append(data);
var num=$("#rightCol").children().length;
that.page=$("#rightCol").children()[num-1];
eval(pageData.page+"=that");
}
}
);
return this;
};
}
So each time a page is added it creates a new instance of the object for that item to work with. If that item is removed it recursively removes all items to the right of it as well.
The line "eval(pageData.page+"=that");" and yes I now that eval is evil and have read all about it, creates a new variable name from the name of the item that has been added. That item say the User item then uses that variable when even it needs to call on its instance of the script. This all works mostly perfectly.
Where it fails is if the User item opens another, call it Connections that shows all the connections for that user. I then want to be able to click on a connection and have it open another User item, this works as it should how ever the problem lies when the second User item is opened and creates a new instance of the AdminPage because the same script has just loaded and has the same name it over writes the JS variable from the previous one. Then then affects several things. If you close one of them it closes both for instance and if you close the first that was loaded it should close what is to the right of it however the childPage property is now empty so that doesn't work.
So now for the question..
Is there a better way to handle creating the variables or is there an all around better way all together that will not cause this problem at all?
Can I get around using the Evil eval and make everything right with the world?
Thanks for any suggestions that you may have.
So I have found an answer to the problem but I still don't like it much.
I have generated a unique id and appended it to the end of the name of the script that I was using as the variable.
Issues with this method is it is still using eval and now I have to pass the variable round trip when getting the new item so that I can use php to inject the variable name into the page.
Not perfect and there has to be a better way.
Here is the updated add function
this.add=function(pageData){
if(this.childPage){
this.deleteChildren();
}
this.childPage=new AdminPage();
this.childPage.id=pageData.page+"_"+(Math.floor(Math.random() * (999999999 - 100000000 + 1)) + 100000000);
eval(this.childPage.id+"=this.childPage");
this.childPage.getPage(pageData);
return this;
};

How to call existing javascript function from android activity

I'm building an ebook reader, so I have a webview that is loading a page stored locally on the device which it retrieved from the ebook itself. On this page, it has a javascript function controls.nextPage() that loads and runs just fine; it's used to not actually navigate to new web pages, but instead redraw virtual pages using javascript. I have this function bound to a button on the web page itself so that I can manually click it to test, again, works just fine when I touch the button on my webview.
Now I am trying to trigger this exact function from within my app. Ideally, I want to do this from a gesture swipe but that is too complicated for this specific question, as I have other issues with the gestures I need to solve first. For now, I've set up a button in my navigation drawer to trigger it and test it:
NavigationView navigationViewRight = (NavigationView) findViewById(R.id.nav_view_webview_right);
navigationViewRight.setNavigationItemSelectedListener(
new NavigationView.OnNavigationItemSelectedListener() {
#SuppressWarnings("StatementWithEmptyBody")
#Override
public boolean onNavigationItemSelected(MenuItem item) {
// Handle navigation view item clicks here.
int id = item.getItemId();
if (id == R.id.nav_camera) {
// *** - Focus here - *** //
runOnUiThread(new Runnable() {
#Override
public void run() {
mWebview.evaluateJavascript("controls.pageNext()", null);
}
});
} else if (id == R.id.nav_share) {
}
drawer.closeDrawer(GravityCompat.END);
return true;
}
}
);
Note, I've also tried calling window.controls.pageNext() to no avail.
So my page is loaded, and I've hit my in-page button to test the function; works. I go to hit my navigation drawer button in the app? Error (when using window.controls.pageNext():
[INFO:CONSOLE(1)] "Uncaught TypeError: Cannot read property 'pageNext' of undefined", source: (1)
So it seems to be that evaluateJavascript() is being run in a fresh environment/thread. How can I tell it not to?
To get around this,I've tried to create an empty javascript interface in the hopes that I could simply initialize my page javascript into it and thus be able to call it from Android.
mWebview.addJavascriptInterface(new TestInterface(), "TestInterface");
public class TestInterface {
#JavascriptInterface
public void test() {
runOnUiThread(new Runnable() {
#Override
public void run() {
mWebview.evaluateJavascript("console.log('test')", null);
}
});
}
}
From my webapp, the javascript can call the interface just fine. calling TestInterface.test(); yields:
[INFO:CONSOLE(1)] "test", source: (1)
But when I tried to assign a new function to that interface from my webapp:
TestInterface.testTwo = function() {
console.log('testTwo');
};
TestInterface.testTwo();
Android wouldn't have it:
[INFO:CONSOLE(674)] "testTwo", source: http://127.0.0.1:8080/js/main.js (674)
What's weird is that it's not really giving me much info to go on. I do know that the rest of my page has issues loading after the testTwo() attempt that test() did not, so I'm assuming failure to load the rest of the script.
Lastly, out of curiousity, I changed my navigation drawer button to try and run the new function like this:
mWebview.evaluateJavascript("TestInterface.testTwo()", null);
Log:
[INFO:CONSOLE(1)] "Uncaught TypeError: TestInterface.testTwo is not a function", source: (1)
Yes but is it something else? I dunno. Thoughts? Thank you.
So I figured out in the end what my issue was. By running mWebview.evaluateJavascript("window.location", null); I realized that I in fact was not actually on the page I thought I was. My ebook page was being loaded into an iframe or some other type of skeleton/wrapper in such a way that the webapp functions were not in fact available when running evaluateJavascript().
So once figured that out, I can confirm some things that I originally questioned above:
So it seems to be that evaluateJavascript() is being run in a fresh
environment/thread. How can I tell it not to?
It does not. Just make sure you know what page is actually loaded.
To get around this,I've tried to create an empty javascript interface in the hopes that I could simply initialize my page javascript into it and thus be able to call it from Android.
This does in fact work. I'm not sure what my mistake was before, but if I create a javascript interface in Android, it's functions are available to the webapp AND I can in fact write new objects to the interface object from within the webapp. So, initializing new objects into TestInterface from within the webapp can be run within the Android app via:
mWebview.evaluateJavascript("TestInterface.someNewFunctionFromWebapp()", null);
I can NOT however overwrite any existing objects/properties of that javascript interface object. so TestInterface.test() is immutable.

How to get a reference to form elements in a Jenkins jelly script for a Builder configuration when the form loads?

I am writing a Jenkins Builder, and in the jelly script for its configuration in the build configuration page I have some Javascript that I want to run when the form is loaded, to do a server lookup and get some information to help the user out with their configuration, which will also be performed when the user changes the form's values.
Previously I have got references to the form elements by passing this in to functions in onchange or onkeyup attributes. However, now I want to run some script even when the form hasn't changed.
I know I could set ID attributes on the form elements, however that's not going to work if the users add to a build two build steps both using this builder.
I've tried generating a random ID on my builder class, and then use that to construct IDs for the elements and write that into some Javascript in the jelly file so I can find those elements there, but that doesn't get initialised until the user saves, so it won't work if the user adds two instances of this builder without saving the job:
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<f:entry title="Entry 1">
<f:textbox field="field1" id="${instance.id}-field1" onchange="fieldChanged('${instance.id}-field1')"/>
</f:entry>
<script type="text/javascript">
function fieldChanged(elementId) {
...
}
fieldChanged('${instance.id}-field1');
</script>
</j:jelly>
Are there any conventions on how to do this sort of thing? Anything built in to Jenkins/jelly to support multiple instances of the same jelly file being able to refer to their own elements?
There's a solution using j:set which is simpler than my other answer.
com.example.MyBuilder.DescriptorImpl:
private int lastEditorId = 0;
...
#JavaScriptMethod
public synchronized String createEditorId() {
return String.valueOf(lastEditorId++);
}
com/example/MyBuilder/config.jelly:
...
<j:set var="editorId" value="${descriptor.createEditorId()}" />
<f:entry title="Field">
<f:textbox field="field" id="field-${editorId}"/>
<p id="message-${editorId}"></p>
</f:entry>
<script>
setTimeout(function(){
var field = document.getElementById('field-${editorId}');
var p = document.getElementById('message-${editorId}');
p.textContent = "Initial value: "+field.value;
}, 50);
</script>
(The call to setTimeout is still due to the fact that when adding new build steps, the elements haven't been added to the DOM by the time that the script executes, so the script execution has to be deferred slightly).
It looks like this solution below might work, but I haven't got very far with it yet.
In my builder class I've added an inner class called Editor:
com.example.MyBuilder(.Editor):
...
public static class Editor {
private final String id;
public Editor(final String id) {
this.id = id;
}
public String getId() {
return id;
}
}
...
Then in the descriptor Java class, provide a JavaScript function to create one of these with a unique ID:
com.example.MyBuilder.DescriptorImpl:
private int lastEditorId = 0;
#JavaScriptMethod
public synchronized Editor createEditor() {
return new Editor(String.valueOf(lastEditorId++));
}
Then in my jelly file I call that method and pass the returned object into st:include, loading a new jelly file to render the fields:
com/example/MyBuilder/config.jelly:
<st:include page="editor.jelly" it="${descriptor.createEditor()}" />
(Although this appears to have to be inside an f:entry element - or perhaps other elements, I haven't tried - otherwise it doesn't seem to get included when a new build step for this builder is added to the job config.)
And finally I create that new editor.jelly file to render the fields (which has to be in a folder whose name reflects the Editor class, as the it object being passed into st:include is of type Editor):
com/example/MyBuilder/Editor/editor.jelly:
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<l:ajax>
<f:entry title="Field">
<f:textbox field="field" id="field-${it.id}"/>
<p id="message-${it.id}"></p>
</f:entry>
<script>
setTimeout(function(){
var field = document.getElementById('field-${it.id}');
var p = document.getElementById('message-${it.id}');
p.textContent = "Initial value: "+field.value;
}, 50);
</script>
</l:ajax>
</j:jelly>
(The call to setTimeout is due to the fact that when adding new build steps, the elements haven't been added to the DOM by the time that the script executes, so the script execution has to be deferred slightly).
However, this breaks the link between the f:entry elements and the equivalent fields in the builder class, and I'm not sure what to do about that. So this is an incomplete answer.
EDIT: I'm not sure if the f:entry elements would have worked or not, as I had forgotten to add the field to the builder class when I was testing it, which was (at least one reason) why I did not see any data saved from this field when I tried this. However, I am now using the solution from my other answer, so I have not gone back to test whether it would have worked or not.

Instantiating javascript objects from ASP.NET Pages

Most of the examples you find on the web, of using javascript from ASP.NET pages puts the javascript in the markup file (*.aspx). This is, of course, a really bad idea(tm), for all but the simplest uses of javascript.
What we want, of course, is to wrap the javascript up into a class, and to instantiate an instance of that class and tie it to the code-behind.
Microsoft provides a framework for doing this for user controls and server controls, in its IScriptControl interface. This allows a developer to create a javascript "component" - to define a javascript class in a *.js file, to include the *.js file on the page that contains the control, to instantiate an instance of the component, to set variables in the component from values in the code-behind, and to get a reference to the component in javascript on the client side.
The thing is - IScriptControl only works for user and server controls. It cannot be used to instantiate javascript objects at the page level.
So - how do people do this? We have some patterns we've been using, that seem to work. I was wondering what everyone thought of them, and what other people were using.
We start by defining a javascript class in a *.js file. In the code-behind, we create a loadJavascript() function, that we call from Page_Load on initial load or full postback (but not on partial postbacks).
In loadJavascript(), we include the *.js file with ScriptManager.RegisterClientScriptInclude(), and then construct a bit of javascript that instantiates an instance of the class, assigns a reference to a known name, and registers the object's initialize() and dispose() methods as handlers for window.load and window.unload.
E.g.:
string url = this.ResolveUrl("./FooBar.js");
ScriptManager.RegisterClientScriptInclude(this, this.GetType(), url, url);
string script = #"
if (typeof {0}_obj == 'undefined')
{0}_obj = {{}};
{0}_obj.fooBar = new FooBar();
Sys.UI.DomEvent.addHandler(window, 'load',
function()
{{
{0}_obj.fooBar.initialize('{1}', '{2}');
}}
);
Sys.UI.DomEvent.addHandler(window, 'unload', {0}_obj.fooBar.dispose);
";
script = String.Format(script,
new object[]
{
this.ClientID,
this.foo.ClientID,
this.bar.ClientID
});
ScriptManager.RegisterStartupScript(
this, this.GetType(), this.ClientID, script, true);
We construct an object name in the global namespace, based on the ClientID of the page, if we haven't already. We add an instance of our new class as a member of our global object. We add a window.load handler that calls our object's intialize() method, passing the clientIDs of the controls on the page that the object's methods need to access. And we add a window.unload handler that calls our object's dispose() method, that does whatever cleanup that is necessary.
This seems to be working, for us. We've used this pattern on a number of pages, some of which did significant amounts of partial-postbacks, without any problems.
I was wondering, first, what people thought of the pattern.
But more, I was wondering if we'd been reinventing the wheel, and if there were other approaches to dealing with the issues we were addressing, that we weren't aware of.
Anyone have any better ideas?
But more, I was wondering if we'd been reinventing the wheel, and if there were other approaches to dealing with the issues we were addressing, that we weren't aware of.
I think this the most good approaches, I use the same way some years now with out any problem in very complex javascript code. I do not see why you question your self :)
The idea is this you follow, now maybe there are some variations, maybe I not call the unload, nether create an object to keep the foobar and call the foobar rightway, but the idea is the same. I also check if the Javascript file have been loaded...
string script = #"
if (typeof (FooBar) != "undefined") {{
var {0}fooBar = new FooBar();
Sys.UI.DomEvent.addHandler(window, 'load',
function()
{{
{0}fooBar.initialize('{1}', '{2}');
}}
);
}}

Categories

Resources