* Enable row height setting for Tree * Add content mode for captions * Align expander element by default to top The content mode allows use of preformatted and HTML captions that bring value to row height Fixes #9411tags/8.1.0.beta3
@@ -17,6 +17,7 @@ package com.vaadin.client.connectors.grid; | |||
import com.google.gwt.safehtml.shared.SafeHtmlUtils; | |||
import com.google.gwt.user.client.Element; | |||
import com.vaadin.client.annotations.OnStateChange; | |||
import com.vaadin.client.renderers.HtmlRenderer; | |||
import com.vaadin.client.renderers.Renderer; | |||
import com.vaadin.client.widget.grid.RendererCellReference; | |||
@@ -27,7 +28,7 @@ import com.vaadin.ui.Tree.TreeRenderer; | |||
import elemental.json.JsonObject; | |||
/** | |||
* Connector for TreeRenderer | |||
* Connector for TreeRenderer. | |||
* | |||
* @author Vaadin Ltd | |||
* @since 8.1 | |||
@@ -42,9 +43,8 @@ public class TreeRendererConnector | |||
@Override | |||
public void render(RendererCellReference cell, String htmlString) { | |||
String content = "<span class=\"v-captiontext\">" + | |||
SafeHtmlUtils.htmlEscape(htmlString) | |||
+ "</span>"; | |||
String content = "<span class=\"v-captiontext\">" | |||
+ getContentString(htmlString) + "</span>"; | |||
JsonObject row = getParent().getParent().getDataSource() | |||
.getRow(cell.getRowIndex()); | |||
@@ -56,9 +56,30 @@ public class TreeRendererConnector | |||
} | |||
super.render(cell, content); | |||
} | |||
private String getContentString(String htmlString) { | |||
switch (getState().mode) { | |||
case HTML: | |||
return htmlString; | |||
case PREFORMATTED: | |||
return "<pre>" + SafeHtmlUtils.htmlEscape(htmlString) | |||
+ "</pre>"; | |||
default: | |||
return SafeHtmlUtils.htmlEscape(htmlString); | |||
} | |||
} | |||
}; | |||
} | |||
@OnStateChange("mode") | |||
void updateContentMode() { | |||
// Redraw content | |||
getParent().getParent().getWidget().requestRefreshBody(); | |||
// Some pre-formatted content might change size of content. | |||
getParent().getParent().getWidget().recalculateColumnWidths(); | |||
} | |||
@Override | |||
public ColumnConnector getParent() { | |||
return (ColumnConnector) super.getParent(); |
@@ -6381,8 +6381,10 @@ public class Grid<T> extends ResizeComposite implements HasSelectionHandlers<T>, | |||
/** | |||
* Request delayed refresh of all body rows. | |||
* | |||
* @since 8.1 | |||
*/ | |||
private void requestRefreshBody() { | |||
public void requestRefreshBody() { | |||
if (!refreshBodyRequested) { | |||
refreshBodyRequested = true; | |||
Scheduler.get().scheduleFinally(() -> { |
@@ -218,13 +218,14 @@ public class Tree<T> extends Composite | |||
private TreeGrid<T> treeGrid = new TreeGrid<>(); | |||
private ItemCaptionGenerator<T> captionGenerator = String::valueOf; | |||
private IconGenerator<T> iconProvider = t -> null; | |||
private final TreeRenderer renderer; | |||
/** | |||
* Constructs a new Tree Component. | |||
*/ | |||
public Tree() { | |||
setCompositionRoot(treeGrid); | |||
TreeRenderer renderer = new TreeRenderer(); | |||
renderer = new TreeRenderer(); | |||
treeGrid.getDataCommunicator().addDataGenerator(renderer); | |||
treeGrid.addColumn(i -> captionGenerator.apply(i), renderer) | |||
.setId("column"); | |||
@@ -762,4 +763,25 @@ public class Tree<T> extends Composite | |||
public void setComponentError(ErrorMessage componentError) { | |||
treeGrid.setComponentError(componentError); | |||
} | |||
/** | |||
* Sets the height of a row. If -1 (default), the row height is calculated | |||
* based on the theme for an empty row before the Tree is displayed. | |||
* | |||
* @param rowHeight | |||
* The height of a row in pixels or -1 for automatic calculation | |||
*/ | |||
public void setRowHeight(double rowHeight) { | |||
treeGrid.setRowHeight(rowHeight); | |||
} | |||
/** | |||
* Sets the content mode of the item caption. | |||
* | |||
* @param contentMode | |||
* the content mode | |||
*/ | |||
public void setContentMode(ContentMode contentMode) { | |||
renderer.getState().mode = contentMode; | |||
} | |||
} |
@@ -15,6 +15,7 @@ | |||
*/ | |||
package com.vaadin.shared.ui.tree; | |||
import com.vaadin.shared.ui.ContentMode; | |||
import com.vaadin.shared.ui.grid.renderers.AbstractRendererState; | |||
/** | |||
@@ -25,4 +26,5 @@ import com.vaadin.shared.ui.grid.renderers.AbstractRendererState; | |||
*/ | |||
public class TreeRendererState extends AbstractRendererState { | |||
public ContentMode mode = ContentMode.TEXT; | |||
} |
@@ -27,6 +27,9 @@ $v-treegrid-class-depth: depth !default; | |||
.#{$primary-stylename}-expander { | |||
display: inline-block; | |||
vertical-align: top; | |||
&::before { | |||
display: inline-block; | |||
width: $v-treegrid-expander-width; | |||
@@ -62,6 +65,7 @@ $v-treegrid-class-depth: depth !default; | |||
// Expander and cell content in same line | |||
.#{$primary-stylename}-cell-content { | |||
display: inline-block; | |||
vertical-align: middle; | |||
} | |||
.#{$primary-stylename}-row-focused { |
@@ -1,7 +1,11 @@ | |||
package com.vaadin.tests.components.tree; | |||
import java.util.Arrays; | |||
import java.util.Enumeration; | |||
import java.util.List; | |||
import java.util.stream.Collectors; | |||
import java.util.stream.IntStream; | |||
import java.util.stream.Stream; | |||
import com.vaadin.annotations.Theme; | |||
import com.vaadin.annotations.Widgetset; | |||
@@ -12,21 +16,24 @@ import com.vaadin.server.ClassResource; | |||
import com.vaadin.server.ThemeResource; | |||
import com.vaadin.server.VaadinRequest; | |||
import com.vaadin.shared.Registration; | |||
import com.vaadin.shared.ui.ContentMode; | |||
import com.vaadin.tests.components.AbstractTestUIWithLog; | |||
import com.vaadin.tests.data.bean.HierarchicalTestBean; | |||
import com.vaadin.ui.Component; | |||
import com.vaadin.ui.Grid.SelectionMode; | |||
import com.vaadin.ui.IconGenerator; | |||
import com.vaadin.ui.MenuBar; | |||
import com.vaadin.ui.MenuBar.Command; | |||
import com.vaadin.ui.MenuBar.MenuItem; | |||
import com.vaadin.ui.Tree; | |||
import com.vaadin.ui.VerticalLayout; | |||
import com.vaadin.ui.Grid.SelectionMode; | |||
@Theme("tests-valo-disabled-animations") | |||
@Widgetset("com.vaadin.DefaultWidgetSet") | |||
public class TreeBasicFeatures extends AbstractTestUIWithLog { | |||
public static final double[] ROW_HEIGHTS = new double[] { 35.5d, 72.78d }; | |||
private Tree<HierarchicalTestBean> tree; | |||
private TreeDataProvider<HierarchicalTestBean> inMemoryDataProvider; | |||
private IconGenerator<HierarchicalTestBean> iconGenerator = i -> { | |||
@@ -69,7 +76,9 @@ public class TreeBasicFeatures extends AbstractTestUIWithLog { | |||
MenuItem componentMenu = menu.addItem("Component", null); | |||
createIconMenu(componentMenu.addItem("Icons", null)); | |||
createCaptionMenu(componentMenu.addItem("Captions", null)); | |||
createContentModeMenu(componentMenu.addItem("ContentMode", null)); | |||
createSelectionModeMenu(componentMenu.addItem("Selection Mode", null)); | |||
createRowHeightMenu(componentMenu.addItem("Row Height", null)); | |||
componentMenu.addItem("Item Click Listener", new Command() { | |||
private Registration registration; | |||
@@ -110,6 +119,12 @@ public class TreeBasicFeatures extends AbstractTestUIWithLog { | |||
return menu; | |||
} | |||
private void createRowHeightMenu(MenuItem rowHeightMenu) { | |||
Stream.concat(Stream.of(-1d), Arrays.stream(ROW_HEIGHTS).boxed()) | |||
.forEach(height -> rowHeightMenu.addItem(String.valueOf(height), | |||
item -> tree.setRowHeight(height))); | |||
} | |||
private void createSelectionModeMenu(MenuItem modeMenu) { | |||
for (SelectionMode mode : SelectionMode.values()) { | |||
modeMenu.addItem(mode.name(), item -> tree.setSelectionMode(mode)); | |||
@@ -117,13 +132,26 @@ public class TreeBasicFeatures extends AbstractTestUIWithLog { | |||
} | |||
private void createCaptionMenu(MenuItem captionMenu) { | |||
captionMenu.addItem("String.valueOf", | |||
menu -> tree.setItemCaptionGenerator(String::valueOf)); | |||
captionMenu | |||
.addItem("Custom caption", | |||
menu -> tree.setItemCaptionGenerator(i -> "Id: " | |||
+ i.getId() + ", Depth: " + i.getDepth() | |||
+ ", Index: " + i.getIndex())); | |||
captionMenu.addItem("String.valueOf", menu -> { | |||
tree.setItemCaptionGenerator(String::valueOf); | |||
tree.setContentMode(ContentMode.TEXT); | |||
}); | |||
captionMenu.addItem("Custom caption", menu -> { | |||
tree.setItemCaptionGenerator(i -> "Id: " + i.getId() + "\nDepth: " | |||
+ i.getDepth() + ", Index: " + i.getIndex()); | |||
tree.setContentMode(ContentMode.PREFORMATTED); | |||
}); | |||
captionMenu.addItem("HTML caption", menu -> { | |||
tree.setItemCaptionGenerator( | |||
i -> "Id: " + i.getId() + "<br/>Depth: " + i.getDepth() | |||
+ "<br/>Index: " + i.getIndex()); | |||
tree.setContentMode(ContentMode.HTML); | |||
}); | |||
} | |||
private void createContentModeMenu(MenuItem contentModeMenu) { | |||
Arrays.stream(ContentMode.values()).forEach(mode -> contentModeMenu | |||
.addItem(mode.toString(), item -> tree.setContentMode(mode))); | |||
} | |||
private void createIconMenu(MenuItem iconMenu) { |
@@ -1,6 +1,7 @@ | |||
package com.vaadin.tests.components.tree; | |||
import java.io.IOException; | |||
import java.util.Arrays; | |||
import java.util.function.Predicate; | |||
import org.junit.Assert; | |||
@@ -92,7 +93,8 @@ public class TreeBasicFeaturesTest extends MultiBrowserTest { | |||
for (int j = 0; j < 3; ++j) { | |||
item = tree.getItem(n++); | |||
Assert.assertEquals((shouldHaveIcon ? "\ue92d" : "") + "1 | " + j, | |||
Assert.assertEquals( | |||
(shouldHaveIcon ? "\ue92d" : "") + "1 | " + j, | |||
item.getText()); | |||
Assert.assertEquals("Unexpected icon state", shouldHaveIcon, | |||
@@ -111,26 +113,61 @@ public class TreeBasicFeaturesTest extends MultiBrowserTest { | |||
@Test | |||
public void tree_custom_caption() { | |||
// Set row height big enough to show whole content. | |||
selectMenuPath("Component", "Row Height", | |||
String.valueOf(TreeBasicFeatures.ROW_HEIGHTS[1])); | |||
selectMenuPath("Component", "Captions", "Custom caption"); | |||
TreeElement tree = $(TreeElement.class).first(); | |||
Assert.assertEquals("Id: /0/0, Depth: 0, Index: 0", | |||
Assert.assertEquals("Id: /0/0\nDepth: 0, Index: 0", | |||
tree.getItem(0).getText()); | |||
Assert.assertEquals("Id: /0/1, Depth: 0, Index: 1", | |||
Assert.assertEquals("Id: /0/1\nDepth: 0, Index: 1", | |||
tree.getItem(1).getText()); | |||
tree.expand(0); | |||
Assert.assertEquals("Id: /0/0/1/0, Depth: 1, Index: 0", | |||
Assert.assertEquals("Id: /0/0/1/0\nDepth: 1, Index: 0", | |||
tree.getItem(1).getText()); | |||
Assert.assertEquals("Id: /0/0/1/1, Depth: 1, Index: 1", | |||
Assert.assertEquals("Id: /0/0/1/1\nDepth: 1, Index: 1", | |||
tree.getItem(2).getText()); | |||
tree.expand(1); | |||
Assert.assertEquals("Id: /0/0/1/0/2/0, Depth: 2, Index: 0", | |||
Assert.assertEquals("Id: /0/0/1/0/2/0\nDepth: 2, Index: 0", | |||
tree.getItem(2).getText()); | |||
Assert.assertEquals("Id: /0/0/1/0/2/1, Depth: 2, Index: 1", | |||
Assert.assertEquals("Id: /0/0/1/0/2/1\nDepth: 2, Index: 1", | |||
tree.getItem(3).getText()); | |||
assertNoErrorNotifications(); | |||
} | |||
@Test | |||
public void tree_html_caption_and_expander_position() { | |||
// Set row height big enough to show whole content. | |||
selectMenuPath("Component", "Row Height", | |||
String.valueOf(TreeBasicFeatures.ROW_HEIGHTS[1])); | |||
selectMenuPath("Component", "Captions", "HTML caption"); | |||
TreeElement tree = $(TreeElement.class).first(); | |||
Assert.assertEquals("Id: /0/0\nDepth: 0\nIndex: 0", | |||
tree.getItem(0).getText()); | |||
Assert.assertEquals("Expander element not aligned to top", | |||
tree.getExpandElement(0).getLocation().getY(), | |||
tree.getItem(0).getLocation().getY()); | |||
assertNoErrorNotifications(); | |||
} | |||
@Test | |||
public void tree_html_caption_text_mode() { | |||
// Set row height big enough to show whole content. | |||
selectMenuPath("Component", "Captions", "HTML caption"); | |||
selectMenuPath("Component", "ContentMode", "TEXT"); | |||
TreeElement tree = $(TreeElement.class).first(); | |||
Assert.assertEquals("Id: /0/0<br/>Depth: 0<br/>Index: 0", | |||
tree.getItem(0).getText()); | |||
assertNoErrorNotifications(); | |||
} | |||
@Test | |||
public void tree_item_click() { | |||
selectMenuPath("Component", "Item Click Listener"); | |||
@@ -203,4 +240,16 @@ public class TreeBasicFeaturesTest extends MultiBrowserTest { | |||
Assert.assertFalse("First row was not deselected", | |||
wrap.getRow(0).isSelected()); | |||
} | |||
@Test | |||
public void tree_row_heigth() { | |||
TreeElement tree = $(TreeElement.class).first(); | |||
TreeGridElement wrap = tree.wrap(TreeGridElement.class); | |||
Arrays.stream(TreeBasicFeatures.ROW_HEIGHTS).boxed() | |||
.map(String::valueOf).forEach(height -> { | |||
selectMenuPath("Component", "Row Height", height); | |||
Assert.assertTrue(wrap.getCell(0, 0).getAttribute("style") | |||
.contains("height: " + height + "px;")); | |||
}); | |||
} | |||
} |