Skip to content

Commit

Permalink
feat: add support for responsive steps
Browse files Browse the repository at this point in the history
  • Loading branch information
javier-godoy authored and paodb committed Jun 23, 2023
1 parent 609bf56 commit 0886943
Show file tree
Hide file tree
Showing 6 changed files with 519 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import com.vaadin.flow.function.SerializablePredicate;
import com.vaadin.flow.shared.Registration;
import java.io.Serializable;
import java.util.Collection;
import lombok.AccessLevel;
import lombok.Getter;
import org.slf4j.Logger;
Expand Down Expand Up @@ -512,4 +513,18 @@ public static HeightMode getHeightMode(Grid<?> grid) {
return getHelper(grid).heightByRowsHelper.getHeightMode();
}

private final ResponsiveGridHelper<T> responsiveGridHelper = new ResponsiveGridHelper<>(this);

/**
* Get or create a responsive steps for the given {@code grid} and minimum width.
*/
public static <T> GridResponsiveStep<T> responsiveStep(Grid<T> grid, int minWidth) {
return getHelper(grid).responsiveGridHelper.getOrCreate(minWidth);
}

/** Return the responsive steps of the given {@code grid}. */
public static <T> Collection<GridResponsiveStep<T>> getResponsiveSteps(Grid<T> grid) {
return getHelper(grid).responsiveGridHelper.getAll();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,307 @@
package com.flowingcode.vaadin.addons.gridhelpers;

import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.grid.Grid.Column;
import com.vaadin.flow.data.renderer.ComponentRenderer;
import com.vaadin.flow.data.renderer.Renderer;
import com.vaadin.flow.function.SerializableConsumer;
import com.vaadin.flow.function.SerializableFunction;
import com.vaadin.flow.shared.Registration;
import java.io.Serializable;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import lombok.Getter;
import lombok.NonNull;

/**
* Class used in describing the responsive layouting behavior of a {@link Grid}
*
* @param <T> the data type for Grid
*/
@SuppressWarnings("serial")
public class GridResponsiveStep<T> implements Serializable {

private int minWidth;

private final ResponsiveGridHelper<T> helper;

private final Map<Column<T>, Boolean> columns = new HashMap<>();

private Boolean allColumnsVisible;

@Getter
private Renderer<T> itemDetailsRenderer;

private boolean itemDetailsRendererSet;

private final Map<SerializableConsumer<GridResponsiveStepEvent>, Boolean> listeners =
new LinkedHashMap<>();

GridResponsiveStep() {
minWidth = -1;
helper = null;
}

GridResponsiveStep(int minWidth, @NonNull ResponsiveGridHelper<T> helper) {
if (minWidth < 0) {
throw new IllegalArgumentException();
}
this.minWidth = minWidth;
this.helper = helper;
}

private void requireRefresh() {
if (helper != null) {
helper.requireRefresh(this);
}
}

/**
* Return the minimum width (in pixels) after which this responsive step is to be applied.
*/
public int getMinWidth() {
return minWidth;
}

/**
* Accumulates the settings from the provided step into this instance.
*
* <pre>{@code
* var result = new GridResponsiveStep(0, helper);
* for (var step : steps)
* result = result.reduce(result, element);
* return result;
* }</pre>
*/
void accumulate(GridResponsiveStep<T> step) {
if (helper != null || step.minWidth <= minWidth) {
throw new IllegalArgumentException();
}

minWidth = step.minWidth;

if (step.itemDetailsRendererSet) {
itemDetailsRendererSet = true;
itemDetailsRenderer = step.itemDetailsRenderer;
}

if (step.allColumnsVisible != null) {
columns.keySet().forEach(c -> columns.put(c, step.allColumnsVisible));
}

itemDetailsRenderer = step.itemDetailsRenderer;
columns.putAll(step.columns);

listeners.entrySet().removeIf(e -> !e.getValue());
listeners.putAll(step.listeners);
}

/**
* Applies the settings from this responsive step.
*/
void apply(Grid<T> grid) {
if (helper != null) {
throw new IllegalStateException();
}

boolean hasChanges = false;

if (itemDetailsRendererSet) {
grid.setItemDetailsRenderer(itemDetailsRenderer);
grid.setDetailsVisibleOnClick(itemDetailsRenderer != null);
}

for (Map.Entry<Column<T>, Boolean> e : columns.entrySet()) {
Grid.Column<T> c = e.getKey();
if (!e.getValue().equals(c.isVisible()) && !GridHelper.isMenuToggleColumn(c)) {
e.getKey().setVisible(e.getValue());
hasChanges = true;
}
}

if (hasChanges && GridHelper.isColumnToggleVisible(grid)) {
// refresh the toggle column
GridHelper.setColumnToggleVisible(grid, true);
}

GridResponsiveStepEvent ev = new GridResponsiveStepEvent(grid, minWidth);
listeners.keySet().forEach(listener -> listener.accept(ev));
}

/**
* Set the renderer to use for displaying the item details rows when this responsive step is
* applied.
*
* @param renderer the renderer to use for displaying item details rows, {@code null} to use no
* detail renderer
* @return the instance of GridResponsiveStep for chaining method calls
* @see Grid#setItemDetailsRenderer(Renderer)
*/
public GridResponsiveStep<T> setItemDetailsRenderer(Renderer<T> renderer) {
itemDetailsRenderer = renderer;
itemDetailsRendererSet = true;
requireRefresh();
return this;
}

/**
* Set the renderer to use for displaying the item details rows when this responsive step is
* applied.
*
* @param componentFunction the function to set item details with a Component
* @param <C> the type of component returned by {@code componentFunction}
* @return the instance of GridResponsiveStep for chaining method calls
* @see Grid#setItemDetailsRenderer(Renderer)
*/
public <C extends Component> GridResponsiveStep<T> setItemDetailsRenderer(
@NonNull SerializableFunction<T, C> componentFunction) {
return setItemDetailsRenderer(new ComponentRenderer<>(componentFunction));
}

/**
* Set the columns that should be visible when this responsive step is applied.
*
* @param columnsToShow columns that should be visible
* @return the instance of GridResponsiveStep for chaining method calls
*/
public GridResponsiveStep<T> setColumns(Grid.Column<?>... columnsToShow) {
hideAll();
show(columnsToShow);
return this;
}

/**
* Set the columns that should be visible when this responsive step is applied.
*
* @param columnsToShow columns that should be visible
* @return the instance of GridResponsiveStep for chaining method calls
*/
public GridResponsiveStep<T> setColumns(List<Grid.Column<T>> columnsToShow) {
return setColumns(columnsToShow.toArray(Grid.Column<?>[]::new));
}

/**
* Show the given columns when this responsive step is applied.
*
* @param columnsToShow columns to show
* @return the instance of GridResponsiveStep for chaining method calls
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public GridResponsiveStep<T> show(Grid.Column<?>... columnsToShow) {
return show((List) Arrays.asList(columnsToShow));
}

/**
* Show the given columns when this responsive step is applied.
*
* @param columnsToShow the columns to show
* @return the instance of GridResponsiveStep for chaining method calls
*/
public GridResponsiveStep<T> show(List<Grid.Column<T>> columnsToShow) {
return setVisibility(columnsToShow, true);
}

/**
* Hide the given columns when this responsive step is applied.
*
* @param columnsToHide the columns to hide
* @return the instance of GridResponsiveStep for chaining method calls
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public GridResponsiveStep<T> hide(Grid.Column<?>... columnsToHide) {
return hide((List) Arrays.asList(columnsToHide));
}

/**
* Hide the given columns when this responsive step is applied.
*
* @param columnsToHide the columns to hide
* @return the instance of GridResponsiveStep for chaining method calls
*/
public GridResponsiveStep<T> hide(List<Grid.Column<T>> columnsToHide) {
return setVisibility(columnsToHide, false);
}

private GridResponsiveStep<T> setVisibility(List<Grid.Column<T>> columnsToSet, Boolean value) {
if (helper != null && !helper.getGrid().getColumns().containsAll(columnsToSet)) {
throw new IllegalArgumentException("Some columns are not contained in the grid");
}
columnsToSet.forEach(c -> columns.put(c, value));
requireRefresh();
return this;
}

/**
* Hide all the grid columns when this responsive step is applied.
*
* @return the instance of GridResponsiveStep for chaining method calls
*/
public GridResponsiveStep<T> hideAll() {
columns.clear();
allColumnsVisible = false;
requireRefresh();
return this;
}

/**
* Show all the grid columns when this responsive step is applied.
*
* @return the instance of GridResponsiveStep for chaining method calls
*/
public GridResponsiveStep<T> showAll() {
columns.clear();
allColumnsVisible = true;
requireRefresh();
return this;
}

/**
* Adds a listener which is invoked when the layout changes because of grid resizing. By default,
* the listener will be "non-cummulative" (i.e. it will only be fired when the exact step is
* selected).
*
* @param listener to add
* @return handle to unregister the listener or make it cummulative
* @see GridResponsiveStepListenerRegistration#cummulative()
*/
public GridResponsiveStepListenerRegistration addListener(
@NonNull SerializableConsumer<GridResponsiveStepEvent> listener) {
listeners.put(listener, Boolean.FALSE);
return new GridResponsiveStepListenerRegistration() {
@Override
public void remove() {
listeners.remove(listener);
}

@Override
public GridResponsiveStepListenerRegistration cummulative() {
listeners.computeIfPresent(listener, (k, v) -> Boolean.TRUE);
return this;
}
};
}

/** Remove this responsive step and update the Grid. */
public void remove() {
helper.remove(this);
}

/**
* A registration object for removing or configuring a {@link GridResponsiveStep} listener.
*/
public interface GridResponsiveStepListenerRegistration extends Registration {
/**
* Set the registered listener as cummulative. A cummulative listener will be fired when the
* minimum width is met. Non-cummulative listeners will only be fired when the exact step is
* selected.
*
* @return this registration instance.
*/
public GridResponsiveStepListenerRegistration cummulative();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.flowingcode.vaadin.addons.gridhelpers;

import com.vaadin.flow.component.grid.Grid;
import java.util.EventObject;

/** An event that is fired when the responsive step of a Grid component changes. */
@SuppressWarnings("serial")
public class GridResponsiveStepEvent extends EventObject {

private final int minWidth;

/**
* Constructs a {@code GridResponsiveStepEvent} object with the specified source grid and minimum
* width for the step.
*
* @param source the {@code Grid} component that fired the event
* @param minWidth the minimum width (in pixels) on which the step is applied
*/
GridResponsiveStepEvent(Grid<?> source, int minWidth) {
super(source);
this.minWidth = minWidth;
}

/**
* Returns the {@code Grid} component that fired the event.
*
* @return the source {@code Grid} component
*/
@Override
public Grid<?> getSource() {
return (Grid<?>) super.getSource();
}

/**
* Returns the minimum width (in pixels) on which the step is applied.
*
* @return the minimum width of the {@link GridResponsiveStep}
*/
public int getMinWidth() {
return minWidth;
}

}
Loading

0 comments on commit 0886943

Please sign in to comment.