You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2020/08/07 10:35:26 UTC

[isis] branch master updated: ISIS-2340: fx: bind UI props and params to backend

This is an automated email from the ASF dual-hosted git repository.

ahuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/isis.git


The following commit(s) were added to refs/heads/master by this push:
     new 3468a4e  ISIS-2340: fx: bind UI props and params to backend
3468a4e is described below

commit 3468a4e9ec8020ed7978247e4454a935eaa15ee4
Author: Andi Huber <ah...@apache.org>
AuthorDate: Fri Aug 7 12:35:07 2020 +0200

    ISIS-2340: fx: bind UI props and params to backend
---
 .../binding/InternalBidirectionalBinding.java      |  90 ++++----
 .../ui/components/text/TextFieldFactory.java       | 232 +++++++++++++++++++--
 2 files changed, 261 insertions(+), 61 deletions(-)

diff --git a/core/commons/src/main/java/org/apache/isis/core/commons/internal/binding/InternalBidirectionalBinding.java b/core/commons/src/main/java/org/apache/isis/core/commons/internal/binding/InternalBidirectionalBinding.java
index bdd8e17..778cc3d 100644
--- a/core/commons/src/main/java/org/apache/isis/core/commons/internal/binding/InternalBidirectionalBinding.java
+++ b/core/commons/src/main/java/org/apache/isis/core/commons/internal/binding/InternalBidirectionalBinding.java
@@ -42,11 +42,11 @@ implements ChangeListener<T>, InternalUtil.WeakListener {
         return binding;
     }
 
-    public static <T> void unbind(Bindable<T> left, Bindable<T> rigth) {
-        checkParameters(left, rigth);
-        val binding = new UntypedBidirectionalBinding(left, rigth);
+    public static <T> void unbind(Bindable<T> left, Bindable<T> right) {
+        checkParameters(left, right);
+        val binding = new UntypedBidirectionalBinding(left, right);
         left.removeListener(binding);
-        rigth.removeListener(binding);
+        right.removeListener(binding);
     }
 
     public static void unbind(Object left, Object right) {
@@ -143,49 +143,53 @@ implements ChangeListener<T>, InternalUtil.WeakListener {
 
         @Override
         public void changed(final Observable<? extends T> observable, final T oldValue, final T newValue) {
-            if (!updating) {
-                final Bindable<T> left = leftRef.get();
-                final Bindable<T> right = rightRef.get();
-                if ((left == null) || (right == null)) {
-                    if (left != null) {
-                        left.removeListener(this);
-                    }
-                    if (right != null) {
-                        right.removeListener(this);
-                    }
+            if (updating) {
+                return;
+            }
+            val left = getLeft();
+            val right = getRight();
+            
+            if ((left == null) || (right == null)) {
+                if (left != null) {
+                    left.removeListener(this);
+                }
+                if (right != null) {
+                    right.removeListener(this);
+                }
+                return;
+            }
+            
+            try {
+                updating = true;
+                if (left == observable) {
+                    right.setValue(newValue);
                 } else {
-                    try {
-                        updating = true;
-                        if (left == observable) {
-                            right.setValue(newValue);
-                        } else {
-                            left.setValue(newValue);
-                        }
-                    } catch (RuntimeException e) {
-                        try {
-                            if (left == observable) {
-                                left.setValue(oldValue);
-                            } else {
-                                right.setValue(oldValue);
-                            }
-                        } catch (Exception e2) {
-                            e2.addSuppressed(e);
-                            unbind(left, right);
-                            throw _Exceptions.unrecoverableFormatted(
-                                    "Bidirectional binding failed with an attempt to restore the "
-                                    + "Observable to the previous value. "
-                                    + "Removing the bidirectional binding from bindables %s and %s",
-                                    ""+left,
-                                    ""+right, 
-                                    e2);
-                        }
-                        throw _Exceptions.unrecoverable(
-                                "Bidirectional binding failed, setting to the previous value", e);
-                    } finally {
-                        updating = false;
+                    left.setValue(newValue);
+                }
+            } catch (RuntimeException e) {
+                try {
+                    if (left == observable) {
+                        left.setValue(oldValue);
+                    } else {
+                        right.setValue(oldValue);
                     }
+                } catch (Exception e2) {
+                    e2.addSuppressed(e);
+                    unbind(left, right);
+                    throw _Exceptions.unrecoverableFormatted(
+                            "Bidirectional binding failed with an attempt to restore the "
+                            + "Observable to the previous value. "
+                            + "Removing the bidirectional binding from bindables %s and %s",
+                            ""+left,
+                            ""+right, 
+                            e2);
                 }
+                throw _Exceptions.unrecoverable(
+                        "Bidirectional binding failed, setting to the previous value", e);
+            } finally {
+                updating = false;
             }
+            
         }
     }
 
diff --git a/incubator/viewers/javafx/ui/src/main/java/org/apache/isis/incubator/viewer/javafx/ui/components/text/TextFieldFactory.java b/incubator/viewers/javafx/ui/src/main/java/org/apache/isis/incubator/viewer/javafx/ui/components/text/TextFieldFactory.java
index 800ddfd..50ffb4b 100644
--- a/incubator/viewers/javafx/ui/src/main/java/org/apache/isis/incubator/viewer/javafx/ui/components/text/TextFieldFactory.java
+++ b/incubator/viewers/javafx/ui/src/main/java/org/apache/isis/incubator/viewer/javafx/ui/components/text/TextFieldFactory.java
@@ -18,20 +18,34 @@
  */
 package org.apache.isis.incubator.viewer.javafx.ui.components.text;
 
+import java.lang.ref.WeakReference;
+import java.util.Objects;
+
 import javax.inject.Inject;
 
 import org.springframework.core.annotation.Order;
 
 import org.apache.isis.applib.annotation.OrderPrecedence;
+import org.apache.isis.core.commons.binding.Bindable;
+import org.apache.isis.core.commons.binding.ChangeListener;
+import org.apache.isis.core.commons.binding.Observable;
+import org.apache.isis.core.commons.internal.base._Casts;
+import org.apache.isis.core.commons.internal.exceptions._Exceptions;
 import org.apache.isis.core.metamodel.facets.value.string.StringValueFacet;
 import org.apache.isis.core.metamodel.interactions.managed.ManagedParameter;
 import org.apache.isis.core.metamodel.interactions.managed.ManagedProperty;
+import org.apache.isis.core.metamodel.spec.ManagedObject;
+import org.apache.isis.core.metamodel.spec.ManagedObjects;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 import org.apache.isis.incubator.viewer.javafx.ui.components.UiComponentHandlerFx;
 import org.apache.isis.viewer.common.model.binding.UiComponentFactory.ComponentRequest;
 
+import lombok.NonNull;
 import lombok.RequiredArgsConstructor;
 import lombok.val;
 
+import javafx.beans.property.Property;
+import javafx.beans.value.ObservableValue;
 import javafx.scene.Node;
 import javafx.scene.control.TextField;
 
@@ -48,33 +62,215 @@ public class TextFieldFactory implements UiComponentHandlerFx {
     @Override
     public Node handle(ComponentRequest request) {
 
-        //TODO 1) move all the logic that is in the request to the underlying ManagedProperty
-        // 2) pass the ManagedProperty over with the request object
-        // 3) design for an API to bind a ManagedProperty to a FormField, also make sure this works
-        // with Vaadin's FormLayout/Field API
-//        val textValue = request.getFeatureValue(String.class)
-//                .orElse("");
-
         val uiComponent = new TextField();
-        
+        val valueSpec = request.getFeatureTypeSpec();
+        val converter = Converter.<String>of(valueSpec);
+
         if(request.getManagedFeature() instanceof ManagedParameter) {
-            
+
             val managedParameter = (ManagedParameter)request.getManagedFeature();
-            
-//            uiComponent.textProperty().
-//            
-//            managedParameter.validate(proposedValue)
-            
-            //TODO bind to parameter model
-            
+
+            bindBidirectional(
+                    uiComponent.textProperty(),
+                    managedParameter.getValue(),
+                    converter);
+
+            //TODO bind parameter validation feedback
+
         } else if(request.getManagedFeature() instanceof ManagedProperty) {
-            
+
             val managedProperty = (ManagedProperty)request.getManagedFeature();
-            //TODO bind to property model
+
+            // readonly binding
+            bind(
+                    uiComponent.textProperty(),
+                    managedProperty.getValue(),
+                    converter);
+
+            //TODO allow property editing
+            //TODO bind property validation feedback
         }
 
         return uiComponent;
     }
 
+    // -- TODO these binding helpers need to move (for reuse) ...
+    
+    @RequiredArgsConstructor(staticName = "of")
+    private static final class Converter<T> {
+
+        private final ObjectSpecification valueSpec;
+
+        public T unwrap(ManagedObject object) {
+            return _Casts.uncheckedCast(ManagedObjects.UnwrapUtil.single(object));
+        }
+
+        public ManagedObject wrap(T pojo) {
+            return ManagedObject.of(valueSpec, pojo);
+        }
+    }
+    
+    private static class BidirBinding<T> 
+    implements 
+        javafx.beans.value.ChangeListener<T>, 
+        ChangeListener<ManagedObject>{
+
+        private final WeakReference<Property<T>> leftRef;
+        private final WeakReference<Bindable<ManagedObject>> rightRef;
+        private final Converter<T> converter;
+        private boolean updating = false;
+        private final int cachedHash;
+
+        private Property<T> getLeft() {
+            return leftRef.get();
+        }
+
+        private Bindable<ManagedObject> getRight() {
+            return rightRef.get();
+        }
+        
+        
+        public BidirBinding(
+                final @NonNull Property<T> left, 
+                final @NonNull Bindable<ManagedObject> right, 
+                final @NonNull Converter<T> converter) {
+
+            this.leftRef = new WeakReference<>(left);
+            this.rightRef = new WeakReference<>(right);
+            this.converter = converter;
+            cachedHash = Objects.hash(left, right, converter);
+        }
+
+        @Override
+        public void changed(ObservableValue<? extends T> leftObservable, T oldValue, T newValue) {
+            changed(oldValue, newValue, null, null);
+        }
+
+        @Override
+        public void changed(
+                Observable<? extends ManagedObject> rightObservable, 
+                ManagedObject oldValue,
+                ManagedObject newValue) {
+            changed(null, null, oldValue, newValue);
+        }
+        
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+
+            final Object left = getLeft();
+            final Object right = getRight();
+            if ((left == null) || (right == null)) {
+                return false;
+            }
+
+            if (obj instanceof BidirBinding) {
+                final BidirBinding<?> otherBinding = (BidirBinding<?>) obj;
+                final Object otherLeft = otherBinding.getLeft();
+                final Object otherRight = otherBinding.getRight();
+                if ((otherLeft == null) || (otherRight == null)) {
+                    return false;
+                }
+
+                if (left == otherLeft && right == otherRight) {
+                    return true;
+                }
+                if (left == otherRight && right == otherLeft) {
+                    return true;
+                }
+            }
+            return false;
+        }
+        
+        @Override
+        public int hashCode() {
+            return cachedHash;
+        }
+        
+        // -- HELPER
+        
+        private void changed(T oldPojo, T newPojo, ManagedObject oldValue, ManagedObject newValue) {
+            if (updating) {
+                return;
+            }
+            val left = getLeft();
+            val right = getRight();
+            if(!isStillBound(left, right)) {
+                return;
+            }
+            
+            try {
+                updating = true;
+                if(newValue!=null) {
+                    left.setValue(converter.unwrap(newValue));
+                } else {
+                    right.setValue(converter.wrap(newPojo));
+                }
+            } catch (RuntimeException e) {
+                try {
+                    if(newValue!=null) {
+                        left.setValue(converter.unwrap(oldValue));
+                    } else {
+                        right.setValue(converter.wrap(oldPojo));
+                    }
+                } catch (Exception e2) {
+                    e2.addSuppressed(e);
+                    left.removeListener(this);
+                    right.removeListener(this);
+                    throw _Exceptions.unrecoverableFormatted(
+                            "Bidirectional binding failed with an attempt to restore the "
+                            + "Observable to the previous value. "
+                            + "Removing the bidirectional binding from bindables %s and %s",
+                            ""+left,
+                            ""+right, 
+                            e2);
+                }
+                throw _Exceptions.unrecoverable(
+                        "Bidirectional binding failed, setting to the previous value", e);
+            } finally {
+                updating = false;
+            }
+        }
+        
+        private boolean isStillBound(
+                final Property<T> left, 
+                final Bindable<ManagedObject> right) {
+            
+            if ((left == null) || (right == null)) {
+                if (left != null) {
+                    left.removeListener(this);
+                }
+                if (right != null) {
+                    right.removeListener(this);
+                }
+                return false;
+            }
+            return true;
+        }
+
+    }
+
+    public static <L, R> void bind(
+            final @NonNull Property<L> leftProperty, 
+            final @NonNull Observable<ManagedObject> rightObservable, 
+            final @NonNull Converter<L> converter) {
+
+        leftProperty.setValue(converter.unwrap(rightObservable.getValue()));
+        rightObservable.addListener((e,o,n)->{
+            leftProperty.setValue(converter.unwrap(n));
+        });
+    }
+
+    public static <L, R> void bindBidirectional(
+            final @NonNull Property<L> leftProperty, 
+            final @NonNull Bindable<ManagedObject> rightProperty, 
+            final @NonNull Converter<L> converter) {
+        final BidirBinding<L> binding = new BidirBinding<L>(leftProperty, rightProperty, converter);
+        leftProperty.setValue(converter.unwrap(rightProperty.getValue()));
+        leftProperty.addListener(binding);
+        rightProperty.addListener(binding);
+    }
 
 }