Vaadin flow javascript to Java call - javascript

Following this tutorial https://vaadin.com/blog/calling-java-from-javascript I'm trying to call a Java function from javascript but that doesn't seem to work as expected.
I'm having a View that contains a button which, on its onClick handler, triggers a call to a Javascript function, which works as expected.
The problem I'm having is that the getElement() that I`m passing to the javascript function is undefined when it reaches the javascript side of things.
My code looks as follows:
#JavaScript("./js/script.js")
public class RouteGraphicsView extends Div {
....
Button b = new Button("Test Button");
b.addClickListener(new ComponentEventListener<ClickEvent<Button>>() {
private static final long serialVersionUID = 1L;
#Override
public void onComponentEvent(final ClickEvent<Button> event) {
UI.getCurrent().getPage().executeJs("greet($0, $1)", "test name", UI.getCurrent().getElement());
}
});
....
}
The above call reaches the script.js file which looks like this
window.greet = function greet(name, element) {
console.log("Hello, I am greeting you, " + name);
try {
console.log("Element ", element);
console.log("Logging 1", element.$server);
} catch (e) {
console.log(e);
}
}
The output shown by the greet function above is
Hello, I am greeting you, test name
vaadin-bundle-62ac8b…b56c6.cache.js:4813 Element
vaadin-bundle-62ac8b…b56c6.cache.js:4813 Logging 1 undefined
Since the element.$server is undefined I can not get the javascript function to call my greet function in the View, which is annotated with #ClientCallable
#ClientCallable
public void greet(final String name) {
System.out.println("Called from JavaScript: " + name + " \n\n\n");
}
I've tried various other ways of calling the script.js, like using button's element to invoke the executeJs function or passing the button's element (b.getElement()) as an argument to the function but to no avail.
What am I doing wrong ?

You're doing element.$server on the element that you passed as UI.getCurrent().getElement(). This corresponds to the UI instance and not an instance of the RouteGraphicsView class that (I assume) has the #ClientCallable method. Using the button would also not work for the same reason.
You should pass an instance of the view, which in your case needs to be written as RouteGraphicsView.this because of the way the regular this refers to the click listener.

Related

Calling JavaScript from Blazor: how to dispose JavaScript within DotNetObjectReference?

I have created a Blazor Webassembly Project and added a key listener in JavaScript, which is listening to every key stroke on the DOM document. Everything works as expected, however when I open the Blazor page where the key listener is registered and later open it again, the following error occurs in the Web Browser:
There is no tracked object with id '2'. Perhaps the
DotNetObjectReference instance was already disposed. (Parameter
'dotNetObjectId')
Obviously the object "dotnethelper" is disposed but the Javascript is still listening / getting called.
Basically I implemented the "Component instance .NET method helper class" from the Microsoft Documentation.
Blazor Page:
Note: The IDisposable is injected on the top and the Dispose function is getting called.
#code {
private KeyListenerInvokeHelper _keyListenerInvokeHelper;
private DotNetObjectReference<KeyListenerInvokeHelper>? objRef;
protected override async Task OnInitializedAsync()
{
objRef = DotNetObjectReference.Create(_keyListenerInvokeHelper);
await JS.InvokeVoidAsync("initializeKeyListener", objRef);
}
public void Dispose()
{
objRef?.Dispose();
}
}
Javascript File:
window.initializeKeyListener = (dotnetHelper) => {
document.addEventListener('keydown', logKey);
function logKey(e) {
dotnetHelper.invokeMethod('OnKeyDown', e.key);
console.log('key down ' + e.key);
}
}
KeyListenerInvokeHelper:
public class KeyListenerInvokeHelper
{
private readonly Action<string> action;
public KeyListenerInvokeHelper(Action<string> action)
{
this.action = action;
}
[JSInvokable("OnKeyDown")]
public void OnKeyDown(string key)
{
action.Invoke(key);
}
}
What have I tried so far?
I tried to reset the function on window.initializeKeyListener (i.e. setting window.initializeKeyListener), however this did not achieve anything
I tried removing the eventlistener on the 'keydown' event.
When you dispose of your object, you need to remove the event listener as well. You mentioned I tried removing the eventlistener on the 'keydown' event., but perhaps the way you did it was not correct?
My javascript is a little rusty, but I think you could do something like the following:
var logkey;
window.initializeKeyListener = (dotnetHelper) => {
logkey = (e) => {
dotnetHelper.invokeMethod('OnKeyDown', e.key);
console.log('key down ' + e.key);
};
document.addEventListener('keydown', logkey);
}
window.removeKeyListener = () => {
document.removeEventListener('keydown', logkey);
}
and then in your component:
#implements IAsyncDisposable
public async ValueTask DisposeAsync()
{
await JSRuntime.InvokeVoidAsync("removeKeyListener");
objRef?.Dispose();
}
Having said that, perhaps calling a static method in C# using [JSInvokable] would be better suited for your use case?

Keeping console.log() line numbers in a wrapper function in TypeScript / Angular 2

I am try to make a logging service for my TypeScript / Angular 2 App. Unfortunately if i call console.log the line number is wrong. Even if i try to return console.log().
Here is my code:
LoggerService.ts
export class LoggerService {
log(message) {
// Server-side logging
// [...]
if (clientSideLogging) return console.log(message);
}
}
SomewhereElse.ts
this.logger.log('hello world');
-> Shows line number of LoggerService.ts instead of source
You could use the .bind() method to bind window.console to your custom log method and then return the function so that the code is executed within the original scope when it is called.
In doing so, the line number will be preserved when calling the logger service's log method:
class LoggerService {
public log = console.log.bind(window.console);
}
// ...or annotated:
class LoggerService {
public log: (message) => void = console.log.bind(window.console);
}
Then if you want to add in your conditional statement:
class LoggerService {
public log = clientSideLogging ? console.log.bind(window.console) : () => {};
}
Here is an example with the compiled TypeScript code.
Aside from the one-liner solutions mentioned above, if you want to implement additional logic inside of the log method, then you could utilize a getter which will return and call the console.log function that is bound to window.console.
class LoggerService {
public get log (): Function {
// Implemnt server-side logging
return console.log.bind(window.console);
}
}
As you can tell, it is important for the console.log function to be returned since it will not preserve the line numbers when it is called directly within another scope.
Then if you want to add in your conditional statement:
class LoggerService {
public get log (): Function {
const log = console.log.bind(window.console);
// Implemnt server-side logging
return clientSideLogging ? log : () => {};
}
}
Here is an example with the compiled TypeScript code.
You could use .trace() instead of .log().
this.logger.trace('hello world');
This will give you a stack trace to the original line number.
https://developer.mozilla.org/en-US/docs/Web/API/Console/trace

Call .NET from JavaScript using CefGlue / CEF3

There is an existing solution for CefGlue: Call .Net from javascript in CefSharp 1 - wpf
I want exactly this, but for CefGlue: I want to communicate with the App using JavaScript. So when I click a button in my HTML site, I want the application to handle this (for example: start a tcp server).
I tried to register an own CefV8Handler but without success, the Execute function on the handler is never called. Here is what I do right now
protected override void OnWebKitInitialized()
{
Console.WriteLine("Registering testy extension");
Xilium.CefGlue.CefRuntime.RegisterExtension("testy", "var testy;if (!testy)testy = {};(function() {testy.hello = function() {};})();", new V8Handler());
base.OnWebKitInitialized();
}
My V8Handler code looks as follows:
public class V8Handler : Xilium.CefGlue.CefV8Handler
{
protected override bool Execute(string name, CefV8Value obj, CefV8Value[] arguments, out CefV8Value returnValue, out string exception)
{
if (name == "testy")
Console.WriteLine("CALLED TESTY");
else
Console.WriteLine("CALLED SOMETHING WEIRED ({0})", name);
returnValue = CefV8Value.CreateNull();
exception = null;
return true;
}
}
I'm in multiprocess mode, no console window shows "CALLED TESTY" nor "CALLED SOMETHING WEIRED".
Found a solution for that. The trick is to create a CefV8Value (CreateFunction) and assign it to a V8Handler. Then assign this value to the global context. This is what it looks like:
internal class RenderProcessHandler : CefRenderProcessHandler
{
protected override void OnContextCreated(CefBrowser browser, CefFrame frame, CefV8Context context)
{
CefV8Value global = context.GetGlobal();
CefV8Value func = CefV8Value.CreateFunction("magic", new V8Handler());
global.SetValue("magic", func, CefV8PropertyAttribute.None);
base.OnContextCreated(browser, frame, context);
}
}
Another problem came up: it was called in the renderer process, but I required the callback in the browser process. In the CefV8Handlers execute function i did this:
var browser = CefV8Context.GetCurrentContext().GetBrowser();
browser.SendProcessMessage(CefProcessId.Browser, CefProcessMessage.Create("ipc-js." + name));
This way I can retrive the message in the OnProcessMessageReceived function in the CefClient implementation.

Call Java component from JavaScript and retrieve value

I am trying to call a Wicket component's method from JavaScript and receive a value from this method which I want to use in the remaining bit of the JavaScript function which I used to call the component. However, I only seem to be able to call a Wicket component without waiting for it to produce a result.
More explicitly, I want to implement an AbstractDefaultAjaxBehavior which allows me to conditionally warn a user when he or she is leaving a page. This condition is for now determined by some OuterClass.shouldWarn method. However, even though this method gets called in my example below, I seem to be both unable to wait for a result of this method as well as I am unable to return some sort of result at all. Instead, the JavaScript just continues in its execution concurrently to the Java method call.
I hope the (not correctly running) example below clarifies my question:
class PageExitWarningBehavior extends AbstractDefaultAjaxBehavior {
#Override
protected void respond(AjaxRequestTarget target) {
target.appendJavaScript("return " +
(OuterClass.this.shouldWarn() ? "false" : "true"));
}
#Override
public void renderHead(Component component, IHeaderResponse response) {
String callbackFunktion = String.format(
"Wicket.Event.add(window, 'beforeunload', function( e ) {%n"
+ "if( e ) { e.returnValue = '%s'; }%n"
+ "var attrs = { 'u': '%s', 'c': '%s', 'ep': { } };%n"
+ "Wicket.Ajax.get( attrs );%n"
+ "return false;%n;"
+ "});",
this.getCallbackUrl(),
OuterClass.this.getMarkupId());
response.render(JavaScriptHeaderItem.forScript(callbackFunktion,
"remind-of-running-task"));
}
}
I believe there is an easier way to intercept a page exit event than implementing your own AjaxBehavior:
Try implementing the following Behavior:
public class PageExitWarningBehavior extends Behavior {
private boolean shouldWarn = false;
#Override
public void renderHead(Component component, IHeaderResponse response) {
super.renderHead(component, response);
if (shouldWarn) {
response.render(new OnDomReadyHeaderItem("window.onbeforeunload = function (e) {"
+ "var message = 'Your confirmation message goes here.',"
+ "e = e || window.event;" + "if (e) {"
+ "e.returnValue = message;" + "}" + "return message;" + "};"));
}
}
#Override
public void onEvent(Component component, IEvent<?> event) {
super.onEvent(component, event);
if (event.getPayload() instanceof PageExitWarningEvent) {
PageExitWarningEvent exitEvent = (PageExitWarningEvent) event.getPayload();
this.shouldWarn = exitEvent.isPageExitWarningEnabled();
}
}
}
In the renderHead method you conditionally add a simple javascript that triggers the browser to show a confirmation dialog when leaving the page (the javascript code is from this post).
In the onEvent method we listen if some other Wicket component has sent an PageExitWarningEvent to inform us that a warning should be displayed at all. You can send such an event from any Wicket component (such as a link or button) like this:
send(HomePage.this, Broadcast.BREADTH, new PageExitWarningEvent(true));
The PageExitWarningEvent class looks like this:
public class PageExitWarningEvent {
private boolean pageExitWarningEnabled = false;
public PageExitWarningEvent(boolean pageExitWarningEnabled) {
this.setPageExitWarningEnabled(pageExitWarningEnabled);
}
public boolean isPageExitWarningEnabled() {
return pageExitWarningEnabled;
}
public void setPageExitWarningEnabled(boolean pageExitWarningEnabled) {
this.pageExitWarningEnabled = pageExitWarningEnabled;
}
}
Let me know if that meets your requirements.

Javascript __dopostback(): arguments passing error "Invalid postback or callback argument"

I would like to raise an event in javascript from Asp.net code behind page; whenever a checkbox control is checked. I am able to use __dopostback from javascript to raise an event in the code behind, but I'm not able to pass a variable as argument.
This is the javascript code:
function CallServerCkhkBox(chkvalue) {
alert("_dopostback " + chkvalue);
__doPostBack('btnRefresh', chkvalue);
//__doPostBack('btnRefresh', 'Blue Green');
}
The alert prints the correct value of the variable chkvalue.
This is the C# code behind:
protected void btnRefresh_Click(object sender, EventArgs e)
{
string checkboxes;
if (Request["__EVENTARGUMENT"] != "")
{
checkboxes = "From Javascript " + Request["__EVENTARGUMENT"];
}
else
{
checkboxes = "From Click " + hdnChkbval.Value;
}
lblCheckBoxes.Text = checkboxes;
}
protected override void Render(System.Web.UI.HtmlTextWriter writer)
{
ClientScript.RegisterForEventValidation("btnRefresh", "Blue Green");
base.Render(writer);
}
If I pass an argument a fixed string to __dopostback it works, otherwise it returns the error:
Invalid postback or callback argument.
I believe in the RegisterForEventValidation() method, the exact value is not declared.
Is there any way to pass a variable string?
Try this:
function CallServerCkhkBox(chkvalue) {
alert("_dopostback " + chkvalue);
__doPostBack('btnRefresh', chkvalue.toString());
}

Categories