Javascript module function in GWT with JsInterop - gwt

Hoping this is way easier than I'm making it - I'm a Java coder, some inner Javascript aspects are a tad unfamiliar to me.
Trying to embed the great CodeJar library inside a GWT panel. There's a pretty nice/simple example for CodeJar:
<script type="module">
import {CodeJar} from './codejar.js'
import {withLineNumbers} from './linenumbers.js';
const editor = document.querySelector('.editor')
const highlight = editor => {
// highlight.js does not trim old tags,
// let's do it by this hack.
editor.textContent = editor.textContent
hljs.highlightBlock(editor)
}
const jar = CodeJar(editor, withLineNumbers(highlight), {
indentOn: /[(\[{]$/
})
jar.updateCode(localStorage.getItem('code'))
jar.onUpdate(code => {
localStorage.setItem('code', code)
})
</script>
The module function itself looks like this:
export function CodeJar(editor, highlight, opt = {}) { ... }
'editor' is a Div reference, and 'highlight' is a callback library function for handling code highlighting.
What I'm battling with is the JsInterop markup and code to make Javascript modules work with GWT. The above has a few aspects which I'm battling with
replacing the "import" such that the javascript module code is available to GWT. Obvioulsy I can just import the js in my top level index.html, but as I understand it JS modules don't become part of the global namespace, they're only usable from the JS module that imports them. Which in my case, presumably needs to be the GWT code.
how to pass the callback function in when recoding the above in GWT
how to get my own 'jar' reference to do own text set/get (replacing the use of local storage)

To load the script and have it available for GWT consumption, you have (at least) 3 possibilities:
use a static import in a <script type=module>, and then assign the CodeJar function to a window property to make it available globally (that could be another global object than window actually)
use a dynamic import() from GWT, using JsInterop and possibly elemental2-promise
use Rollup/Webpack/whatever to turn the CodeJar module into a non-module script so you can use it differently
Next, you need to create JsInterop bindings so you can call it from GWT; something like that (assuming you made CodeJar available globally as window.CodeJar, and using elemental2-dom for HTMLElement, but com.google.gwt.dom.client.Element would work just as well):
#JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "?")
interface CodeJar {
#JsMethod(namespace = JsPackage.GLOBAL, name = "CodeJar")
static native CodeJar newInstance(HTMLElement element, HighlightFn highlight);
#JsMethod(namespace = JsPackage.GLOBAL, name = "CodeJar")
static native CodeJar newInstance(HTMLElement element, HighlightFn highlight, Options opts);
void updateOptions(Options options);
void updateCode(String code);
void onUpdate(UpdateFn cb);
void destroy();
}
#JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "Object")
class Options {
public String tab;
public JsRegExp indentOn;
public boolean spellcheck;
public boolean addClosing;
}
#JsFunction
#FunctionalInterface
interface HighlightFn {
void highlight(HTMLElement e);
}
#JsFunction
#FunctionalInterface
interface UpdateFn {
void onUpdate(String code);
}
With the above code, you should be able to create an editor using something like:
CodeJar jar = CodeJar.newInstance(editor, MyHighlighter::highlight);
If you use a dynamic import(), replace the static methods with instance ones in a #JsType interface representing the module received from the promise.

Related

How to add a custom/dynamic target to a hyperlink

I am using jasperreports-6.14.0. As far as I can tell, there is only one way to add a custom hyperlink target to anything that allows hyperlinks. Please tell me there is a better way (other than putting javascript into my reference expression).
Implement the net.sf.jasperreports.engine.export.JRHyperlinkTargetProducer interface, looking in the hyperlink parameters for a specific, named parameter to return as your target string.
Extend net.sf.jasperreports.engine.export.HtmlExporter and set its targetProducerFactory protected field as an instance of your new custom hyperlink target producer.
It looks like this is the only option, but it just feels like there should be a way to skip step 2 by just setting the targetProducerFactory. It's almost like the Jasper devs started to do exactly that and thought "Nah, I just don't feel right about that. Let's take it out."
I am going to do the above unless some kind soul can show me a better way.
Custom target producers are loaded as extensions by the HTML exporter. You can register extensions either programmatically by creating the HTML exporter using your own JasperReportsContext instance, or package the extension in a jar and have it autodetected by the exporter.
If you control the HTML exporter creation you can pass the extension programmatically:
JRHyperlinkTargetProducer targetProducer = new JRHyperlinkTargetProducer() {
#Override
public String getHyperlinkTarget(JRPrintHyperlink hyperlink) {
return "foo";
}
};
JRHyperlinkTargetProducerMapFactory targetProducerFactory = new JRHyperlinkTargetProducerMapFactory();
targetProducerFactory.addProducer("mycustomtarget", targetProducer);
SimpleJasperReportsContext jasperReportsContext = new SimpleJasperReportsContext();
jasperReportsContext.setExtensions(JRHyperlinkTargetProducerFactory.class,
Collections.singletonList(targetProducerFactory));
HtmlExporter htmlExporter = new HtmlExporter(jasperReportsContext);
If you want to have the extension autodected you need to create a jar that contains a class like this:
public class CustomTargetProducerExtension implements ExtensionsRegistryFactory {
#Override
public ExtensionsRegistry createRegistry(String registryId, JRPropertiesMap properties) {
JRHyperlinkTargetProducer targetProducer = new JRHyperlinkTargetProducer() {
#Override
public String getHyperlinkTarget(JRPrintHyperlink hyperlink) {
return "bar";
}
};
JRHyperlinkTargetProducerMapFactory targetProducerFactory = new JRHyperlinkTargetProducerMapFactory();
targetProducerFactory.addProducer("mycustomtarget", targetProducer);
return new SingletonExtensionRegistry<>(JRHyperlinkTargetProducerFactory.class, targetProducerFactory);
}
}
And put a jasperreports_extension.properties resource in the root of the jar containing the line:
net.sf.jasperreports.extension.registry.factory.my.custom.target.producer=<package>.CustomTargetProducerExtension
Then your custom target producer would be automatically detected for elements that have hyperlinkTarget="mycustomtarget"

Better testing of coffeescript with Jasmine

I've been switching to Coffeescript lately and I consider it a step forward (not alwasy as you'll see). The problem I'm having is that coffeescript class:
class #ComparisonCollection extends Backbone.Collection
is compiled into
(function() {
var ComparisonCollection, _ref,
__hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
ComparisonCollection = (function(_super) {
__extends(ComparisonCollection, _super);
function ComparisonCollection() {
_ref = ComparisonCollection.__super__.constructor.apply(this, arguments);
return _ref;
}
return ComparisonCollection;
})(Backbone.Collection);
}).call(this);
What this means is, Jasmine cannot test it unless I define the whole class in global namespace like this (note # ):
class #ComparisonCollection extends Backbone.Collection
This attaches the object ComparissonCollection to the window object (global namespace) which:
seems to go against the coffeescript encapsulation in the first places
is a solution that makes changing my code to be able to test it
Do you have any better suggestions how to test it without turning everything into window.something
A popular method is using a packaging solution for the browser. I like the simplicity of stitch. Other popular alternatives include RequireJS, Browserify and Ender.
This entails packaging your modules in some manner. In stitch and Browserify, this is exactly like in node, for instance (RequireJS uses AMD modules, which are slightly different).
Example:
// collections/comparison.coffee
class ComparisonCollection extends Backbone.Collection
module.exports = ComparisonCollection
Using it (in your own application as well as in jasmine tests):
ComparisonCollection = require 'collections/comparison'
Alternately:
// collections.coffee
class exports.ComparisonCollection extends Backbone.Collection
Using it:
{ComparisonCollection} = require 'collections'
This also adds the possibility for headless testing, using jasmine-node, so you can easily test everything while building, on a build server etc, in addition to testing it in the browser.

In GWT how can we share objects between javascript and java?

I have a pojo in my class containing some methods to manipulate Maps and Arrays in java. This object is used in RPC calls to carry my configurations. I have a mechanism in which before making any RPC call I execute a javascript function. Now what I really want is to pass my configuration object to this javascript function and this javascript function can manipulate this configuration object and finally this manipulated object will be passed in my RPC call.
So how can I pass my java object to javascript and allow manipulating it?
First, you cannot manipulate Java objects from javascript directly. But what you can do, is to export a set of static methods to javascript and use them to manipulate your objects. This is done in this way:
public void onModuleLoad() {
exportHelloMethod(this);
}
public String exportedMethod(String name) {
// Manipulate your java classes here
// return something to JS
}
// Create a reference in the browser to the static java method
private native void exportHelloMethod(HelloClass instance) /*-{
$wnd.hello = instance#[...]HelloClass::exportedMethod(Ljava/lang/String;);
}-*/;
Fortunately there is a library which allows exporting java methods and classes in a simpler way. It is gwt-exporter, and you have just to implement Exportable in your class and use a set of annotations so as the exporter generator does all the work.
#ExportPackage("jsc")
#Export
public class MyClass implements Exportable {
public void show(String s){
}
}
public void onModuleLoad() {
ExporterUtil.exportAll();
}
Then in javascript you can instanciate and manipulate the class:
var myclass = new jsc.MyClass();
myclass.show('whatever');

Linking to GWT instance method from JSNI does not automatically bind "this"

I am going to file this as a bug report, but I wanted to check if someone here can see something wrong with what I am doing.
When you expose an instance method from a GWT class through JSNI, this works as expected in JavaScript. Since we are cross compiling Java, I would instead expect this to be bound to the instance automatically. For example:
package com.test;
class Foo {
public void instanceFunction() {
this.otherFunction() // will cause an error when called from JSNI!
}
public void otherFunction() {
// does some stuff
}
public native JavaScriptObject getInstanceFunction() /*-{
return this.#com.test.Foo::instanceFunction();
}-*/;
}
Currently the workaround is to bind the function yourself (not very portable):
public native JavaScriptObject getInstanceFunction() /*-{
return this.#com.test.Foo::instanceFunction().bind(this);
}-*/;
This can also be seen as preference, some may prefer that the functions remain unbound. I would say the current functionality is unintuitave and unnecessary. I cannot imagine a use case for having an unbound this directly in Java code. Also, some browsers do not implement bind(1), so my workaround is not robust.
If you want a portable bind, it's as easy as:
var that = this;
return $entry(function() {
return that.#com.test.Foo::instanceFunction()();
});

Passing javascript parameter from external javascript to java

An external javascript gives a number that should be handed over to Java method named mycallback.
I have defined:
Java:
class MyClass {
public static void mycallback(JavaScriptObject number) {
// do something with the number
}
}
Javascript:
$wnd.callback = $entry(#com.package.MyClass::mycallback(Lcom/google/gwt/core/client/JavaScriptObject));
And the Javascript call is:
$wnd.callback(number_from_external_javascript);
But I get error:
JS value of type number, expected com.google.gwt.core.client.JavaScriptObject
And my ultimate goal is to have a java method with parameter type of Integer, not of JavascriptObject. I just thought GWT should wrap javascript objects in JavascriptObject, but it seems it won't.
GWT version is 2.4.
GWT will automatically cast a JS Number value to any Java number primitive type (int, double, etc.), JS String to Java String, and JS Boolean to Java boolean. It'll never pass them as JavaScriptObjects.
If the number cannot be null, then just declare your callback with an int argument. If it can be null, then you'll have to explicitly create an Integer instance, something like:
$wnd.callback = $entry(function(n) {
if (number != null) {
// box into java.lang.Integer
number = #java.lang.Integer::valueOf(I)(n);
}
#com.packge.MyClass::mycallback(Ljava/lang/Integer;)(number);
});
Alternatively, I think you can pass a JS number as a JavaScriptObject if it's a Number object rather than a Number value, so this might work:
$wnd.callback = $entry(function(n) {
n = new Number(n); // "box" as a Number object
#com.packge.MyClass::mycallback(Lcom/google/gwt/core/client/JavaScriptObject;)(n);
});
What about using the gwt-exporter generator to expose your gwt code to js, so you dont have to deal with jsni and you could benefit of the nice features it has (complex objects, arrays, closures, overlays, doclet, etc)
Using gwt-exporter your class just have to implement Exportable and use an annotation to expose your method.
public static class MyClass implements Exportable {
#Export("$wnd.mycallback")
public static void mycallback(long number) {
Window.alert("" + number);
}
}
Add this line to your onmoduleload and leave the compiler to do the work
public void onModuleLoad() {
ExporterUtil.exportAll();
}
Then you can use the method as you said
<script>
window.mycallback(1234)
</script>