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;
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;
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;
}
}
+ 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);
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) {
--- /dev/null
+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;
+ }
+}