Application hooks from JavaScript

Hello,

I am looking for a way to hook into my Cuba application from a child browser window. The workflow is this:

  1. A child browser window is opened from the Cuba application.
  2. A user clicks a button on the new window.
  3. Using JavaScript in the child window I’d like to manipulate the Cuba application (open a new screen, for example).

Using DevTools I can inspect the parent window’s properties and functions but I have yet to find a way to do what I’ve described. I see a reference to

 window.opener.vaadin.getApp("app-xxxxx")  

but that looks like a dead end.

Thanks and hat tip to the solid platform you have put together!

Hi,

if you really want to do this through Javascript, i think you have to look at the Vaadin mechanisms to do so.
Here are some Vaadin / CUBA docs on that:

I’m not really sure if this works, but it is probably the best starting point (technically it is definitively interesting). Nevertheless, can you explain a little bit more in detail how you want to communicate through the tabs?

With “1. A child browser window is opened…” you mean a totally different application that will act like a remote for the CUBA app, or are you talking about another screen of the CUBA app that should update the first CUBA app tab?

However, can you explain the use cases that you want to establish besides opening a screen? In fact, opening a screen is easily possible without using javascript hooks at all… I wrote a blog post a few month ago about it: Deep Links for Resources – Road to CUBA and beyond...

Basically since the state of the screens is stored on the web server, when you open a link in the browser (like described in the blog post) CUBA will add another “in-app tab” with the desired screen while the other screen is still accessible, without the need to login etc.)

But depending on other communication patterns you want to do, it might be worth trying out to implement some kind of JS hook.

Bye
Mario

One additional starting point that worked for me. You can directly add JS functions that can be used from the dev tools like this:


public class CustomerBrowse extends AbstractLookup {

    @Override
    public void init(Map<String, Object> params) {
        super.init(params);

        JavaScript.getCurrent().addFunction("myFunction", (JavaScriptFunction) arguments -> {
            showNotification("hello myFunction with " + arguments.length() + " arguments");
        });
    }
}

With this you can open the dev tools and call myFunction(). I don’t really now how to use this in a meaningful way, but at least it is possible :slight_smile:

I want to host a map in a new browser tab (not the Google map that Cuba supports already). The use cases are these:

  1. From a Cuba browse screen, open a new child window that hosts the map. Pass as parameters to the window the id(s) of the selected records in the browse screen.
  2. From the map, select one or more records and pass their id(s) to the Cuba application. It looks like this could be done using the
Javascript.getCurrent().addFunction()

method but I’d want to do this at startup somehow so I could have “myFunction()” available from any screen.

For the second use case, is it possible to programmatically create/update record sets in the folders panel?

Thanks.

But how do you want to communicate from the google map tab back to the CUBA tab only through javascript? This is generally not possible due to the security restrictions of the browsers. See here for more information.

My example with addFunction has been in the CustomerBrowser just because of convenience. You can add it to any screen (or add it to the mainwindow).

For opening the different elements from the CUBA app i would just use openWebPage method in the controller. For opening screens in the CUBA app, you can use the above mentioned method. This should be far easier…

Bye

If you want to make JS function available for any screen - just initialize it in the main window. You can extend mainWindow from CUBA and add your init logic to the ready life cycle method. In this case function will be initialized at mainWindow initialization.

I found a solution. Please let me know if you see any problems with my approach.

Following Yuriy’s advice I extended the main window. In the

ready

method I added some JavaScript to the main window that overrides the

window.open

function in order to maintain a list of open windows (so they may be reused rather than duplicated):

        
StringBuilder scriptBuilder = new StringBuilder();
scriptBuilder.append("var _windows = new Array();");
scriptBuilder.append("var open_ = window.open;");
scriptBuilder.append("window.open = function(url, name, opts) {");
// remove closed windows from the collection
scriptBuilder.append("  discardClosedWindows(url);");
// attempt to find the window (it may already be opened)
scriptBuilder.append("  var window = $.grep(_windows, function(w, i) { return w.location.href.toLowerCase() == url.toLowerCase(); })[0];");
scriptBuilder.append("  if (!window) {");
scriptBuilder.append("    window = open_(url, name, opts);");
scriptBuilder.append("    _windows.push(window);");
scriptBuilder.append("  }");
scriptBuilder.append("  window.focus();");
scriptBuilder.append("  return window;");
scriptBuilder.append("}\n");
// add a function that removes closed windows from the collection
scriptBuilder.append("window.discardClosedWindows = function(url) { /* todo */ }");
// register system functions
JavaScript.eval(scriptBuilder.toString());

Then I created a custom action that allows users to open a new browser window. This results in the new

window.open

function being called (above):


public class CustomAction implements Runnable {
    @Override
    public void run() {
        App app = App.getInstance();
        WebWindowManager wm = app.getWindowManager();

        // calls window.open
        wm.showWebPage("http://...", null);
    }
}

Following Mario’s suggestion I added a function callback for my child windows to use to communicate back to the application:


JavaScript.getCurrent().addFunction("addToFolder", (JavaScriptFunction) arguments -> {
    // do work
}

My child windows can then use the “addToFolder” function:


<html>
<body>
	<button onclick="addToFolder()">Add to Folder</button>
	<script>
		function addToFolder() {
			var items = {};
			items.entity = "Order";
			items.instances = new Array();
			items.instances.push("abc");
			items.instances.push("xyz");
			window.opener.addToFolder(items);
		}
	</script>
</body>
</html>

Thanks for sharing your recipe! It can be slightly improved. You can override CubaBootstrapListener bean and include your JS overriding to the header of HTML page instead of eval.

See this topic for details: Dashboard BootstrapListener - CUBA.Platform

Actually, I do not recommend overriding window.open, it is too invasive and can be broken in the future. You can use your own window.open code with eval (instead of showWebPage call) and at the same time register your child windows somewhere.

Hi,

How do you open the child html window?

Regards,
-n

Hi Mario,
Sorry bothering you, I came here after I have trouble add syntax

Javascript.getCurrent()

When I assemble my code, I keep get exception

error: package com.vaadin.ui does not exist
import com.vaadin.ui.JavaScript;
Build failed with an exception

So how to put library com.vaadin.ui.JavaScript in cuba project to solve that problem?