package org.argeo.app.swt.js; import java.util.Arrays; import java.util.Locale; import java.util.StringJoiner; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.function.Function; import org.argeo.api.cms.CmsLog; import org.eclipse.swt.SWT; import org.eclipse.swt.browser.Browser; import org.eclipse.swt.browser.BrowserFunction; import org.eclipse.swt.browser.ProgressEvent; import org.eclipse.swt.browser.ProgressListener; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; /** * A part using a {@link Browser} and remote JavaScript components on the client * side. */ public class SwtBrowserJsPart { private final static CmsLog log = CmsLog.getLog(SwtBrowserJsPart.class); private final static String GLOBAL_THIS_ = "globalThis."; private final Browser browser; private final CompletableFuture pageLoaded = new CompletableFuture<>(); public SwtBrowserJsPart(Composite parent, int style, String url) { this.browser = new Browser(parent, 0); if (parent.getLayout() instanceof GridLayout) browser.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); // TODO other layouts browser.setUrl(url); browser.addProgressListener(new ProgressListener() { static final long serialVersionUID = 1L; @Override public void completed(ProgressEvent event) { try { init(); loadExtensions(); pageLoaded.complete(true); } catch (Exception e) { log.error("Cannot initialise " + url + " in browser", e); pageLoaded.complete(false); } } @Override public void changed(ProgressEvent event) { } }); } /* * LIFECYCLE */ /** Called when the page has been loaded. */ protected void init() { } /** To be overridden with calls to {@link #loadExtension(String)}. */ protected void loadExtensions() { } protected void loadExtension(String url) { // String js = """ // var script = document.createElement("script"); // script.src = '%s'; // document.head.appendChild(script); // """; // browser.evaluate(String.format(Locale.ROOT, js, url)); browser.evaluate(String.format(Locale.ROOT, "import('%s')", url)); } protected CompletionStage getReadyStage() { return pageLoaded.minimalCompletionStage(); } /* * JAVASCRIPT ACCESS */ /** * Execute this JavaScript on the client side after making sure that the page * has been loaded and the map object has been created. * * @param js the JavaScript code, possibly formatted according to * {@link String#format}, with {@link Locale#ROOT} as locale (for * stability of decimal separator, as expected by JavaScript. * @param args the optional arguments of * {@link String#format(String, Object...)} */ protected CompletionStage evaluate(String js, Object... args) { CompletableFuture res = pageLoaded.thenApply((ready) -> { if (!ready) throw new IllegalStateException("Component is not initialised."); Object result = browser.evaluate(String.format(Locale.ROOT, js, args)); return result; }); return res.minimalCompletionStage(); } /** @return the globally usable function name. */ protected String createJsFunction(String name, Function toDo) { // browser functions must be directly on window (RAP specific) new BrowserFunction(browser, name) { @Override public Object function(Object[] arguments) { Object result = toDo.apply(arguments); return result; } }; return "window." + name; } /** Directly executes */ protected void doExecute(String js, Object... args) { browser.execute(String.format(Locale.ROOT, js, args)); } protected CompletionStage callMethod(String jsObject, String methodCall, Object... args) { return evaluate(jsObject + '.' + methodCall, args); } protected String getJsVarName(String name) { return GLOBAL_THIS_ + name; } protected static String toJsArray(int... arr) { return Arrays.toString(arr); } protected static String toJsArray(long... arr) { return Arrays.toString(arr); } protected static String toJsArray(double... arr) { return Arrays.toString(arr); } protected static String toJsArray(String... arr) { return toJsArray((Object[]) arr); } protected static String toJsArray(Object... arr) { StringJoiner sj = new StringJoiner(",", "[", "]"); for (Object o : arr) { sj.add(toJsValue(o)); } return sj.toString(); } protected static String toJsValue(Object o) { if (o instanceof CharSequence) return '\"' + o.toString() + '\"'; else if (o instanceof Number) return o.toString(); else if (o instanceof Boolean) return o.toString(); else return '\"' + o.toString() + '\"'; } /* * ACCESSORS */ public Control getControl() { return browser; } }