@@ -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; | |||
} | |||
} |
@@ -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 |
@@ -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) { |
@@ -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 |
@@ -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; | |||
} |
@@ -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; |
@@ -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; | |||
} | |||
} | |||
} | |||
@@ -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) { |
@@ -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(); |