]> source.dussan.org Git - vaadin-framework.git/commitdiff
Initial import of TypedForm component feature/databinding
authorTeemu Suo-Anttila <teemusa@vaadin.com>
Thu, 11 Feb 2016 09:48:25 +0000 (10:48 +0100)
committerVaadin Code Review <review@vaadin.com>
Mon, 14 Mar 2016 10:56:39 +0000 (10:56 +0000)
TypedForm determines the fields from the type class and generates
textfields and datefields for editing purposes.

Change-Id: I1bbc0e77190a3fc5a74d7339a3127e8a351a01df

server/src/com/vaadin/ui/proto/TypedForm.java [new file with mode: 0644]
uitest/src/com/vaadin/tests/proto/TypedFormUI.java [new file with mode: 0644]

diff --git a/server/src/com/vaadin/ui/proto/TypedForm.java b/server/src/com/vaadin/ui/proto/TypedForm.java
new file mode 100644 (file)
index 0000000..d95085c
--- /dev/null
@@ -0,0 +1,227 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui.proto;
+
+import java.beans.IntrospectionException;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import com.vaadin.data.util.BeanUtil;
+import com.vaadin.event.ShortcutAction.KeyCode;
+import com.vaadin.server.communication.data.typed.DataSource;
+import com.vaadin.shared.util.SharedUtil;
+import com.vaadin.ui.AbstractField;
+import com.vaadin.ui.Button;
+import com.vaadin.ui.Button.ClickEvent;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.DateField;
+import com.vaadin.ui.FormLayout;
+import com.vaadin.ui.HorizontalLayout;
+import com.vaadin.ui.TextField;
+import com.vaadin.ui.themes.ValoTheme;
+
+/**
+ * Generic TypedForm component. This class provides some automatic field
+ * detection to make form creation easier. This class is meant to be customised
+ * by extending it.
+ * 
+ * @since
+ * @param <T>
+ *            form data type
+ */
+public class TypedForm<T> extends FormLayout {
+
+    private Map<String, AbstractField<?>> fields = new LinkedHashMap<String, AbstractField<?>>();
+    // TODO: Figure out the serialization of this.
+    private Map<AbstractField<?>, Method> getters = new HashMap<AbstractField<?>, Method>();
+    private Map<AbstractField<?>, Method> setters = new HashMap<AbstractField<?>, Method>();
+    protected DataSource<T> dataSource;
+    protected T data = null;
+
+    public TypedForm() {
+        setEnabled(false);
+    }
+
+    public TypedForm(Class<T> cls) {
+        this();
+        generateFields(cls);
+    }
+
+    public TypedForm(Class<T> cls, DataSource<T> dataSource) {
+        this(cls);
+        setDataSource(dataSource);
+    }
+
+    public void setDataSource(DataSource<T> dataSource) {
+        this.dataSource = dataSource;
+    }
+
+    public void generateFields(Class<T> cls) {
+        try {
+            List<PropertyDescriptor> props = BeanUtil
+                    .getBeanPropertyDescriptor(cls);
+
+            for (PropertyDescriptor p : props) {
+                if (p.getName().equals("class") || p.getReadMethod() == null) {
+                    continue;
+                }
+
+                // TODO: improve the type/editor combo detection
+                AbstractField<?> f;
+                if (p.getPropertyType().isAssignableFrom(Date.class)) {
+                    f = new DateField(SharedUtil.camelCaseToHumanFriendly(p
+                            .getName()));
+                } else if (p.getPropertyType().isAssignableFrom(String.class)) {
+                    TextField textField = new TextField(
+                            SharedUtil.camelCaseToHumanFriendly(p.getName()));
+                    textField.setNullRepresentation("");
+                    f = textField;
+
+                } else {
+                    continue;
+                }
+
+                if (p.getWriteMethod() == null) {
+                    f.setReadOnly(true);
+                }
+
+                fields.put(p.getName(), f);
+                getters.put(f, p.getReadMethod());
+                setters.put(f, p.getWriteMethod());
+            }
+
+        } catch (IntrospectionException e) {
+            throw new IllegalArgumentException(
+                    "Unable to detect fields from given type "
+                            + cls.getSimpleName(), e);
+        }
+
+        for (Entry<String, AbstractField<?>> e : fields.entrySet()) {
+            addComponent(e.getValue());
+        }
+    }
+
+    public void removeField(String name) {
+        if (fields.containsKey(name)) {
+            removeComponent(fields.remove(name));
+        }
+    }
+
+    public void edit(T data) {
+        this.data = data;
+
+        setEnabled(data != null);
+        if (data == null) {
+            for (AbstractField<?> f : fields.values()) {
+                f.clear();
+            }
+            return;
+        }
+
+        for (AbstractField<?> f : getters.keySet()) {
+            setValueWithGetter(f, getters.get(f));
+        }
+    }
+
+    public void clear() {
+        edit(null);
+    }
+
+    protected void save() {
+        if (data == null) {
+            // NO-OP
+            return;
+        }
+
+        for (AbstractField<?> f : setters.keySet()) {
+            storeValueWithSetter(f, setters.get(f));
+        }
+
+        if (dataSource != null) {
+            dataSource.save(data);
+        }
+    }
+
+    protected void cancel() {
+        clear();
+    }
+
+    @SuppressWarnings("unchecked")
+    private <V> void setValueWithGetter(AbstractField<V> field, Method getter) {
+        try {
+            field.setValue((V) getter.invoke(data));
+        } catch (Exception e) {
+            // TODO: Exception handling
+            e.printStackTrace();
+        }
+    }
+
+    private <V> void storeValueWithSetter(AbstractField<V> field, Method setter) {
+        try {
+            setter.invoke(data, field.getValue());
+        } catch (Exception e) {
+            // TODO: Exception handling
+            e.printStackTrace();
+        }
+    }
+
+    public Component getButtonLayout() {
+        Button saveButton = new Button("Save", new Button.ClickListener() {
+
+            @Override
+            public void buttonClick(ClickEvent event) {
+                save();
+            }
+        });
+        saveButton.setStyleName(ValoTheme.BUTTON_PRIMARY);
+        saveButton.setClickShortcut(KeyCode.ENTER);
+        Button cancelButton = new Button("Cancel", new Button.ClickListener() {
+
+            @Override
+            public void buttonClick(ClickEvent event) {
+                cancel();
+            }
+        });
+        HorizontalLayout layout = new HorizontalLayout(saveButton, cancelButton);
+        layout.setSpacing(true);
+        return layout;
+    }
+
+    public void setFields(String... fieldNames) {
+        List<String> names = Arrays.asList(fieldNames);
+        removeAllComponents();
+
+        List<AbstractField<?>> order = new ArrayList<AbstractField<?>>();
+        for (String s : names) {
+            if (fields.containsKey(s)) {
+                AbstractField<?> c = fields.get(s);
+                order.add(c);
+            }
+        }
+
+        for (AbstractField<?> f : order) {
+            addComponent(f);
+        }
+    }
+}
diff --git a/uitest/src/com/vaadin/tests/proto/TypedFormUI.java b/uitest/src/com/vaadin/tests/proto/TypedFormUI.java
new file mode 100644 (file)
index 0000000..9e0f48c
--- /dev/null
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.tests.proto;
+
+import java.util.Date;
+
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.server.communication.data.typed.CollectionDataSource;
+import com.vaadin.server.communication.data.typed.DataSource;
+import com.vaadin.tests.components.AbstractTestUI;
+import com.vaadin.ui.Alignment;
+import com.vaadin.ui.Button;
+import com.vaadin.ui.Button.ClickEvent;
+import com.vaadin.ui.Button.ClickListener;
+import com.vaadin.ui.HorizontalLayout;
+import com.vaadin.ui.proto.ListBox;
+import com.vaadin.ui.proto.TypedForm;
+
+public class TypedFormUI extends AbstractTestUI {
+
+    public static class MyPerson {
+
+        private String firstName;
+        private String lastName;
+
+        private Date birthDate;
+
+        public MyPerson(String firstName, String lastName, Date birthDate) {
+            setFirstName(firstName);
+            setLastName(lastName);
+            setBirthDate(birthDate);
+        }
+
+        public MyPerson() {
+        }
+
+        public String getFirstName() {
+            return firstName;
+        }
+
+        public void setFirstName(String firstName) {
+            this.firstName = firstName;
+        }
+
+        public String getLastName() {
+            return lastName;
+        }
+
+        public void setLastName(String lastName) {
+            this.lastName = lastName;
+        }
+
+        public Date getBirthDate() {
+            return birthDate;
+        }
+
+        public void setBirthDate(Date birthDate) {
+            this.birthDate = birthDate;
+        }
+
+        @Override
+        public String toString() {
+            return getLastName() + ", " + getFirstName();
+        }
+    }
+
+    @Override
+    protected void setup(VaadinRequest request) {
+        DataSource<MyPerson> data = new CollectionDataSource<MyPerson>();
+        final TypedForm<MyPerson> form = new TypedForm<MyPerson>(
+                MyPerson.class, data) {
+
+            @Override
+            protected void save() {
+                super.save();
+
+                // We want to disable the form when saving.
+                clear();
+            }
+        };
+
+        form.addComponent(form.getButtonLayout());
+
+        HorizontalLayout layout = new HorizontalLayout();
+        layout.setSpacing(true);
+        layout.setMargin(true);
+
+        final ListBox<MyPerson> listBox = new ListBox<MyPerson>(data);
+        layout.addComponent(listBox);
+        layout.addComponent(new Button("Edit", new ClickListener() {
+
+            @Override
+            public void buttonClick(ClickEvent event) {
+                if (listBox.getSelected() != null) {
+                    form.edit(listBox.getSelected());
+                }
+            }
+        }));
+        Button newButton = new Button("New MyPerson", new ClickListener() {
+            @Override
+            public void buttonClick(ClickEvent event) {
+                form.edit(new MyPerson());
+            }
+        });
+        layout.addComponent(newButton);
+
+        layout.setWidth("100%");
+        layout.setComponentAlignment(newButton, Alignment.MIDDLE_RIGHT);
+        layout.setExpandRatio(newButton, 1.0f);
+
+        addComponent(layout);
+        addComponent(form);
+    }
+}
\ No newline at end of file