From 405262d805f68979c8aa4c1dc25614d7bf9e80cf Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Mon, 8 Dec 2014 20:13:20 +0200 Subject: [PATCH] Split and capitalize header captions by default Only done on the server side where we have property ids to generate the default headers from. On the client side you must always specify the header. Change-Id: Ic743fb3f52517116193b022cfdd2df7dea8dd487 --- .../com/vaadin/ui/DefaultFieldFactory.java | 38 +---- server/src/com/vaadin/ui/Grid.java | 4 +- .../server/component/grid/GridColumns.java | 8 +- .../com/vaadin/shared/util/SharedUtil.java | 136 ++++++++++++++++++ .../vaadin/shared/util/SharedUtilTests.java | 66 +++++++-- .../components/grid/GridColspansTest.java | 6 +- 6 files changed, 200 insertions(+), 58 deletions(-) diff --git a/server/src/com/vaadin/ui/DefaultFieldFactory.java b/server/src/com/vaadin/ui/DefaultFieldFactory.java index ad6461686c..535943bcd5 100644 --- a/server/src/com/vaadin/ui/DefaultFieldFactory.java +++ b/server/src/com/vaadin/ui/DefaultFieldFactory.java @@ -20,6 +20,7 @@ import java.util.Date; import com.vaadin.data.Container; import com.vaadin.data.Item; import com.vaadin.data.Property; +import com.vaadin.shared.util.SharedUtil; /** * This class contains a basic implementation for both {@link FormFieldFactory} @@ -75,42 +76,7 @@ public class DefaultFieldFactory implements FormFieldFactory, TableFieldFactory * @return the formatted caption string */ public static String createCaptionByPropertyId(Object propertyId) { - String name = propertyId.toString(); - if (name.length() > 0) { - - int dotLocation = name.lastIndexOf('.'); - if (dotLocation > 0 && dotLocation < name.length() - 1) { - name = name.substring(dotLocation + 1); - } - if (name.indexOf(' ') < 0 - && name.charAt(0) == Character.toLowerCase(name.charAt(0)) - && name.charAt(0) != Character.toUpperCase(name.charAt(0))) { - StringBuffer out = new StringBuffer(); - out.append(Character.toUpperCase(name.charAt(0))); - int i = 1; - - while (i < name.length()) { - int j = i; - for (; j < name.length(); j++) { - char c = name.charAt(j); - if (Character.toLowerCase(c) != c - && Character.toUpperCase(c) == c) { - break; - } - } - if (j == name.length()) { - out.append(name.substring(i)); - } else { - out.append(name.substring(i, j)); - out.append(" " + name.charAt(j)); - } - i = j + 1; - } - - name = out.toString(); - } - } - return name; + return SharedUtil.propertyIdToHumanFriendly(propertyId); } /** diff --git a/server/src/com/vaadin/ui/Grid.java b/server/src/com/vaadin/ui/Grid.java index b9f0ec86aa..b8330e5efc 100644 --- a/server/src/com/vaadin/ui/Grid.java +++ b/server/src/com/vaadin/ui/Grid.java @@ -76,6 +76,7 @@ import com.vaadin.shared.ui.grid.HeightMode; import com.vaadin.shared.ui.grid.ScrollDestination; import com.vaadin.shared.ui.grid.SortDirection; import com.vaadin.shared.ui.grid.SortEventOriginator; +import com.vaadin.shared.util.SharedUtil; import com.vaadin.ui.components.grid.Renderer; import com.vaadin.ui.components.grid.SortOrderChangeEvent; import com.vaadin.ui.components.grid.SortOrderChangeListener; @@ -2119,7 +2120,8 @@ public class Grid extends AbstractComponent implements SelectionChangeNotifier, header.addColumn(datasourcePropertyId); footer.addColumn(datasourcePropertyId); - column.setHeaderCaption(String.valueOf(datasourcePropertyId)); + column.setHeaderCaption(SharedUtil.camelCaseToHumanFriendly(String + .valueOf(datasourcePropertyId))); return column; } diff --git a/server/tests/src/com/vaadin/tests/server/component/grid/GridColumns.java b/server/tests/src/com/vaadin/tests/server/component/grid/GridColumns.java index e9b33ba879..366176c3fa 100644 --- a/server/tests/src/com/vaadin/tests/server/component/grid/GridColumns.java +++ b/server/tests/src/com/vaadin/tests/server/component/grid/GridColumns.java @@ -34,6 +34,7 @@ import com.vaadin.data.util.IndexedContainer; import com.vaadin.server.KeyMapper; import com.vaadin.shared.ui.grid.GridColumnState; import com.vaadin.shared.ui.grid.GridState; +import com.vaadin.shared.util.SharedUtil; import com.vaadin.ui.Grid; import com.vaadin.ui.Grid.Column; @@ -79,9 +80,10 @@ public class GridColumns { Column column = grid.getColumn(propertyId); assertNotNull(column); - // Property id should be the column header by default - assertEquals(propertyId.toString(), grid.getDefaultHeaderRow() - .getCell(propertyId).getText()); + // Humanized property id should be the column header by default + assertEquals( + SharedUtil.camelCaseToHumanFriendly(propertyId.toString()), + grid.getDefaultHeaderRow().getCell(propertyId).getText()); } } diff --git a/shared/src/com/vaadin/shared/util/SharedUtil.java b/shared/src/com/vaadin/shared/util/SharedUtil.java index 7276f418fa..b40d8f03bb 100644 --- a/shared/src/com/vaadin/shared/util/SharedUtil.java +++ b/shared/src/com/vaadin/shared/util/SharedUtil.java @@ -60,4 +60,140 @@ public class SharedUtil implements Serializable { */ public static final String SIZE_PATTERN = "^(-?\\d*(?:\\.\\d+)?)(%|px|em|rem|ex|in|cm|mm|pt|pc)?$"; + /** + * Splits a camelCaseString into an array of words with the casing + * preserved. + * + * @since 7.4 + * @param camelCaseString + * The input string in camelCase format + * @return An array with one entry per word in the input string + */ + public static String[] splitCamelCase(String camelCaseString) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < camelCaseString.length(); i++) { + char c = camelCaseString.charAt(i); + if (Character.isUpperCase(c) && isWordComplete(camelCaseString, i)) { + sb.append(' '); + } + sb.append(c); + } + return sb.toString().split(" "); + } + + private static boolean isWordComplete(String camelCaseString, int i) { + if (i == 0) { + // Word can't end at the beginning + return false; + } else if (!Character.isUpperCase(camelCaseString.charAt(i - 1))) { + // Word ends if previous char wasn't upper case + return true; + } else if (i + 1 < camelCaseString.length() + && !Character.isUpperCase(camelCaseString.charAt(i + 1))) { + // Word ends if next char isn't upper case + return true; + } else { + return false; + } + } + + /** + * Converts a camelCaseString to a human friendly format (Camel case + * string). + *

+ * In general splits words when the casing changes but also handles special + * cases such as consecutive upper case characters. Examples: + *

+ * {@literal MyBeanContainer} becomes {@literal My Bean Container} + * {@literal AwesomeURLFactory} becomes {@literal Awesome URL Factory} + * {@literal SomeUriAction} becomes {@literal Some Uri Action} + * + * @since 7.4 + * @param camelCaseString + * The input string in camelCase format + * @return A human friendly version of the input + */ + public static String camelCaseToHumanFriendly(String camelCaseString) { + String[] parts = splitCamelCase(camelCaseString); + for (int i = 0; i < parts.length; i++) { + parts[i] = capitalize(parts[i]); + } + return join(parts, " "); + } + + private static boolean isAllUpperCase(String string) { + for (int i = 0; i < string.length(); i++) { + char c = string.charAt(i); + if (!Character.isUpperCase(c) && !Character.isDigit(c)) { + return false; + } + } + return true; + } + + /** + * Joins the words in the input array together into a single string by + * inserting the separator string between each word. + * + * @since 7.4 + * @param parts + * The array of words + * @param separator + * The separator string to use between words + * @return The constructed string of words and separators + */ + public static String join(String[] parts, String separator) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < parts.length; i++) { + sb.append(parts[i]); + sb.append(separator); + } + return sb.substring(0, sb.length() - 1); + } + + /** + * Capitalizes the first character in the given string + * + * @since 7.4 + * @param string + * The string to capitalize + * @return The capitalized string + */ + public static String capitalize(String string) { + if (string == null) { + return null; + } + + if (string.length() <= 1) { + return string.toUpperCase(); + } + + return string.substring(0, 1).toUpperCase() + string.substring(1); + } + + /** + * Converts a property id to a human friendly format. Handles nested + * properties by only considering the last part, e.g. "address.streetName" + * is equal to "streetName" for this method. + * + * @since 7.4 + * @param propertyId + * The propertyId to format + * @return A human friendly version of the property id + */ + public static String propertyIdToHumanFriendly(Object propertyId) { + String string = propertyId.toString(); + if (string.isEmpty()) { + return ""; + } + + // For nested properties, only use the last part + int dotLocation = string.lastIndexOf('.'); + if (dotLocation > 0 && dotLocation < string.length() - 1) { + string = string.substring(dotLocation + 1); + } + + return camelCaseToHumanFriendly(string); + } + } diff --git a/shared/tests/src/com/vaadin/shared/util/SharedUtilTests.java b/shared/tests/src/com/vaadin/shared/util/SharedUtilTests.java index b593032bd6..208d4ca7c7 100644 --- a/shared/tests/src/com/vaadin/shared/util/SharedUtilTests.java +++ b/shared/tests/src/com/vaadin/shared/util/SharedUtilTests.java @@ -1,43 +1,79 @@ package com.vaadin.shared.util; -import org.junit.Before; -import org.junit.Test; - import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; -public class SharedUtilTests { - - private SharedUtil sut; +import org.junit.Assert; +import org.junit.Test; - @Before - public void setup() { - sut = new SharedUtil(); - } +public class SharedUtilTests { @Test public void trailingSlashIsTrimmed() { - assertThat(sut.trimTrailingSlashes("/path/"), is("/path")); + assertThat(SharedUtil.trimTrailingSlashes("/path/"), is("/path")); } @Test public void noTrailingSlashForTrimming() { - assertThat(sut.trimTrailingSlashes("/path"), is("/path")); + assertThat(SharedUtil.trimTrailingSlashes("/path"), is("/path")); } @Test public void trailingSlashesAreTrimmed() { - assertThat(sut.trimTrailingSlashes("/path///"), is("/path")); + assertThat(SharedUtil.trimTrailingSlashes("/path///"), is("/path")); } @Test public void emptyStringIsHandled() { - assertThat(sut.trimTrailingSlashes(""), is("")); + assertThat(SharedUtil.trimTrailingSlashes(""), is("")); } @Test public void rootSlashIsTrimmed() { - assertThat(sut.trimTrailingSlashes("/"), is("")); + assertThat(SharedUtil.trimTrailingSlashes("/"), is("")); } + @Test + public void camelCaseToHumanReadable() { + Assert.assertEquals("First Name", + SharedUtil.camelCaseToHumanFriendly("firstName")); + Assert.assertEquals("First Name", + SharedUtil.camelCaseToHumanFriendly("first name")); + Assert.assertEquals("First Name2", + SharedUtil.camelCaseToHumanFriendly("firstName2")); + Assert.assertEquals("First", + SharedUtil.camelCaseToHumanFriendly("first")); + Assert.assertEquals("First", + SharedUtil.camelCaseToHumanFriendly("First")); + Assert.assertEquals("Some XYZ Abbreviation", + SharedUtil.camelCaseToHumanFriendly("SomeXYZAbbreviation")); + + // Javadoc examples + Assert.assertEquals("My Bean Container", + SharedUtil.camelCaseToHumanFriendly("MyBeanContainer")); + Assert.assertEquals("Awesome URL Factory", + SharedUtil.camelCaseToHumanFriendly("AwesomeURLFactory")); + Assert.assertEquals("Some Uri Action", + SharedUtil.camelCaseToHumanFriendly("SomeUriAction")); + + } + + @Test + public void splitCamelCase() { + assertCamelCaseSplit("firstName", "first", "Name"); + assertCamelCaseSplit("fooBar", "foo", "Bar"); + assertCamelCaseSplit("fooBar", "foo", "Bar"); + assertCamelCaseSplit("fBar", "f", "Bar"); + assertCamelCaseSplit("FBar", "F", "Bar"); + assertCamelCaseSplit("MYCdi", "MY", "Cdi"); + assertCamelCaseSplit("MyCDIUI", "My", "CDIUI"); + assertCamelCaseSplit("MyCDIUITwo", "My", "CDIUI", "Two"); + assertCamelCaseSplit("first name", "first", "name"); + + } + + private void assertCamelCaseSplit(String camelCaseString, String... parts) { + String[] splitParts = SharedUtil.splitCamelCase(camelCaseString); + Assert.assertArrayEquals(parts, splitParts); + } } diff --git a/uitest/src/com/vaadin/tests/components/grid/GridColspansTest.java b/uitest/src/com/vaadin/tests/components/grid/GridColspansTest.java index cb0113bcca..f763f7820a 100644 --- a/uitest/src/com/vaadin/tests/components/grid/GridColspansTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/GridColspansTest.java @@ -89,12 +89,12 @@ public class GridColspansTest extends MultiBrowserTest { GridElement grid = $(GridElement.class).first(); assertEquals("Failed initial condition.", "all the stuff", grid .getHeaderCell(0, 1).getText().toLowerCase()); - assertEquals("Failed initial condition.", "firstname", grid + assertEquals("Failed initial condition.", "first name", grid .getHeaderCell(2, 1).getText().toLowerCase()); $(ButtonElement.class).first().click(); assertEquals("Header text changed on column hide.", "all the stuff", grid.getHeaderCell(0, 1).getText().toLowerCase()); - assertEquals("Failed initial condition.", "lastname", grid + assertEquals("Failed initial condition.", "last name", grid .getHeaderCell(2, 1).getText().toLowerCase()); } @@ -106,7 +106,7 @@ public class GridColspansTest extends MultiBrowserTest { GridCellElement headerCell = grid.getHeaderCell(1, 1); assertEquals("Failed initial condition.", "full name", headerCell .getText().toLowerCase()); - assertEquals("Failed initial condition.", "firstname", grid + assertEquals("Failed initial condition.", "first name", grid .getHeaderCell(2, 1).getText().toLowerCase()); $(ButtonElement.class).get(1).click(); headerCell = grid.getHeaderCell(1, 1); -- 2.39.5