package com.vaadin.client;
import com.vaadin.shared.ui.ErrorLevel;
+import com.vaadin.shared.ui.label.ContentMode;
import com.vaadin.shared.util.SharedUtil;
/**
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.
&& 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;
+ }
}
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;
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();
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
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;
.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;
}
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) {
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;
* 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();
}
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
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
@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;
+
}
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;
h4 {
color: inherit;
}
+
+ pre.v-tooltip-pre {
+ font: inherit;
+ white-space: pre-wrap;
+ margin: 0;
+ }
}
}
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;
@Override
public String getDescription(RowReference row) {
- return "Row tooltip for row " + row.getItemId();
+ return "<b>Row</b> tooltip\n for row " + row.getItemId();
}
};
@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;
}
});
- 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);
+ }
}
});
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) {
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;
@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();