]> source.dussan.org Git - vaadin-framework.git/commitdiff
Add declarative support to Tree (#9881)
authorAleksi Hietanen <aleksi@vaadin.com>
Tue, 29 Aug 2017 08:15:33 +0000 (11:15 +0300)
committerHenri Sara <henri.sara@gmail.com>
Tue, 29 Aug 2017 08:15:33 +0000 (11:15 +0300)
Closes #9838

server/src/main/java/com/vaadin/ui/Tree.java
server/src/test/java/com/vaadin/tests/server/component/abstractcomponent/AbstractComponentDeclarativeTestBase.java
server/src/test/java/com/vaadin/tests/server/component/tree/TreeDeclarativeTest.java [new file with mode: 0644]

index 77d7d2300aeed71ac0df8508076233ffe7b4512a..f5d24c716c5c4f43b241819ff49bb4085ddf56b0 100644 (file)
 package com.vaadin.ui;
 
 import java.lang.reflect.Method;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
+import java.util.UUID;
+
+import org.jsoup.nodes.Attributes;
+import org.jsoup.nodes.Element;
+import org.jsoup.select.Elements;
 
 import com.vaadin.data.Binder;
 import com.vaadin.data.HasHierarchicalDataProvider;
@@ -30,6 +38,7 @@ import com.vaadin.data.TreeData;
 import com.vaadin.data.provider.DataGenerator;
 import com.vaadin.data.provider.DataProvider;
 import com.vaadin.data.provider.HierarchicalDataProvider;
+import com.vaadin.data.provider.HierarchicalQuery;
 import com.vaadin.data.provider.TreeDataProvider;
 import com.vaadin.event.CollapseEvent;
 import com.vaadin.event.CollapseEvent.CollapseListener;
@@ -51,6 +60,10 @@ import com.vaadin.shared.ui.tree.TreeRendererState;
 import com.vaadin.ui.Grid.SelectionMode;
 import com.vaadin.ui.components.grid.DescriptionGenerator;
 import com.vaadin.ui.components.grid.MultiSelectionModelImpl;
+import com.vaadin.ui.components.grid.NoSelectionModel;
+import com.vaadin.ui.components.grid.SingleSelectionModelImpl;
+import com.vaadin.ui.declarative.DesignAttributeHandler;
+import com.vaadin.ui.declarative.DesignContext;
 import com.vaadin.ui.renderers.AbstractRenderer;
 import com.vaadin.util.ReflectTools;
 
@@ -702,6 +715,20 @@ public class Tree<T> extends Composite
         }
     }
 
+    private SelectionMode getSelectionMode() {
+        SelectionModel<T> selectionModel = getSelectionModel();
+        SelectionMode mode = null;
+        if (selectionModel.getClass().equals(SingleSelectionModelImpl.class)) {
+            mode = SelectionMode.SINGLE;
+        } else if (selectionModel.getClass()
+                .equals(TreeMultiSelectionModel.class)) {
+            mode = SelectionMode.MULTI;
+        } else if (selectionModel.getClass().equals(NoSelectionModel.class)) {
+            mode = SelectionMode.NONE;
+        }
+        return mode;
+    }
+
     @Override
     public void setCaption(String caption) {
         treeGrid.setCaption(caption);
@@ -886,6 +913,153 @@ public class Tree<T> extends Composite
         setupContextClickListener();
     }
 
+    @Override
+    public void writeDesign(Element design, DesignContext designContext) {
+        super.writeDesign(design, designContext);
+        Attributes attrs = design.attributes();
+
+        SelectionMode mode = getSelectionMode();
+        if (mode != null) {
+            DesignAttributeHandler.writeAttribute("selection-mode", attrs, mode,
+                    SelectionMode.SINGLE, SelectionMode.class, designContext);
+        }
+        DesignAttributeHandler.writeAttribute("content-mode", attrs,
+                renderer.getState(false).mode, ContentMode.TEXT,
+                ContentMode.class, designContext);
+
+        if (designContext.shouldWriteData(this)) {
+            writeItems(design, designContext);
+        }
+    }
+
+    private void writeItems(Element design, DesignContext designContext) {
+        getDataProvider().fetch(new HierarchicalQuery<>(null, null))
+                .forEach(item -> writeItem(design, designContext, item, null));
+    }
+
+    private void writeItem(Element design, DesignContext designContext, T item,
+            T parent) {
+
+        Element itemElement = design.appendElement("node");
+        itemElement.attr("item", serializeDeclarativeRepresentation(item));
+
+        if (parent != null) {
+            itemElement.attr("parent",
+                    serializeDeclarativeRepresentation(parent));
+        }
+
+        if (getSelectionModel().isSelected(item)) {
+            itemElement.attr("selected", "");
+        }
+
+        Resource icon = getItemIconGenerator().apply(item);
+        DesignAttributeHandler.writeAttribute("icon", itemElement.attributes(),
+                icon, null, Resource.class, designContext);
+
+        String text = getItemCaptionGenerator().apply(item);
+        itemElement.html(
+                Optional.ofNullable(text).map(Object::toString).orElse(""));
+
+        getDataProvider().fetch(new HierarchicalQuery<>(null, item)).forEach(
+                childItem -> writeItem(design, designContext, childItem, item));
+    }
+
+    @Override
+    public void readDesign(Element design, DesignContext designContext) {
+        super.readDesign(design, designContext);
+        Attributes attrs = design.attributes();
+        if (attrs.hasKey("selection-mode")) {
+            setSelectionMode(DesignAttributeHandler.readAttribute(
+                    "selection-mode", attrs, SelectionMode.class));
+        }
+        if (attrs.hasKey("content-mode")) {
+            setContentMode(DesignAttributeHandler.readAttribute("content-mode",
+                    attrs, ContentMode.class));
+        }
+        readItems(design.children());
+    }
+
+    private void readItems(Elements bodyItems) {
+        if (bodyItems.isEmpty()) {
+            return;
+        }
+
+        DeclarativeValueProvider<T> valueProvider = new DeclarativeValueProvider<>();
+        setItemCaptionGenerator(item -> valueProvider.apply(item));
+
+        DeclarativeIconGenerator<T> iconGenerator = new DeclarativeIconGenerator<>(
+                item -> null);
+        setItemIconGenerator(iconGenerator);
+
+        getSelectionModel().deselectAll();
+        List<T> selectedItems = new ArrayList<>();
+        TreeData<T> data = new TreeData<T>();
+
+        for (Element row : bodyItems) {
+            T item = deserializeDeclarativeRepresentation(row.attr("item"));
+            T parent = null;
+            if (row.hasAttr("parent")) {
+                parent = deserializeDeclarativeRepresentation(
+                        row.attr("parent"));
+            }
+            data.addItem(parent, item);
+            if (row.hasAttr("selected")) {
+                selectedItems.add(item);
+            }
+
+            valueProvider.addValue(item, row.html());
+            iconGenerator.setIcon(item, DesignAttributeHandler
+                    .readAttribute("icon", row.attributes(), Resource.class));
+        }
+
+        setDataProvider(new TreeDataProvider<>(data));
+        selectedItems.forEach(getSelectionModel()::select);
+    }
+
+    /**
+     * Deserializes a string to a data item. Used when reading from the
+     * declarative format of this Tree.
+     * <p>
+     * Default implementation is able to handle only {@link String} as an item
+     * type. There will be a {@link ClassCastException} if {@code T } is not a
+     * {@link String}.
+     *
+     * @since
+     *
+     * @see #serializeDeclarativeRepresentation(Object)
+     *
+     * @param item
+     *            string to deserialize
+     * @throws ClassCastException
+     *             if type {@code T} is not a {@link String}
+     * @return deserialized item
+     */
+    @SuppressWarnings("unchecked")
+    protected T deserializeDeclarativeRepresentation(String item) {
+        if (item == null) {
+            return (T) new String(UUID.randomUUID().toString());
+        }
+        return (T) new String(item);
+    }
+
+    /**
+     * Serializes an {@code item} to a string. Used when saving this Tree to its
+     * declarative format.
+     * <p>
+     * Default implementation delegates a call to {@code item.toString()}.
+     *
+     * @since
+     *
+     * @see #deserializeDeclarativeRepresentation(String)
+     *
+     * @param item
+     *            a data item
+     * @return string representation of the {@code item}.
+     */
+    protected String serializeDeclarativeRepresentation(T item) {
+        return item.toString();
+    }
+
     private void setupContextClickListener() {
         if (hasListeners(ContextClickEvent.class)) {
             if (contextClickRegistration == null) {
index ea7e9c77a327b2d3b75de8cad6389c3970bf5f1d..84c2534f849db3723257f7a758ca3dfa7b059732 100644 (file)
@@ -212,7 +212,7 @@ public abstract class AbstractComponentDeclarativeTestBase<T extends AbstractCom
     }
 
     @Test
-    public void sizeUnderfinedDeserialization()
+    public void sizeUndefinedDeserialization()
             throws InstantiationException, IllegalAccessException {
         String design = String.format("<%s/>", getComponentTag());
 
@@ -237,7 +237,7 @@ public abstract class AbstractComponentDeclarativeTestBase<T extends AbstractCom
     }
 
     @Test
-    public void widthUnderfinedDeserialization()
+    public void widthUndefinedDeserialization()
             throws InstantiationException, IllegalAccessException {
         String design = String.format("<%s/>", getComponentTag());
 
diff --git a/server/src/test/java/com/vaadin/tests/server/component/tree/TreeDeclarativeTest.java b/server/src/test/java/com/vaadin/tests/server/component/tree/TreeDeclarativeTest.java
new file mode 100644 (file)
index 0000000..a8f7741
--- /dev/null
@@ -0,0 +1,172 @@
+package com.vaadin.tests.server.component.tree;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.vaadin.data.TreeData;
+import com.vaadin.data.provider.HierarchicalQuery;
+import com.vaadin.shared.ui.ContentMode;
+import com.vaadin.tests.data.bean.Person;
+import com.vaadin.tests.server.component.abstractcomponent.AbstractComponentDeclarativeTestBase;
+import com.vaadin.ui.Grid.SelectionMode;
+import com.vaadin.ui.IconGenerator;
+import com.vaadin.ui.ItemCaptionGenerator;
+import com.vaadin.ui.Tree;
+
+public class TreeDeclarativeTest
+        extends AbstractComponentDeclarativeTestBase<Tree> {
+
+    @Test
+    public void dataSerialization() {
+        Person person1 = createPerson("a");
+        Person person2 = createPerson("a/a");
+        Person person3 = createPerson("a/b");
+        Person person4 = createPerson("a/b/c");
+        Person person5 = createPerson("b");
+
+        TreeData<Person> data = new TreeData<>();
+        data.addItems(null, person1, person5);
+        data.addItems(person1, person2, person3);
+        data.addItem(person3, person4);
+
+        Tree<Person> tree = new Tree<>();
+        tree.setTreeData(data);
+        tree.setItemCaptionGenerator(item -> item.getFirstName());
+        
+        String designString = String.format("<%s>"
+                + "<node item='%s'>%s</node>"
+                + "<node item='%s' parent='%s'>%s</node>"
+                + "<node item='%s' parent='%s'>%s</node>"
+                + "<node item='%s' parent='%s'>%s</node>"
+                + "<node item='%s'>%s</node>"
+                + "</%s>", getComponentTag(),
+                person1.toString(), person1.getFirstName(),
+                person2.toString(), person1.toString(), person2.getFirstName(),
+                person3.toString(), person1.toString(), person3.getFirstName(),
+                person4.toString(), person3.toString(), person4.getFirstName(),
+                person5.toString(), person5.getFirstName(),
+                getComponentTag());
+
+        testWrite(designString, tree, true);
+        Tree<String> readTree = testRead(designString, tree);
+        Assert.assertEquals(2, readTree.getDataProvider()
+                .getChildCount(new HierarchicalQuery<>(null, null)));
+        Assert.assertEquals(2, readTree.getDataProvider().getChildCount(
+                new HierarchicalQuery<>(null, person1.toString())));
+        Assert.assertEquals(1, readTree.getDataProvider().getChildCount(
+                new HierarchicalQuery<>(null, person3.toString())));
+    }
+
+    @Test
+    public void htmlContentMode() {
+        Person person = createPerson("A Person");
+        Tree<Person> tree = new Tree<>();
+        tree.setItems(person);
+        tree.setItemCaptionGenerator(
+                item -> String.format("<b>%s</b>", item.getFirstName()));
+        tree.setContentMode(ContentMode.HTML);
+
+        String designString = String.format(
+                "<%s content-mode='html'><node item='%s'><b>%s</b></node></%s>",
+                getComponentTag(), person.toString(), person.getFirstName(),
+                getComponentTag());
+
+        testWrite(designString, tree, true);
+        testRead(designString, tree);
+    }
+
+    @Test
+    public void selectionMode() {
+        Tree<Person> tree = new Tree<>();
+        tree.setSelectionMode(SelectionMode.MULTI);
+
+        String designString = String.format("<%s selection-mode='multi'></%s>",
+                getComponentTag(), getComponentTag());
+
+        testRead(designString, tree);
+        testWrite(designString, tree, false);
+    }
+
+    @Test
+    @Override
+    public void heightFullDeserialization()
+            throws InstantiationException, IllegalAccessException {
+        // width is full by default
+        String design = String.format("<%s size-full/>", getComponentTag());
+
+        Tree<String> tree = new Tree<>();
+
+        tree.setHeight("100%");
+        testRead(design, tree);
+        testWrite(design, tree);
+    }
+
+    @Test
+    @Override
+    public void sizeUndefinedDeserialization()
+            throws InstantiationException, IllegalAccessException {
+        String design = String.format("<%s size-auto/>", getComponentTag());
+
+        Tree<String> tree = new Tree<>();
+
+        tree.setSizeUndefined();
+        testRead(design, tree);
+        testWrite(design, tree);
+    }
+
+    @Test
+    @Override
+    public void widthFullDeserialization()
+            throws InstantiationException, IllegalAccessException {
+        // width is full by default
+        String design = String.format("<%s/>", getComponentTag());
+
+        Tree<String> tree = new Tree<>();
+
+        tree.setWidth("100%");
+        testRead(design, tree);
+        testWrite(design, tree);
+    }
+
+    @Test
+    @Override
+    public void widthUndefinedDeserialization()
+            throws InstantiationException, IllegalAccessException {
+        String design = String.format("<%s size-auto/>", getComponentTag());
+
+        Tree<String> tree = new Tree<>();
+
+        tree.setWidthUndefined();
+        testRead(design, tree);
+        testWrite(design, tree);
+    }
+
+    @Override
+    protected String getComponentTag() {
+        return "vaadin-tree";
+    }
+
+    @Override
+    protected Class<? extends Tree> getComponentClass() {
+        return Tree.class;
+    }
+
+    @Override
+    protected void assertEquals(String message, Object o1, Object o2) {
+        if (o1 instanceof ItemCaptionGenerator) {
+            Assert.assertTrue(o2 instanceof ItemCaptionGenerator);
+            return;
+        }
+        if (o1 instanceof IconGenerator) {
+            Assert.assertTrue(o2 instanceof IconGenerator);
+            return;
+        }
+        super.assertEquals(message, o1, o2);
+    }
+
+    private Person createPerson(String name) {
+        Person person = new Person();
+        person.setFirstName(name);
+        return person;
+    }
+}