diff --git a/README.md b/README.md
index a419c2e0..613bdf7f 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,7 @@ This is the relevant dependency:
org.vaadin.miki
superfields
- 0.8.0
+ 0.9.0
```
@@ -47,6 +47,20 @@ This repository has a branch `java-8` which contains the most recent release com
You are more than welcome to contribute. Feel free to make PRs, submit issues, ideas etc.
+### Contributors
+
+The author of the majority of the code is Miki, but this project would not be possible without these wonderful people - listed in alphabetical order:
+
+* Wolfgang Fischlein
+* Jean-Christophe Gueriaud
+* Holger Hähnel
+* Gerald Koch
+* Sebastian Kühnau
+* Jean-François Lamy
+* Stuart Robinson
+* Kaspar Scherrer
+* Tomi Virkki
+
## Small print
All components are provided "as is", with no warranty or liability. See license for details.
diff --git a/demo-v14/pom.xml b/demo-v14/pom.xml
index 6ce960e1..726099ba 100644
--- a/demo-v14/pom.xml
+++ b/demo-v14/pom.xml
@@ -4,11 +4,11 @@
superfields-parent
org.vaadin.miki
- 0.8.0
+ 0.9.0
superfields-demo-v14
- 0.8.0
+ 0.9.0
V14 demo app for SuperFields
Showcase application for V14 and SuperFields.
war
@@ -23,7 +23,7 @@
org.vaadin.miki
superfields
- 0.8.0
+ 0.9.0
javax.servlet
diff --git a/demo-v14/src/main/java/org/vaadin/miki/DemoComponentFactory.java b/demo-v14/src/main/java/org/vaadin/miki/DemoComponentFactory.java
index dc8ab7ba..3ff9f821 100644
--- a/demo-v14/src/main/java/org/vaadin/miki/DemoComponentFactory.java
+++ b/demo-v14/src/main/java/org/vaadin/miki/DemoComponentFactory.java
@@ -37,6 +37,7 @@
import org.vaadin.miki.superfields.buttons.SimpleButtonState;
import org.vaadin.miki.superfields.dates.SuperDatePicker;
import org.vaadin.miki.superfields.dates.SuperDateTimePicker;
+import org.vaadin.miki.superfields.gridselect.GridSelect;
import org.vaadin.miki.superfields.itemgrid.ItemGrid;
import org.vaadin.miki.superfields.lazyload.ComponentObserver;
import org.vaadin.miki.superfields.lazyload.LazyLoad;
@@ -53,6 +54,7 @@
import org.vaadin.miki.superfields.text.SuperTextField;
import org.vaadin.miki.superfields.unload.UnloadObserver;
+import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Collection;
@@ -63,13 +65,14 @@
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Supplier;
+import java.util.stream.Collectors;
/**
* Stores information about components to demo.
* @author miki
* @since 2020-07-04
*/
-public final class DemoComponentFactory {
+public final class DemoComponentFactory implements Serializable {
private static final int NOTIFICATION_TIME = 1500;
@@ -126,6 +129,8 @@ private DemoComponentFactory() {
new SimpleButtonState("Are you sure?", VaadinIcon.INFO_CIRCLE.create()),
new SimpleButtonState("Really navigate away?", VaadinIcon.INFO.create()).withThemeVariant(ButtonVariant.LUMO_ERROR)
).withId("multi-click-button"));
+ final GridSelect gridSelect = new GridSelect<>(SuperFieldsGridItem.class, true);
+ this.components.put(GridSelect.class, gridSelect); // note: extra config below to include all fields
this.components.put(ComponentObserver.class, new ComponentObserver());
this.components.put(UnloadObserver.class, UnloadObserver.get().withoutQueryingOnUnload());
this.components.put(ItemGrid.class, new ItemGrid>(
@@ -157,6 +162,8 @@ private DemoComponentFactory() {
return result;
})
);
+ gridSelect.getGrid().getColumnByKey("nameLength").setAutoWidth(true);
+ gridSelect.setItems(this.components.keySet().stream().map(SuperFieldsGridItem::new).collect(Collectors.toList()));
this.contentBuilders.put(CanSelectText.class, this::buildCanSelectText);
this.contentBuilders.put(HasValue.class, this::buildHasValue);
@@ -296,7 +303,7 @@ private void buildItemGrid(Component component, Consumer callback)
private void buildHasDatePattern(Component component, Consumer callback) {
final ComboBox patterns = new ComboBox<>("Select date display pattern:",
DatePatterns.YYYY_MM_DD, DatePatterns.M_D_YYYY_SLASH,
- DatePatterns.DD_MM_YYYY_DOTTED, DatePatterns.D_M_YY_DOTTED,
+ DatePatterns.DD_MM_YYYY_DOTTED, DatePatterns.DD_MM_YY_OR_YYYY_DOTTED, DatePatterns.D_M_YY_DOTTED,
DatePatterns.YYYYMMDD, DatePatterns.DDMMYY
);
final Button clearPattern = new Button("Clear pattern", event -> ((HasDatePattern)component).setDatePattern(null));
diff --git a/demo-v14/src/main/java/org/vaadin/miki/DemoPage.java b/demo-v14/src/main/java/org/vaadin/miki/DemoPage.java
index 9cab4853..25fa686c 100644
--- a/demo-v14/src/main/java/org/vaadin/miki/DemoPage.java
+++ b/demo-v14/src/main/java/org/vaadin/miki/DemoPage.java
@@ -8,6 +8,9 @@
import com.vaadin.flow.router.HasUrlParameter;
import com.vaadin.flow.router.Route;
+import java.util.HashMap;
+import java.util.Map;
+
/**
* Page that shows a demo of a component.
* @author miki
@@ -18,24 +21,31 @@ public class DemoPage extends VerticalLayout implements HasUrlParameter,
private final DemoComponentFactory demoComponentFactory = DemoComponentFactory.get();
+ private final Map pages = new HashMap<>();
+
private Class extends Component> componentType;
@Override
public void setParameter(BeforeEvent event, String parameter) {
this.removeAll();
- this.demoComponentFactory.getDemoableComponentTypes().stream()
- .filter(type -> type.getSimpleName().equalsIgnoreCase(parameter))
- .findFirst().ifPresentOrElse(this::buildDemoPageFor, this::buildErrorPage);
+
+ this.add(this.pages.computeIfAbsent(parameter, s ->
+ this.demoComponentFactory.getDemoableComponentTypes().stream()
+ .filter(type -> type.getSimpleName().equalsIgnoreCase(s))
+ .findFirst()
+ .map(this::buildDemoPageFor)
+ .orElseGet(this::buildErrorPage)
+ ));
}
- private void buildDemoPageFor(Class extends Component> type) {
+ private Component buildDemoPageFor(Class extends Component> type) {
this.componentType = type;
- this.add(this.demoComponentFactory.buildDemoPageFor(type));
+ return this.demoComponentFactory.buildDemoPageFor(type);
}
- private void buildErrorPage() {
+ private Component buildErrorPage() {
this.componentType = null;
- this.add(new Span("You are seeing this because there was a problem in navigating to the demo page for your selected component."));
+ return new Span("You are seeing this because there was a problem in navigating to the demo page for your selected component.");
}
@Override
diff --git a/demo-v14/src/main/java/org/vaadin/miki/SuperFieldsGridItem.java b/demo-v14/src/main/java/org/vaadin/miki/SuperFieldsGridItem.java
new file mode 100644
index 00000000..1e1753df
--- /dev/null
+++ b/demo-v14/src/main/java/org/vaadin/miki/SuperFieldsGridItem.java
@@ -0,0 +1,36 @@
+package org.vaadin.miki;
+
+/**
+ * Simple data class to showcase in grid select demo.
+ * @author miki
+ * @since 2020-08-07
+ */
+// needs to be public, otherwise grid cannot access it
+public class SuperFieldsGridItem {
+
+ private final int nameLength;
+
+ private String name;
+
+ SuperFieldsGridItem(Class> type) {
+ this.name = type.getSimpleName();
+ this.nameLength = this.name.length();
+ }
+
+ public int getNameLength() {
+ return this.nameLength;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String toString() {
+ return this.name;
+ }
+}
diff --git a/pom.xml b/pom.xml
index 3532021d..0f6c00e7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
4.0.0
org.vaadin.miki
superfields-parent
- 0.8.0
+ 0.9.0
superfields
demo-v14
diff --git a/superfields/pom.xml b/superfields/pom.xml
index 855cffcf..d13004e0 100644
--- a/superfields/pom.xml
+++ b/superfields/pom.xml
@@ -8,7 +8,7 @@
superfields
SuperFields
Code for various V14+ fields and other components.
- 0.8.0
+ 0.9.0
10
diff --git a/superfields/release-notes.md b/superfields/release-notes.md
index 1546e97b..90eb5a62 100644
--- a/superfields/release-notes.md
+++ b/superfields/release-notes.md
@@ -1,3 +1,13 @@
+# 0.9.0 - GridSelect
+## New features and enhancements
+* \#211 - [GridSelect](https://github.com/vaadin-miki/super-fields/issues/211)
+* \#212 - [Date pickers should have an option to always accept short year as input](https://github.com/vaadin-miki/super-fields/issues/212)
+## Changes to API
+(nothing reported)
+## Bug fixes
+* \#206 - [Client-side listener for UnloadObserver is not cleared when navigating away](https://github.com/vaadin-miki/super-fields/issues/206)
+* \#214 - [Demo app events are triggered multiple times](https://github.com/vaadin-miki/super-fields/issues/214)
+* \#216 - [Year calculation in date pickers for short year is incorrect](https://github.com/vaadin-miki/super-fields/issues/216)
# 0.8.0 - MultiClickButton
## New features and enhancements
* \#160 - [Confirm-on-click button](https://github.com/vaadin-miki/super-fields/issues/160)
diff --git a/superfields/src/main/java/org/vaadin/miki/shared/dates/DatePattern.java b/superfields/src/main/java/org/vaadin/miki/shared/dates/DatePattern.java
index e02dac64..742b70fb 100644
--- a/superfields/src/main/java/org/vaadin/miki/shared/dates/DatePattern.java
+++ b/superfields/src/main/java/org/vaadin/miki/shared/dates/DatePattern.java
@@ -48,6 +48,8 @@ public enum Order {DAY_MONTH_YEAR, MONTH_DAY_YEAR, YEAR_MONTH_DAY}
private boolean previousCenturyBelowBoundary = false;
+ private boolean shortYearAlwaysAccepted = false;
+
private Order displayOrder = Order.YEAR_MONTH_DAY;
/**
@@ -316,6 +318,37 @@ public DatePattern withPreviousCenturyBelowBoundary(boolean belowBoundaryIsPrevi
return this;
}
+ /**
+ * Allows short year to be always accepted as input.
+ * @param shortYearAlwaysAccepted When {@code true}, short year can always be entered and will be parsed properly and displayed as full year.
+ * @see #setBaseCentury(int)
+ * @see #setCenturyBoundaryYear(int)
+ * @see #setPreviousCenturyBelowBoundary(boolean)
+ * @see #setShortYear(boolean)
+ */
+ public void setShortYearAlwaysAccepted(boolean shortYearAlwaysAccepted) {
+ this.shortYearAlwaysAccepted = shortYearAlwaysAccepted;
+ }
+
+ /**
+ * Whether or not short year is accepted as user input even if {@link #isShortYear()} returns {@code false}.
+ * @return When {@code true}, user can input last two digits of the year and it will be properly parsed.
+ */
+ public boolean isShortYearAlwaysAccepted() {
+ return shortYearAlwaysAccepted;
+ }
+
+ /**
+ * Chains {@link #setShortYearAlwaysAccepted(boolean)} and returns itself.
+ * @param shortYearAlwaysAccepted Whether or not to always accept short year as user input.
+ * @return This.
+ * @see #setShortYearAlwaysAccepted(boolean)
+ */
+ public DatePattern withShortYearAlwaysAccepted(boolean shortYearAlwaysAccepted) {
+ this.setShortYearAlwaysAccepted(shortYearAlwaysAccepted);
+ return this;
+ }
+
/**
* Returns display name defined in the constructor. The display name is irrelevant in {@link #equals(Object)} and {@link #hashCode()}.
* @return Display name. May be {@code null} when no-arg constructor has been used.
@@ -336,12 +369,13 @@ public boolean equals(Object o) {
getBaseCentury() == pattern.getBaseCentury() &&
getCenturyBoundaryYear() == pattern.getCenturyBoundaryYear() &&
isPreviousCenturyBelowBoundary() == pattern.isPreviousCenturyBelowBoundary() &&
+ isShortYearAlwaysAccepted() == pattern.isShortYearAlwaysAccepted() &&
getDisplayOrder() == pattern.getDisplayOrder();
}
@Override
public int hashCode() {
- return Objects.hash(getSeparator(), isZeroPrefixedDay(), isZeroPrefixedMonth(), isShortYear(), getBaseCentury(), getCenturyBoundaryYear(), isPreviousCenturyBelowBoundary(), getDisplayOrder());
+ return Objects.hash(getSeparator(), isZeroPrefixedDay(), isZeroPrefixedMonth(), isShortYear(), getBaseCentury(), getCenturyBoundaryYear(), isPreviousCenturyBelowBoundary(), isShortYearAlwaysAccepted(), getDisplayOrder());
}
@Override
@@ -355,6 +389,7 @@ public String toString() {
", baseCentury=" + baseCentury +
", centuryBoundaryYear=" + centuryBoundaryYear +
", previousCenturyBelowBoundary=" + previousCenturyBelowBoundary +
+ ", shortYearAlwaysAccepted=" + shortYearAlwaysAccepted +
", displayOrder=" + displayOrder +
'}' :
getDisplayName();
diff --git a/superfields/src/main/java/org/vaadin/miki/shared/dates/DatePatterns.java b/superfields/src/main/java/org/vaadin/miki/shared/dates/DatePatterns.java
index 7d5a81ba..b76da894 100644
--- a/superfields/src/main/java/org/vaadin/miki/shared/dates/DatePatterns.java
+++ b/superfields/src/main/java/org/vaadin/miki/shared/dates/DatePatterns.java
@@ -19,6 +19,16 @@ public final class DatePatterns {
.withDisplayOrder(DatePattern.Order.DAY_MONTH_YEAR)
.withSeparator('.');
+ /**
+ * Uses zero-prefixed day and month, optionally full year, separated by {@code .}.
+ * For short years the century boundary year is 40 (years less than 40 are from 21st century).
+ */
+ public static final DatePattern DD_MM_YY_OR_YYYY_DOTTED = new DatePattern("dd.MM.(yy)yy")
+ .withDisplayOrder(DatePattern.Order.DAY_MONTH_YEAR)
+ .withShortYearAlwaysAccepted(true)
+ .withBaseCentury(21).withCenturyBoundaryYear(40).withPreviousCenturyBelowBoundary(false)
+ .withSeparator('.');
+
/**
* Uses day, month and short year with century boundary year 40 (years less than 40 are from 21st century), separated by {@code .}.
*/
diff --git a/superfields/src/main/java/org/vaadin/miki/superfields/dates/DatePatternDelegate.java b/superfields/src/main/java/org/vaadin/miki/superfields/dates/DatePatternDelegate.java
index 231483bb..53e9bef8 100644
--- a/superfields/src/main/java/org/vaadin/miki/superfields/dates/DatePatternDelegate.java
+++ b/superfields/src/main/java/org/vaadin/miki/superfields/dates/DatePatternDelegate.java
@@ -51,7 +51,7 @@ private static String convertDatePatternToClientPattern(DatePattern pattern) {
builder.append(yearPart).append(monthPart).append(dayPart);
break;
}
- if (pattern.isShortYear()) {
+ if (pattern.isShortYear() || pattern.isShortYearAlwaysAccepted()) {
builder.append(pattern.isPreviousCenturyBelowBoundary() ? '+' : '-');
builder.append(String.format("%02d", pattern.getBaseCentury() % 100));
builder.append(String.format("%02d", pattern.getCenturyBoundaryYear() % 100));
diff --git a/superfields/src/main/java/org/vaadin/miki/superfields/gridselect/GridSelect.java b/superfields/src/main/java/org/vaadin/miki/superfields/gridselect/GridSelect.java
new file mode 100644
index 00000000..97a9e7ce
--- /dev/null
+++ b/superfields/src/main/java/org/vaadin/miki/superfields/gridselect/GridSelect.java
@@ -0,0 +1,124 @@
+package org.vaadin.miki.superfields.gridselect;
+
+import com.vaadin.flow.component.AbstractField;
+import com.vaadin.flow.component.customfield.CustomField;
+import com.vaadin.flow.component.grid.Grid;
+import com.vaadin.flow.data.selection.SelectionEvent;
+import org.vaadin.miki.markers.WithIdMixin;
+import org.vaadin.miki.markers.WithItemsMixin;
+import org.vaadin.miki.markers.WithValueMixin;
+
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * A single-selection {@link Grid} that also is a value component that broadcasts value change events.
+ * @param Type of value to include in the grid.
+ * @author miki
+ * @since 2020-08-07
+ */
+public class GridSelect extends CustomField
+ implements WithIdMixin>, WithItemsMixin>,
+ WithValueMixin, V>, V, GridSelect> {
+
+ private final Grid grid;
+
+ /**
+ * Creates the component.
+ * This requires a subsequent configuration of grid's columns.
+ * @see #getGrid()
+ */
+ public GridSelect() {
+ this(new Grid<>());
+ }
+
+ /**
+ * Constructs the component. This is the recommended constructor.
+ * @param type Type of items displayed in the grid.
+ * @param createColumns Whether or not to create default columns.
+ * @param items Items to add to the grid.
+ */
+ @SafeVarargs
+ public GridSelect(Class type, boolean createColumns, V... items) {
+ this(new Grid<>(type, createColumns));
+ this.setItems(items);
+ }
+
+ /**
+ * More advanced constructor that allows using predefined grid.
+ * It is not public, as usage of this constructor implies you know what you are doing.
+ * @param underlyingGrid A grid to use.
+ */
+ protected GridSelect(Grid underlyingGrid) {
+ this.grid = underlyingGrid;
+ this.configureGrid(this.grid);
+ this.setSizeFull();
+ }
+
+ /**
+ * Configures the grid.
+ * @param grid Grid to configure.
+ */
+ protected void configureGrid(Grid grid) {
+ this.add(grid);
+
+ this.grid.addSelectionListener(this::onGridSelected);
+ this.grid.addFocusListener(gridFocusEvent -> this.fireEvent(new FocusEvent<>(this, gridFocusEvent.isFromClient())));
+ this.grid.addBlurListener(gridBlurEvent -> this.fireEvent(new BlurEvent<>(this, gridBlurEvent.isFromClient())));
+
+ // this js snippet courtesy of Tomi Virkki
+ // prevents selection on-click and on-space-pressed, basically turning the grid into read-only when needed
+ this.grid.getElement().getNode().runWhenAttached(ui -> ui.beforeClientResponse(this.grid, executionContext ->
+ this.grid.getElement().executeJs("this.$.table.addEventListener('click', function(e) {this.preventSelection && e.stopPropagation()}.bind(this)); " +
+ "this.$.table.addEventListener('keydown', function(e){this.preventSelection && e.keyCode === 32 && e.stopPropagation()}.bind(this));")));
+
+ grid.addClassName("grid-select-inner-grid");
+ grid.setSelectionMode(Grid.SelectionMode.SINGLE);
+ }
+
+ private void onGridSelected(SelectionEvent, V> event) {
+ this.updateValue();
+ }
+
+ /**
+ * Returns the underlying {@link Grid}. Use with caution. Please do not mess with grid's selection.
+ * @return The {@link Grid}. Any changes to the grid will affect this component.
+ */
+ public Grid getGrid() {
+ return this.grid;
+ }
+
+ @Override
+ protected V generateModelValue() {
+ final Set items = this.grid.getSelectedItems();
+ return items.isEmpty() ? null : items.iterator().next();
+ }
+
+ @Override
+ protected void setPresentationValue(V v) {
+ this.grid.select(v);
+ }
+
+ @Override
+ public void setItems(Collection collection) {
+ this.grid.setItems(collection);
+ this.grid.recalculateColumnWidths();
+ }
+
+ @Override
+ public void setReadOnly(boolean readOnly) {
+ super.setReadOnly(readOnly);
+ this.grid.getElement().setProperty("preventSelection", readOnly);
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ this.grid.setEnabled(enabled);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return this.grid.isEnabled();
+ }
+
+}
diff --git a/superfields/src/main/java/org/vaadin/miki/superfields/unload/UnloadObserver.java b/superfields/src/main/java/org/vaadin/miki/superfields/unload/UnloadObserver.java
index 2bb40884..efb9d93d 100644
--- a/superfields/src/main/java/org/vaadin/miki/superfields/unload/UnloadObserver.java
+++ b/superfields/src/main/java/org/vaadin/miki/superfields/unload/UnloadObserver.java
@@ -186,6 +186,12 @@ protected void onAttach(AttachEvent attachEvent) {
@Override
protected void onDetach(DetachEvent detachEvent) {
this.clientInitialised = false;
+
+ // getElement().callJsFunction(..) doesn't work in this phase, so let's execute the necessary js directly without the element involved.
+ detachEvent.getUI().getPage().executeJs(
+ "if (window.Vaadin.unloadObserver.attemptHandler !== undefined) {" +
+ " window.removeEventListener('beforeunload', window.Vaadin.unloadObserver.attemptHandler);" +
+ "}");
super.onDetach(detachEvent);
}
diff --git a/superfields/src/main/resources/META-INF/resources/frontend/date-pattern-mixin.js b/superfields/src/main/resources/META-INF/resources/frontend/date-pattern-mixin.js
index fbe65532..2d640c28 100644
--- a/superfields/src/main/resources/META-INF/resources/frontend/date-pattern-mixin.js
+++ b/superfields/src/main/resources/META-INF/resources/frontend/date-pattern-mixin.js
@@ -149,16 +149,16 @@ export class DatePatternMixin {
}
}
// end of parsing stuff
-
- // now, if short year is used
- if (shortYear) {
+ // now, if short year is allowed (i.e. there is a description of default century and boundary year)
+ if (ddp.length === 12 || ddp.length === 11) {
const boundaryYear = parseInt(ddp.substr(-2));
const defaultCentury = parseInt(ddp.substr(-4, 2));
if (year < boundaryYear) {
year += (ddp[ddp.length-5] === '+' ? defaultCentury - 2 : defaultCentury - 1) * 100;
} else if (year < 100) {
- year += (ddp[ddp.length-5] === '+' ? defaultCentury - 2 : defaultCentury - 1) * 100;
+ year += (ddp[ddp.length-5] === '+' ? defaultCentury - 1 : defaultCentury - 2) * 100;
}
+ console.log("SDP: after fixing the year is "+year);
}
// return result
if (date !== undefined) {