From: Mathieu Baudier Date: Mon, 4 Sep 2023 07:55:00 +0000 (+0200) Subject: Refactor SWT JavaScript support X-Git-Tag: v2.3.16~40 X-Git-Url: https://git.argeo.org/?p=gpl%2Fargeo-suite.git;a=commitdiff_plain;h=0d531e23f44e4cc66d8a8f9837e05ad5d7fd4f20 Refactor SWT JavaScript support --- diff --git a/org.argeo.app.geo.js/src/org.argeo.app.geo.js/MapPart.js b/org.argeo.app.geo.js/src/org.argeo.app.geo.js/MapPart.js index 6635b08..5711233 100644 --- a/org.argeo.app.geo.js/src/org.argeo.app.geo.js/MapPart.js +++ b/org.argeo.app.geo.js/src/org.argeo.app.geo.js/MapPart.js @@ -49,7 +49,7 @@ export default class MapPart { } // - // AcCESSORS + // ACCESSORS // getMapName() { return this.#mapName; diff --git a/swt/org.argeo.app.geo.swt/src/org/argeo/app/geo/swt/SwtJSMapPart.java b/swt/org.argeo.app.geo.swt/src/org/argeo/app/geo/swt/SwtJSMapPart.java index 913636b..2e90900 100644 --- a/swt/org.argeo.app.geo.swt/src/org/argeo/app/geo/swt/SwtJSMapPart.java +++ b/swt/org.argeo.app.geo.swt/src/org/argeo/app/geo/swt/SwtJSMapPart.java @@ -1,71 +1,32 @@ package org.argeo.app.geo.swt; -import java.util.Locale; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.function.Consumer; import java.util.function.Function; -import org.argeo.api.cms.CmsLog; import org.argeo.app.geo.ux.JsImplementation; import org.argeo.app.geo.ux.MapPart; -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.argeo.app.swt.js.SwtBrowserJsPart; import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; /** - * An SWT implementation of {@link MapPart} based on JavaScript execute in a - * {@link Browser} control. + * An SWT implementation of {@link MapPart} based on JavaScript. */ -public class SwtJSMapPart implements MapPart { +public class SwtJSMapPart extends SwtBrowserJsPart implements MapPart { static final long serialVersionUID = 2713128477504858552L; - private final static CmsLog log = CmsLog.getLog(SwtJSMapPart.class); - - private final static String GLOBAL_THIS_ = "globalThis."; - - private final Browser browser; - - private final CompletableFuture pageLoaded = new CompletableFuture<>(); - private String jsImplementation = JsImplementation.OPENLAYERS_MAP_PART.getJsClass(); private final String mapName;// = "argeoMap"; public SwtJSMapPart(String mapName, Composite parent, int style) { + super(parent, style, "/pkg/org.argeo.app.geo.js/index.html"); this.mapName = mapName; - browser = new Browser(parent, 0); - browser.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - - browser.setUrl("/pkg/org.argeo.app.geo.js/index.html"); - browser.addProgressListener(new ProgressListener() { - static final long serialVersionUID = 1L; - - @Override - public void completed(ProgressEvent event) { - try { - // create map - browser.execute(getJsMapVar() + " = new " + jsImplementation + "('" + mapName + "');"); - loadExtensions(); - pageLoaded.complete(true); - } catch (Exception e) { - log.error("Cannot create map in browser", e); - pageLoaded.complete(false); - } - } - - @Override - public void changed(ProgressEvent event) { - } - }); } - public Control getControl() { - return browser; + @Override + protected void init() { + // create map + doExecute(getJsMapVar() + " = new " + jsImplementation + "('" + mapName + "');"); } /* @@ -96,47 +57,8 @@ public class SwtJSMapPart implements MapPart { return callMethod(getJsMapVar(), methodCall, args); } - protected CompletionStage callMethod(String jsObject, String methodCall, Object... args) { - return evaluate(jsObject + '.' + methodCall, args); - } - private String getJsMapVar() { - return GLOBAL_THIS_ + mapName; - } - - /** - * 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("Map " + mapName + " is not initialised."); - Object result = browser.evaluate(String.format(Locale.ROOT, js, args)); - return result; - }); - return res.minimalCompletionStage(); - } - - 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)); - } - - /** To be overridden with calls to {@link #loadExtension(String)}. */ - protected void loadExtensions() { - + return getJsVarName(mapName); } /* @@ -163,18 +85,9 @@ public class SwtJSMapPart implements MapPart { } protected void addCallback(String suffix, Function toDo) { - pageLoaded.thenAccept((ready) -> { - // browser functions must be directly on window (RAP specific) - new BrowserFunction(browser, mapName + "__on" + suffix) { - - @Override - public Object function(Object[] arguments) { - Object result = toDo.apply(arguments); - return result; - } - - }; - browser.execute(getJsMapVar() + ".callbacks['on" + suffix + "']=window." + mapName + "__on" + suffix + ";"); + getReadyStage().thenAccept((ready) -> { + String functionName = createJsFunction(mapName + "__on" + suffix, toDo); + doExecute(getJsMapVar() + ".callbacks['on" + suffix + "']=" + functionName + ";"); callMethod(mapName, "enable" + suffix + "()"); }); } diff --git a/swt/org.argeo.app.swt/src/org/argeo/app/swt/js/SwtBrowserJsPart.java b/swt/org.argeo.app.swt/src/org/argeo/app/swt/js/SwtBrowserJsPart.java new file mode 100644 index 0000000..8ae34d5 --- /dev/null +++ b/swt/org.argeo.app.swt/src/org/argeo/app/swt/js/SwtBrowserJsPart.java @@ -0,0 +1,146 @@ +package org.argeo.app.swt.js; + +import java.util.Locale; +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; + } + + /* + * ACCESSORS + */ + + public Control getControl() { + return browser; + } + +}