Browse Source

Allow configuring content modes for Grid cell tooltips (#10396)

tags/7.7.14
Leif Åstrand 6 years ago
parent
commit
002c188e2b

+ 26
- 0
client/src/main/java/com/vaadin/client/TooltipInfo.java View File

@@ -16,6 +16,7 @@
package com.vaadin.client;

import com.vaadin.shared.ui.ErrorLevel;
import com.vaadin.shared.ui.label.ContentMode;
import com.vaadin.shared.util.SharedUtil;

/**
@@ -30,6 +31,8 @@ public class TooltipInfo {

private ErrorLevel errorLevel;

private ContentMode contentMode = ContentMode.HTML;

// Contains the tooltip's identifier. If a tooltip's contents and this
// identifier haven't changed, the tooltip won't be updated in subsequent
// events.
@@ -203,4 +206,27 @@ public class TooltipInfo {
&& SharedUtil.equals(other.errorLevel, errorLevel)
&& other.identifier == identifier);
}

/**
* Gets the tooltip title's content mode.
*
* @since
*
* @return the content mode
*/
public ContentMode getContentMode() {
return contentMode;
}

/**
* Sets the tooltip title's content mode.
*
* @since
*
* @param contentMode
* the content mode to set
*/
public void setContentMode(ContentMode contentMode) {
this.contentMode = contentMode;
}
}

+ 28
- 3
client/src/main/java/com/vaadin/client/VTooltip.java View File

@@ -18,7 +18,9 @@ package com.vaadin.client;
import com.google.gwt.aria.client.LiveValue;
import com.google.gwt.aria.client.RelevantValue;
import com.google.gwt.aria.client.Roles;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.PreElement;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
@@ -48,8 +50,9 @@ import com.vaadin.client.ui.VOverlay;
public class VTooltip extends VOverlay {
private static final String CLASSNAME = "v-tooltip";
private static final int MARGIN = 4;
public static final int TOOLTIP_EVENTS = Event.ONKEYDOWN | Event.ONMOUSEOVER
| Event.ONMOUSEOUT | Event.ONMOUSEMOVE | Event.ONCLICK;
public static final int TOOLTIP_EVENTS = Event.ONKEYDOWN
| Event.ONMOUSEOVER | Event.ONMOUSEOUT | Event.ONMOUSEMOVE
| Event.ONCLICK;
VErrorMessage em = new VErrorMessage();
Element description = DOM.createDiv();

@@ -142,7 +145,29 @@ public class VTooltip extends VOverlay {
em.setVisible(false);
}
if (info.getTitle() != null && !info.getTitle().isEmpty()) {
description.setInnerHTML(info.getTitle());
switch (info.getContentMode()) {
case RAW:
case XML:
case HTML:
description.setInnerHTML(info.getTitle());
break;
case TEXT:
// Cannot use setInnerText because IE8 preserves linebreaks
description.removeAllChildren();
description.appendChild(Document.get().createTextNode(
info.getTitle()));
break;
case PREFORMATTED:
PreElement preElement = Document.get().createPreElement();
preElement.addClassName(CLASSNAME + "-pre");
preElement.setInnerText(info.getTitle());
description.removeAllChildren();
// add preformatted text to dom
description.appendChild(preElement);
break;
default:
break;
}
/*
* Issue #11871: to correctly update the offsetWidth of description
* element we need to clear style width of its parent DIV from old

+ 13
- 3
client/src/main/java/com/vaadin/client/connectors/GridConnector.java View File

@@ -99,6 +99,7 @@ import com.vaadin.shared.ui.grid.GridStaticSectionState;
import com.vaadin.shared.ui.grid.GridStaticSectionState.CellState;
import com.vaadin.shared.ui.grid.GridStaticSectionState.RowState;
import com.vaadin.shared.ui.grid.ScrollDestination;
import com.vaadin.shared.ui.label.ContentMode;

import elemental.json.JsonObject;
import elemental.json.JsonValue;
@@ -1325,10 +1326,12 @@ public class GridConnector extends AbstractHasComponentsConnector
.getObject(GridState.JSONKEY_CELLDESCRIPTION);

if (cellDescriptions != null && cellDescriptions.hasKey(c.id)) {
return new TooltipInfo(cellDescriptions.getString(c.id));
return createCellTooltipInfo(cellDescriptions.getString(c.id),
getState().cellTooltipContentMode);
} else if (row.hasKey(GridState.JSONKEY_ROWDESCRIPTION)) {
return new TooltipInfo(
row.getString(GridState.JSONKEY_ROWDESCRIPTION));
return createCellTooltipInfo(
row.getString(GridState.JSONKEY_ROWDESCRIPTION),
getState().rowTooltipContentMode);
} else {
return null;
}
@@ -1337,6 +1340,13 @@ public class GridConnector extends AbstractHasComponentsConnector
return super.getTooltipInfo(element);
}

private static TooltipInfo createCellTooltipInfo(String text,
ContentMode contentMode) {
TooltipInfo info = new TooltipInfo(text);
info.setContentMode(contentMode);
return info;
}

@Override
protected void sendContextClickEvent(MouseEventDetails details,
EventTarget eventTarget) {

+ 100
- 11
server/src/main/java/com/vaadin/ui/Grid.java View File

@@ -104,6 +104,7 @@ import com.vaadin.shared.ui.grid.selection.MultiSelectionModelServerRpc;
import com.vaadin.shared.ui.grid.selection.MultiSelectionModelState;
import com.vaadin.shared.ui.grid.selection.SingleSelectionModelServerRpc;
import com.vaadin.shared.ui.grid.selection.SingleSelectionModelState;
import com.vaadin.shared.ui.label.ContentMode;
import com.vaadin.shared.util.SharedUtil;
import com.vaadin.ui.Grid.SelectionModel.HasUserSelectionAllowed;
import com.vaadin.ui.declarative.DesignAttributeHandler;
@@ -6663,21 +6664,54 @@ public class Grid extends AbstractFocusable implements SelectionNotifier,
* optional descriptions (tooltips) for individual Grid cells. If a
* {@link RowDescriptionGenerator} is also set, the row description it
* generates is displayed for cells for which {@code generator} returns
* null.
* <code>null</code>.
* <p>
* The descriptions are rendered in the browser as HTML and the developer is
* responsible for ensuring no harmful HTML is used.
*
* @param generator
* the description generator to use or {@code null} to remove a
* previously set generator if any
* the description generator to use or <code>null</code> to
* remove a previously set generator if any
*
* @see #setCellDescriptionGenerator(CellDescriptionGenerator, ContentMode)
* @see #setRowDescriptionGenerator(RowDescriptionGenerator)
*
* @since 7.6
*/
public void setCellDescriptionGenerator(
CellDescriptionGenerator generator) {

public void setCellDescriptionGenerator(CellDescriptionGenerator generator) {
/*
* When porting this to the v7 version in Framework 8, the default
* should be changed to PREFORMATTED to preserve the more secure
* default that has accidentally been used there.
*/
setCellDescriptionGenerator(generator, ContentMode.HTML);
}

/**
* Sets the {@code CellDescriptionGenerator} instance and content mode for
* generating optional descriptions (tooltips) for individual Grid cells. If
* a {@link RowDescriptionGenerator} is also set, the row description it
* generates is displayed for cells for which {@code generator} returns
* <code>null</code>.
*
* @param generator
* the description generator to use or <code>null</code> to
* remove a previously set generator if any
* @param contentMode
* the content mode for cell tooltips, not <code>null</code>
* @see #setRowDescriptionGenerator(RowDescriptionGenerator)
*
* @since
*/
public void setCellDescriptionGenerator(CellDescriptionGenerator generator,
ContentMode contentMode) {
if (contentMode == null) {
throw new IllegalArgumentException("Content mode cannot be null");
}
cellDescriptionGenerator = generator;
getState().hasDescriptions = (generator != null
|| rowDescriptionGenerator != null);
getState().hasDescriptions = (generator != null || rowDescriptionGenerator != null);
getState().cellTooltipContentMode = contentMode;
datasourceExtension.refreshCache();
}

@@ -6693,29 +6727,84 @@ public class Grid extends AbstractFocusable implements SelectionNotifier,
return cellDescriptionGenerator;
}

/**
* Gets the content mode used for cell descriptions.
*
* @return the content mode used for cell descriptions, not
* <code>null</code>
* @see #setCellDescriptionGenerator(CellDescriptionGenerator, ContentMode)
*/
public ContentMode getCellDescriptionContentMode() {
return getState(false).cellTooltipContentMode;
}

/**
* Sets the {@code RowDescriptionGenerator} instance for generating optional
* descriptions (tooltips) for Grid rows. If a
* {@link CellDescriptionGenerator} is also set, the row description
* generated by {@code generator} is used for cells for which the cell
* description generator returns null.
*
* description generator returns <code>null</code>.
* <p>
* The descriptions are rendered in the browser as HTML and the developer is
* responsible for ensuring no harmful HTML is used.
*
* @param generator
* the description generator to use or {@code null} to remove a
* previously set generator if any
* the description generator to use or <code>null</code> to
* remove a previously set generator if any
*
* @see #setRowDescriptionGenerator(RowDescriptionGenerator, ContentMode)
* @see #setCellDescriptionGenerator(CellDescriptionGenerator)
*
* @since 7.6
*/
public void setRowDescriptionGenerator(RowDescriptionGenerator generator) {
/*
* When porting this to the v7 version in Framework 8, the default
* should be changed to PREFORMATTED to preserve the more secure
* default that has accidentally been used there.
*/
setRowDescriptionGenerator(generator, ContentMode.HTML);
}

/**
* Sets the {@code RowDescriptionGenerator} instance for generating optional
* descriptions (tooltips) for Grid rows. If a
* {@link CellDescriptionGenerator} is also set, the row description
* generated by {@code generator} is used for cells for which the cell
* description generator returns <code>null</code>.
*
* @param generator
* the description generator to use or <code>null</code> to
* remove a previously set generator if any
* @param contentMode
* the content mode for row tooltips, not <code>null</code>
*
* @see #setCellDescriptionGenerator(CellDescriptionGenerator)
*
* @since
*/
public void setRowDescriptionGenerator(RowDescriptionGenerator generator,
ContentMode contentMode) {
if (contentMode == null) {
throw new IllegalArgumentException("Content mode cannot be null");
}
rowDescriptionGenerator = generator;
getState().hasDescriptions = (generator != null
|| cellDescriptionGenerator != null);
getState().rowTooltipContentMode = contentMode;
datasourceExtension.refreshCache();
}

/**
* Gets the content mode used for row descriptions.
*
* @return the content mode used for row descriptions, not <code>null</code>
* @see #setRowDescriptionGenerator(RowDescriptionGenerator, ContentMode)
*/
public ContentMode getRowDescriptionContentMode() {
return getState(false).rowTooltipContentMode;
}

/**
* Returns the {@code RowDescriptionGenerator} instance used to generate
* descriptions (tooltips) for Grid rows

+ 25
- 0
shared/src/main/java/com/vaadin/shared/ui/grid/GridState.java View File

@@ -22,6 +22,7 @@ import java.util.List;
import com.vaadin.shared.annotations.DelegateToWidget;
import com.vaadin.shared.data.sort.SortDirection;
import com.vaadin.shared.ui.TabIndexState;
import com.vaadin.shared.ui.label.ContentMode;

/**
* The shared state for the {@link com.vaadin.ui.components.grid.Grid} component
@@ -209,4 +210,28 @@ public class GridState extends TabIndexState {
@DelegateToWidget
public boolean columnReorderingAllowed;

/**
* The content mode used for cell tooltips.
*
* @since
*/
/*
* When porting this to the v7 version in Framework 8, the default should be
* changed to PREFORMATTED to preserve the more secure default that has
* accidentally been used there.
*/
public ContentMode cellTooltipContentMode = ContentMode.HTML;

/**
* The content mode used for row tooltips.
*
* @since
*/
/*
* When porting this to the v7 version in Framework 8, the default should be
* changed to PREFORMATTED to preserve the more secure default that has
* accidentally been used there.
*/
public ContentMode rowTooltipContentMode = ContentMode.HTML;

}

+ 6
- 0
themes/src/main/themes/VAADIN/themes/base/common/common.scss View File

@@ -129,6 +129,12 @@ body &.v-app .v-app-loading {
cursor: default;
background: #fff;
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, .5);

pre.v-tooltip-pre {
font: inherit;
white-space: pre-wrap;
margin: 0;
}
}
.v-tooltip-text {
overflow: auto;

+ 6
- 0
themes/src/main/themes/VAADIN/themes/valo/shared/_tooltip.scss View File

@@ -176,6 +176,12 @@ $v-tooltip-border-radius: $v-border-radius - 1px !default;
h4 {
color: inherit;
}

pre.v-tooltip-pre {
font: inherit;
white-space: pre-wrap;
margin: 0;
}
}
}


+ 35
- 16
uitest/src/main/java/com/vaadin/tests/components/grid/basicfeatures/GridBasicFeatures.java View File

@@ -47,6 +47,7 @@ import com.vaadin.shared.data.sort.SortDirection;
import com.vaadin.shared.ui.grid.ColumnResizeMode;
import com.vaadin.shared.ui.grid.GridStaticCellType;
import com.vaadin.shared.ui.grid.HeightMode;
import com.vaadin.shared.ui.label.ContentMode;
import com.vaadin.tests.components.AbstractComponentTest;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
@@ -139,7 +140,7 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> {

@Override
public String getDescription(RowReference row) {
return "Row tooltip for row " + row.getItemId();
return "<b>Row</b> tooltip\n for row " + row.getItemId();
}
};

@@ -148,7 +149,7 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> {
@Override
public String getDescription(CellReference cell) {
if ("Column 0".equals(cell.getPropertyId())) {
return "Cell tooltip for row " + cell.getItemId()
return "<b>Cell</b> tooltip\n for row " + cell.getItemId()
+ ", column 0";
} else {
return null;
@@ -671,22 +672,41 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> {
}
});

createBooleanAction("Row description generator", "State", false,
new Command<Grid, Boolean>() {
LinkedHashMap<String, ContentMode> contentModes = new LinkedHashMap<String, ContentMode>();
contentModes.put("None", null);
// Abusing an unused value for this special case
contentModes.put("Default", ContentMode.RAW);
contentModes.put("Plain text", ContentMode.TEXT);
contentModes.put("Preformatted", ContentMode.PREFORMATTED);
contentModes.put("HTML", ContentMode.HTML);

createSelectAction("Row description generator", "State", contentModes,
"None", new Command<Grid, ContentMode>() {
@Override
public void execute(Grid c, Boolean value, Object data) {
c.setRowDescriptionGenerator(
value ? rowDescriptionGenerator : null);
public void execute(Grid grid, ContentMode mode, Object data) {
if (mode == null) {
grid.setRowDescriptionGenerator(null);
} else if (mode == ContentMode.RAW) {
grid.setRowDescriptionGenerator(rowDescriptionGenerator);
} else {
grid.setRowDescriptionGenerator(
rowDescriptionGenerator, mode);
}
}
});

createBooleanAction("Cell description generator", "State", false,
new Command<Grid, Boolean>() {
createSelectAction("Cell description generator", "State",
contentModes, "None", new Command<Grid, ContentMode>() {
@Override
public void execute(Grid c, Boolean value, Object data) {
c.setCellDescriptionGenerator(
value ? cellDescriptionGenerator : null);
public void execute(Grid grid, ContentMode mode, Object data) {
if (mode == null) {
grid.setCellDescriptionGenerator(null);
} else if (mode == ContentMode.RAW) {
grid.setCellDescriptionGenerator(cellDescriptionGenerator);
} else {
grid.setCellDescriptionGenerator(
cellDescriptionGenerator, mode);
}
}
});

@@ -1157,10 +1177,9 @@ public class GridBasicFeatures extends AbstractComponentTest<Grid> {
defaultRows, new Command<Grid, GridStaticCellType>() {

@Override
public void execute(Grid grid, GridStaticCellType value,
Object columnIndex) {
final Object propertyId = getColumnProperty(
(Integer) columnIndex);
public void execute(Grid grid,
GridStaticCellType value, Object columnIndex) {
final Object propertyId = getColumnProperty((Integer) columnIndex);
final HeaderCell cell = grid.getDefaultHeaderRow()
.getCell(propertyId);
switch (value) {

+ 99
- 20
uitest/src/test/java/com/vaadin/tests/components/grid/basicfeatures/GridDescriptionGeneratorTest.java View File

@@ -16,6 +16,7 @@
package com.vaadin.tests.components.grid.basicfeatures;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.util.List;
@@ -29,52 +30,130 @@ public class GridDescriptionGeneratorTest extends GridBasicFeaturesTest {
@Test
public void testCellDescription() {
openTestURL();
selectMenuPath("Component", "State", "Cell description generator");
selectCellGenerator("Default");

getGridElement().getCell(1, 0).showTooltip();
String tooltipText = findElement(By.className("v-tooltip-text"))
.getText();
showCellTooltip(1, 0);
String tooltipText = getTooltipText();
assertEquals("Tooltip text", "Cell tooltip for row 1, column 0",
tooltipText);

getGridElement().getCell(1, 1).showTooltip();
showCellTooltip(1, 1);
assertTrue("Tooltip should not be present in cell (1, 1) ",
findElement(By.className("v-tooltip-text")).getText()
.isEmpty());
getTooltipText().isEmpty());
}

@Test
public void testRowDescription() {
openTestURL();
selectMenuPath("Component", "State", "Row description generator");
selectRowGenerator("Default");

getGridElement().getCell(5, 3).showTooltip();
String tooltipText = findElement(By.className("v-tooltip-text"))
.getText();
showCellTooltip(5, 3);
String tooltipText = getTooltipText();
assertEquals("Tooltip text", "Row tooltip for row 5", tooltipText);

getGridElement().getCell(15, 3).showTooltip();
tooltipText = findElement(By.className("v-tooltip-text")).getText();
showCellTooltip(15, 3);
tooltipText = getTooltipText();
assertEquals("Tooltip text", "Row tooltip for row 15", tooltipText);
}

@Test
public void testRowAndCellDescription() {
openTestURL();
selectMenuPath("Component", "State", "Row description generator");
selectMenuPath("Component", "State", "Cell description generator");
selectRowGenerator("Default");
selectCellGenerator("Default");

getGridElement().getCell(5, 0).showTooltip();
String tooltipText = findElement(By.className("v-tooltip-text"))
.getText();
showCellTooltip(5, 0);
String tooltipText = getTooltipText();
assertEquals("Tooltip text", "Cell tooltip for row 5, column 0",
tooltipText);

getGridElement().getCell(5, 3).showTooltip();
tooltipText = findElement(By.className("v-tooltip-text")).getText();
showCellTooltip(5, 3);
tooltipText = getTooltipText();
assertEquals("Tooltip text", "Row tooltip for row 5", tooltipText);
}

@Test
public void testContentTypes() {
openTestURL();

selectCellGenerator("Default");
showCellTooltip(1, 0);
/*
* When porting this to the v7 version in Framework 8, the default
* should be changed to PREFORMATTED to preserve the more secure default
* that has accidentally been used there.
*/
assertHtmlTooltipShown();

selectRowGenerator("Default");
showCellTooltip(1, 1);
/*
* When porting this to the v7 version in Framework 8, the default
* should be changed to PREFORMATTED to preserve the more secure default
* that has accidentally been used there.
*/
assertHtmlTooltipShown();

selectCellGenerator("Plain text");
showCellTooltip(2, 0);
assertPlainTooltipShown();

selectRowGenerator("Plain text");
showCellTooltip(2, 1);
assertPlainTooltipShown();

selectCellGenerator("Preformatted");
showCellTooltip(3, 0);
assertPreTooltipShown();

selectRowGenerator("Preformatted");
showCellTooltip(3, 1);
assertPreTooltipShown();

selectCellGenerator("HTML");
showCellTooltip(4, 0);
assertHtmlTooltipShown();

selectRowGenerator("HTML");
showCellTooltip(4, 1);
assertHtmlTooltipShown();
}

private void assertPreTooltipShown() {
assertTrue("Tooltip should contain <b> as text", getTooltipText()
.contains("<b>"));
assertTrue("Tooltip should contain a newline", getTooltipText()
.contains("\n"));
}

private void assertPlainTooltipShown() {
assertTrue("Tooltip should contain <b> as text", getTooltipText()
.contains("<b>"));
assertFalse("Tooltip should not contain a newline", getTooltipText()
.contains("\n"));
}

private void assertHtmlTooltipShown() {
assertTrue("Tooltip should contain <b> tag",
isElementPresent(By.cssSelector(".v-tooltip-text b")));
}

private void showCellTooltip(int row, int col) {
getGridElement().getCell(row, col).showTooltip();
}

private void selectCellGenerator(String name) {
selectMenuPath("Component", "State", "Cell description generator", name);
}

private void selectRowGenerator(String name) {
selectMenuPath("Component", "State", "Row description generator", name);
}

private String getTooltipText() {
return findElement(By.className("v-tooltip-text")).getText();
}

@Override
public List<DesiredCapabilities> getBrowsersToTest() {
return getBrowsersExcludingFirefox();

Loading…
Cancel
Save