Refactor SWT JavaScript support
authorMathieu Baudier <mbaudier@argeo.org>
Mon, 4 Sep 2023 07:55:00 +0000 (09:55 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Mon, 4 Sep 2023 07:55:00 +0000 (09:55 +0200)
org.argeo.app.geo.js/src/org.argeo.app.geo.js/MapPart.js
swt/org.argeo.app.geo.swt/src/org/argeo/app/geo/swt/SwtJSMapPart.java
swt/org.argeo.app.swt/src/org/argeo/app/swt/js/SwtBrowserJsPart.java [new file with mode: 0644]

index 6635b0809e427e56db749e01cf794fa6e2ae02e3..5711233a629767c5647405cbcf40e11b32a2c750 100644 (file)
@@ -49,7 +49,7 @@ export default class MapPart {
        }
 
        //
-       // AcCESSORS
+       // ACCESSORS
        //
        getMapName() {
                return this.#mapName;
index 913636b3acd67d3e559654a0d5ff34935655ab8f..2e909004a92e83ce02c9ae1ec2847a059db4ee9f 100644 (file)
@@ -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<Boolean> 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<Object> 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<Object> evaluate(String js, Object... args) {
-               CompletableFuture<Object> 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<Object[], Object> 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 (file)
index 0000000..8ae34d5
--- /dev/null
@@ -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<Boolean> 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<Boolean> 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<Object> evaluate(String js, Object... args) {
+               CompletableFuture<Object> 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<Object[], Object> 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<Object> 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;
+       }
+
+}