All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.bklab.crud.FluentCrudView Maven / Gradle / Ivy

There is a newer version: 22.0.1
Show newest version
package org.bklab.crud;

import com.vaadin.flow.component.AttachEvent;
import com.vaadin.flow.component.HasValue;
import com.vaadin.flow.component.dependency.CssImport;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.grid.GridSortOrder;
import com.vaadin.flow.component.grid.contextmenu.GridContextMenu;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.treegrid.TreeGrid;
import com.vaadin.flow.data.event.SortEvent;
import com.vaadin.flow.data.provider.DataProvider;
import com.vaadin.flow.data.provider.ListDataProvider;
import com.vaadin.flow.data.provider.hierarchy.HierarchicalDataProvider;
import com.vaadin.flow.data.provider.hierarchy.TreeData;
import com.vaadin.flow.data.provider.hierarchy.TreeDataProvider;
import com.vaadin.flow.router.BeforeEnterEvent;
import com.vaadin.flow.router.BeforeEnterObserver;
import com.vaadin.flow.shared.Registration;
import org.bklab.crud.core.ICrudViewExcelExportSupporter;
import org.bklab.crud.core.IFluentCrudViewCommonField;
import org.bklab.crud.menu.FluentCrudMenuButton;
import org.bklab.crud.menu.ICrudViewMenuColumnSupporter;
import org.bklab.crud.menu.IFluentGridMenuBuilder;
import org.bklab.flow.base.HasReturnThis;
import org.bklab.flow.components.button.FluentButton;
import org.bklab.flow.components.pagination.PageSwitchEvent;
import org.bklab.flow.components.pagination.Pagination;
import org.bklab.flow.components.pagination.layout.MiddleCustomPaginationLayout;
import org.bklab.flow.components.textfield.KeywordField;
import org.bklab.flow.dialog.ErrorDialog;
import org.bklab.flow.layout.EmptyLayout;
import org.bklab.flow.layout.ToolBar;
import org.bklab.flow.util.url.QueryParameterUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.function.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

@SuppressWarnings("UnusedReturnValue")
@CssImport("./styles/org/bklab/component/crud/fluent-crud-view.css")
public abstract class FluentCrudView> extends VerticalLayout implements
        IFluentCrudViewCommonField>,
        ICrudViewMenuColumnSupporter>,
        ICrudViewExcelExportSupporter>,
        HasReturnThis>,
        BeforeEnterObserver {

    protected final List> effectQueryParameterUtils = new ArrayList<>();

    private final static String CLASS_NAME = "fluent-crud-view";
    private static final Logger logger = LoggerFactory.getLogger(FluentCrudView.class);
    protected final Map> parameterMap = new LinkedHashMap<>();
    protected final ToolBar header = new ToolBar();
    protected final Div content = new Div();
    protected final Div footer = new Div();
    protected final List entities = new ArrayList<>();
    protected final List inMemoryFilteredEntities = new ArrayList<>();
    protected final Map> inMemoryEntityFilter = new LinkedHashMap<>();
    protected final Pagination pagination = new Pagination().onePageSize(20).limit(10).customLayout(new MiddleCustomPaginationLayout());
    protected final List>> afterReloadListeners = new ArrayList<>();
    private final List> exceptionConsumers = new ArrayList<>();
    private final List> menuButtons = new ArrayList<>();
    protected final EmptyLayout emptyLayout = new EmptyLayout("暂无数据");
    protected FluentButton searchButton = (FluentButton) new FluentButton(VaadinIcon.SEARCH, "查询")
            .primary().asFactory().clickListener(e -> reloadGridData()).get();
    protected boolean hasGridMenu = true;
    protected boolean hasPagination = true;
    protected Function> childProvider = null;
    protected Function, DataProvider> dataProviderCreator = null;
    protected BiPredicate sameEntityBiPredicate = Objects::equals;
    protected final G grid;
    protected QueryParameterUtil queryParameterUtil;

    public FluentCrudView() {
        emptyLayout.setVisible(false);
        getStyle().set("padding", "0.5em 0 0").set("background-color", "white");
        addClassName(CLASS_NAME);
        header.addClassName(CLASS_NAME + "__header");
        content.addClassName(CLASS_NAME + "__content");
        footer.addClassName(CLASS_NAME + "__footer");
        emptyLayout.addClassName(CLASS_NAME + "__empty");
        add(header, content, footer);
        emptyLayout.getElement().getStyle().set("flex-grow", "1");
        emptyLayout.setHeight("calc(100% - 5em)");
        addExceptionConsumer(e -> new ErrorDialog(e).header("错误").build().open());
        addExceptionConsumer(e -> logger.error(Optional.ofNullable(e.getLocalizedMessage()).orElse("错误"), e));

        beforeInitGrid();
        this.grid = createGrid();
        grid.addClassName(CLASS_NAME + "__grid");
    }

    protected void beforeInitGrid() {
    }

    public FluentCrudView useRefreshIconButton() {
        searchButton.reset().noPadding().link().iconOnly().asFactory().tooltip("刷新").icon(VaadinIcon.REFRESH.create()).text(null);
        return this;
    }

    @Deprecated
    public FluentCrudView addRefreshIconButton() {
        return useRefreshIconButton();
    }

    public FluentCrudView addExceptionConsumer(Consumer exceptionConsumer) {
        this.exceptionConsumers.add(exceptionConsumer);
        return this;
    }

    public Consumer getExceptionConsumer() {
        return exception -> exceptionConsumers.forEach(a -> a.accept(exception));
    }

    public abstract G createGrid();

    public abstract Collection queryEntities(Map parameters);

    public FluentCrudView headerVisible(boolean visible) {
        header.setVisible(visible);
        if (visible) content.removeClassName("fluent-crud-view__no_header");
        else content.addClassName("fluent-crud-view__no_header");
        return this;
    }

    public FluentCrudView watch(HasValue hasValue) {
        Registration registration = hasValue.addValueChangeListener(e -> reloadGridData());
        addDetachListener(e -> registration.remove());
        return this;
    }

    public FluentCrudView noPagination() {
        this.hasPagination = false;
        return this;
    }

    public FluentCrudView hasPagination() {
        this.hasPagination = true;
        return this;
    }

    public FluentCrudView toggleEmpty(boolean empty) {
        return empty ? toggleEmpty() : toggleNotEmpty();
    }

    private FluentCrudView toggleEmpty() {
        if (grid != null) {
            if (!grid.getFooterRows().isEmpty()) {
                return toggleNotEmpty();
            }

            grid.getStyle().remove("height");
            grid.getStyle().remove("min-height");
            grid.setHeightByRows(true);
            grid.setHeight("unset");
            if (grid instanceof TreeGrid) {
                grid.setItems(new TreeDataProvider<>(new TreeData<>()));
            } else {
                grid.setItems(new ListDataProvider<>(Collections.emptyList()));
            }
        }
        footer.setVisible(false);
        emptyLayout.setVisible(true);
        return this;
    }

    private FluentCrudView toggleNotEmpty() {
        grid.setHeightByRows(false);
        grid.setHeightFull();
        emptyLayout.setVisible(false);
        footer.setVisible(true);
        return this;
    }

    public BiPredicate getSameEntityBiPredicate() {
        if (sameEntityBiPredicate == null) this.sameEntityBiPredicate = Objects::equals;
        return sameEntityBiPredicate;
    }

    public FluentCrudView setSameEntityBiPredicate(BiPredicate sameEntityBiPredicate) {
        this.sameEntityBiPredicate = Objects.requireNonNull(sameEntityBiPredicate, "sameEntityBiPredicate is null");
        return this;
    }

    public FluentCrudView menuColumnAtFirst() {
        Grid.Column menuColumn = grid.getColumnByKey("menuColumn");
        if (menuColumn == null) return this;

        List> columns = new ArrayList<>(grid.getColumns());
        columns.remove(menuColumn);
        columns.add(0, menuColumn);
        grid.setColumnOrder(columns);
        return this;
    }

    public FluentCrudView reloadContextMenu(T entity, Predicate samePredicate) {
        for (FluentCrudMenuButton menuButton : menuButtons) {
            menuButton.reload(entity, samePredicate != null
                                      ? samePredicate : t -> getSameEntityBiPredicate().test(entity, t));
        }
        return this;
    }

    public FluentCrudView removeContextMenu(T entity) {
        return this.removeContextMenu(entity, sameEntityBiPredicate);
    }

    public FluentCrudView removeContextMenu(T entity, BiPredicate sameEntityBiPredicate) {
        menuButtons.stream().filter(m -> m.isThisEntity(entity, sameEntityBiPredicate))
                .collect(Collectors.toList()).forEach(menuButtons::remove);
        return this;
    }

    private void handleGridGridSortOrderSortEvent(SortEvent, GridSortOrder> gridGridSortOrderSortEvent) {
        List filteredEntities = inMemoryFilteredEntities.stream().sorted((o1, o2) -> {
            for (GridSortOrder order : gridGridSortOrderSortEvent.getSortOrder()) {
                Grid.Column sorted = order.getSorted();
                int compare = sorted.getComparator(order.getDirection()).compare(o1, o2);
                if (compare == 0) continue;
                return compare;
            }
            return 0;
        }).collect(Collectors.toList());
        inMemoryFilteredEntities.clear();
        inMemoryFilteredEntities.addAll(filteredEntities);
        pagination.refresh();
    }

    @Override
    public void beforeEnter(BeforeEnterEvent beforeEnterEvent) {
        this.queryParameterUtil = new QueryParameterUtil(beforeEnterEvent);
    }

    @Override
    protected void onAttach(AttachEvent attachEvent) {
        super.onAttach(attachEvent);

        header.right(searchButton);
        content.remove(grid, emptyLayout);
        content.add(grid, emptyLayout);
        if (hasPagination) {
            footer.add(pagination);
        } else {
            footer.setVisible(false);
            content.getStyle().set("border-bottom", "none");
        }
        pagination.pageSwitchEvent(createPageSwitchEvent());
        grid.addSortListener(this::handleGridGridSortOrderSortEvent);

        this.effectQueryParameterUtils.forEach(a -> a.accept(queryParameterUtil));
        reloadGridData();
        onAfterAttach();
    }

    protected void onAfterAttach() {

    }

    public void insertEntity(T entity) {
        this.entities.add(entity);
        this.inMemoryFilteredEntities.add(entity);
        this.pagination.totalData(this.entities.size()).build();
        toggleNotEmpty();
    }

    public void deleteEntity(T entity) {
        this.entities.remove(entity);
        this.inMemoryFilteredEntities.remove(entity);
        this.pagination.totalData(this.entities.size()).build();
        this.removeContextMenu(entity);
        toggleEmpty(inMemoryFilteredEntities.isEmpty());
    }

    public void deleteEntity(T entity, BiPredicate isEquals) {
        List instance = this.entities.stream().filter(t -> isEquals.test(t, entity)).collect(Collectors.toList());
        this.entities.removeAll(instance);
        this.inMemoryFilteredEntities.removeAll(entities);
        this.pagination.totalData(this.entities.size()).build();
        this.removeContextMenu(entity, isEquals == null ? getSameEntityBiPredicate() : isEquals);
        toggleEmpty(inMemoryFilteredEntities.isEmpty());
    }

    public void updateEntity(T entity) {
        this.updateEntity(entity, t -> getSameEntityBiPredicate().test(t, entity));
    }

    public void updateEntity(T oldEntity, T newEntity) {
        this.updateEntity(newEntity, t -> getSameEntityBiPredicate().test(oldEntity, t));
    }

    public void updateEntity(T entity, Predicate isEquals) {
        replaceSameEntity(entities, entity, isEquals);
        replaceSameEntity(inMemoryFilteredEntities, entity, isEquals);
        this.reloadContextMenu(entity, isEquals);
        this.pagination.totalData(this.entities.size()).build();
    }

    private void replaceSameEntity(Collection collection, T entity, Predicate isEquals) {
        List instances = collection.stream().map(t -> isEquals.test(t) ? entity : t).collect(Collectors.toList());
        collection.clear();
        collection.addAll(instances);
    }

    public FluentCrudView buildGridMenu(IFluentGridMenuBuilder menuEntityBiConsumer) {
        GridContextMenu gridContextMenu = grid.addContextMenu();
        gridContextMenu.setDynamicContentHandler(entity -> {
            if (entity == null) {
                return false;
            }
            gridContextMenu.removeAll();
            grid.select(entity);
            menuEntityBiConsumer.safeBuild(this, gridContextMenu, entity);
            return true;
        });

        return this;
    }

    protected PageSwitchEvent createPageSwitchEvent() {
        return (currentPageNumber, pageSize, lastPageNumber, isFromClient) -> setGridItem(
                hasPagination ? this.inMemoryFilteredEntities.stream().skip((long) (Math.max(Math.min(currentPageNumber, this.inMemoryFilteredEntities.size()
                        / pageSize + 1), 1) - 1) * pageSize).limit(pageSize).collect(Collectors.toList()) : inMemoryFilteredEntities
        );
    }

    public FluentCrudView setGridItem(Collection items) {
        if (dataProviderCreator != null) {
            grid.setItems(dataProviderCreator.apply(items));
        } else if (grid instanceof TreeGrid) {
            //noinspection unchecked,rawtypes,rawtypes
            ((TreeGrid) grid).setDataProvider((HierarchicalDataProvider) new TreeDataProvider<>((new TreeData()).addItems(items, getChildProvider()::apply)));
        } else {
            grid.setItems(items);
        }
        return this;
    }

    protected Function> getChildProvider() {
        return childProvider == null ? a -> Collections.emptyList() : childProvider;
    }

    public FluentCrudView addParameter(String name, HasValue hasValue) {
        return addParameter(name, hasValue, s -> s instanceof String ? ((String) s).trim().isEmpty() ? null : ((String) s).trim() : s);
    }

    public  FluentCrudView addParameter(String name, HasValue hasValue, Function mapValue) {
        parameterMap.put(name, () -> Optional.ofNullable(hasValue.getValue()).map(mapValue).orElse(null));
        return this;
    }

    protected KeywordField addKeywordField() {
        KeywordField keywordField = new KeywordField((c, v) -> reloadGridData());
        parameterMap.put("keyword", () -> Optional.ofNullable(keywordField.getValue()).map(k -> k.trim().isEmpty() ? null : k.trim()).orElse(null));
        return keywordField;
    }

    protected FluentCrudView addKeywordFieldToLeft() {
        header.left(addKeywordField());
        return this;
    }

    protected FluentCrudView addKeywordFieldToMiddle() {
        header.middle(addKeywordField());
        return this;
    }

    protected FluentCrudView addKeywordFieldToRight() {
        header.right(addKeywordField());
        return this;
    }

    public void reloadGridDataInMemory() {
        List entities = this.entities.stream().filter(t ->
                inMemoryEntityFilter.values().stream().filter(Objects::nonNull).allMatch(p -> p.test(t))
        ).collect(Collectors.toList());
        this.inMemoryFilteredEntities.clear();
        this.inMemoryFilteredEntities.addAll(entities);
        pagination.totalData(entities.size()).build();
        toggleEmpty(entities.isEmpty());
        afterReloadListeners.forEach(a -> {
            try {
                a.accept(entities);
            } catch (Exception e) {
                logger.warn("afterReloadListeners throws an error.", e);
            }
        });
        grid.recalculateColumnWidths();
    }

    public void reloadGridData() {
        this.entities.clear();
        Map map = new LinkedHashMap<>();
        parameterMap.forEach((k, v) -> Optional.ofNullable(v).map(Supplier::get).ifPresent(v1 -> map.put(k, v1)));
        Optional.ofNullable(queryEntities(map)).ifPresent(this.entities::addAll);
        this.reloadGridDataInMemory();
    }

    public void switchEntityPage(Predicate predicate) {
        IntStream.range(0, inMemoryFilteredEntities.size()).filter(i -> predicate.test(inMemoryFilteredEntities.get(i)))
                .findFirst().ifPresent(i -> pagination.setCurrentPage(i / pagination.getPageSize() + 1).refresh());
    }

    @Override
    public ToolBar header() {
        return header;
    }

    @Override
    public Collection getEntities() {
        return entities;
    }

    @Override
    public G getGrid() {
        return grid;
    }

    @Override
    public List> getMenuButtons() {
        return menuButtons;
    }

    @Override
    public FluentCrudView getCrudView() {
        return this;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy