org.opensingular.form.wicket.mapper.masterdetail.MasterDetailPanel.java Source code

Java tutorial

Introduction

Here is the source code for org.opensingular.form.wicket.mapper.masterdetail.MasterDetailPanel.java

Source

/*
 * Copyright (C) 2016 Singular Studios (a.k.a Atom Tecnologia) - www.opensingular.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.opensingular.form.wicket.mapper.masterdetail;

import org.apache.wicket.Component;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.opensingular.form.SFormUtil;
import org.opensingular.form.SIComposite;
import org.opensingular.form.SIList;
import org.opensingular.form.SInstance;
import org.opensingular.form.SType;
import org.opensingular.form.STypeComposite;
import org.opensingular.form.STypeSimple;
import org.opensingular.form.document.SDocument;
import org.opensingular.form.type.basic.AtrBasic;
import org.opensingular.form.validation.IValidationError;
import org.opensingular.form.validation.ValidationErrorLevel;
import org.opensingular.form.view.SViewListByMasterDetail;
import org.opensingular.form.wicket.ISValidationFeedbackHandlerListener;
import org.opensingular.form.wicket.SValidationFeedbackHandler;
import org.opensingular.form.wicket.WicketBuildContext;
import org.opensingular.form.wicket.component.SingularFormWicket;
import org.opensingular.form.wicket.enums.ViewMode;
import org.opensingular.form.wicket.feedback.FeedbackFence;
import org.opensingular.form.wicket.feedback.SValidationFeedbackCompactPanel;
import org.opensingular.form.wicket.mapper.AbstractListMapper;
import org.opensingular.form.wicket.mapper.MapperCommons;
import org.opensingular.form.wicket.mapper.behavior.RequiredListLabelClassAppender;
import org.opensingular.form.wicket.mapper.common.util.ColumnType;
import org.opensingular.form.wicket.model.ISInstanceAwareModel;
import org.opensingular.form.wicket.model.SInstanceListItemModel;
import org.opensingular.form.wicket.util.WicketFormProcessing;
import org.opensingular.lib.commons.lambda.IConsumer;
import org.opensingular.lib.commons.lambda.IFunction;
import org.opensingular.lib.wicket.util.datatable.BSDataTable;
import org.opensingular.lib.wicket.util.datatable.BSDataTableBuilder;
import org.opensingular.lib.wicket.util.datatable.BaseDataProvider;
import org.opensingular.lib.wicket.util.datatable.IBSAction;
import org.opensingular.lib.wicket.util.datatable.column.BSActionPanel;
import org.opensingular.lib.wicket.util.datatable.column.BSPropertyColumn;
import org.opensingular.lib.wicket.util.model.IMappingModel;
import org.opensingular.lib.wicket.util.model.IReadOnlyModel;
import org.opensingular.lib.wicket.util.resource.Icone;
import org.opensingular.lib.wicket.util.scripts.Scripts;
import org.opensingular.lib.wicket.util.util.JavaScriptUtils;
import org.opensingular.lib.wicket.util.util.WicketUtils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;

import static org.apache.commons.lang3.StringUtils.trimToEmpty;
import static org.opensingular.lib.wicket.util.util.Shortcuts.$b;
import static org.opensingular.lib.wicket.util.util.Shortcuts.$m;

public class MasterDetailPanel extends Panel {

    private final WicketBuildContext ctx;
    private final IModel<SIList<SInstance>> lista;
    private final MasterDetailModal modal;
    private final SViewListByMasterDetail view;

    private SingularFormWicket<?> form;
    private WebMarkupContainer head;
    private Label headLabel;
    private WebMarkupContainer body;
    private Component table;
    private WebMarkupContainer footer;
    private AjaxLink addButton;
    private Label addButtonLabel;
    private SValidationFeedbackCompactPanel feedback;

    public MasterDetailPanel(String id, WicketBuildContext ctx, IModel<SIList<SInstance>> lista,
            MasterDetailModal modal, SViewListByMasterDetail view) {
        super(id);
        this.ctx = ctx;
        this.lista = lista;
        this.modal = modal;
        this.view = view;
        createComponents();
        addComponents();
        addBehaviours();
    }

    private void addBehaviours() {
        footer.add($b.visibleIf(() -> AbstractListMapper.canAddItems(ctx)));
        addButton.add(WicketUtils.$b.attr("title", addButtonLabel.getDefaultModel()));
        addButton.setEscapeModelStrings(false);
    }

    private void addComponents() {
        add(form);
        form.add(head.add(headLabel));
        form.add(body.add(table));
        form.add(footer.add(addButton.add(addButtonLabel)));
        form.add(feedback);
    }

    private void createComponents() {
        form = new SingularFormWicket<>("form");
        head = new WebMarkupContainer("head");
        headLabel = newHeadLabel();
        body = new WebMarkupContainer("body");
        footer = new WebMarkupContainer("footer");
        addButton = newAddAjaxLink();
        addButtonLabel = new Label("addButtonLabel", Model.of(AbstractListMapper.defineLabel(ctx)));
        table = newTable("table");
        feedback = ctx.createFeedbackCompactPanel("feedback");
    }

    private BSDataTable<SInstance, ?> newTable(String id) {

        final BSDataTableBuilder<SInstance, ?, ?> builder = new MasterDetailBSDataTableBuilder<>(newDataProvider())
                .withNoRecordsToolbar();
        final BSDataTable<SInstance, ?> dataTable;

        configureColumns(view.getColumns(), builder, lista, modal, ctx, ctx.getViewMode(), view);
        dataTable = builder.build(id);

        dataTable.setOnNewRowItem((IConsumer<Item<SInstance>>) rowItem -> {
            SValidationFeedbackHandler feedbackHandler = SValidationFeedbackHandler
                    .bindTo(new FeedbackFence(rowItem)).addInstanceModel(rowItem.getModel())
                    .addListener(ISValidationFeedbackHandlerListener.withTarget(t -> t.add(rowItem)));
            rowItem.add($b.classAppender("singular-form-table-row can-have-error"));
            rowItem.add($b.classAppender("has-errors",
                    $m.ofValue(feedbackHandler).map(SValidationFeedbackHandler::containsNestedErrors)));
        });

        dataTable.setStripedRows(false);
        dataTable.setHoverRows(false);
        dataTable.setBorderedTable(false);

        return dataTable;
    }

    private Label newHeadLabel() {

        final AtrBasic attr = lista.getObject().asAtr();
        final IModel<String> labelModel = $m.ofValue(trimToEmpty(attr.getLabel()));

        ctx.configureContainer(labelModel);

        Label label = new Label("headLabel", labelModel);

        if (ctx.getViewMode() != null && ctx.getViewMode().isEdition()) {
            label.add(new RequiredListLabelClassAppender(ctx.getModel()));
        }
        return label;
    }

    private AjaxLink<String> newAddAjaxLink() {
        return new AjaxLink<String>("addButton") {
            @Override
            public void onClick(AjaxRequestTarget target) {
                final SInstance si = ctx.getModel().getObject();
                if (si instanceof SIList) {
                    final SIList<?> sil = (SIList<?>) si;
                    if (sil.getType().getMaximumSize() != null && sil.getType().getMaximumSize() == sil.size()) {
                        target.appendJavaScript(";bootbox.alert('A Quantidade mxima de valores foi atingida.');");
                        target.appendJavaScript(Scripts.multipleModalBackDrop());
                    } else {
                        modal.setOnHideCallback(t -> t.focusComponent(this));
                        modal.showNew(target);
                    }
                }
            }
        };
    }

    private void configureColumns(List<SViewListByMasterDetail.Column> mapColumns,
            BSDataTableBuilder<SInstance, ?, ?> builder, IModel<? extends SInstance> model, MasterDetailModal modal,
            WicketBuildContext ctx, ViewMode viewMode, SViewListByMasterDetail view) {

        final List<ColumnType> columnTypes = new ArrayList<>();

        if (mapColumns.isEmpty()) {
            final SType<?> tipo = ((SIList<?>) model.getObject()).getElementsType();
            if (tipo instanceof STypeSimple) {
                columnTypes.add(new ColumnType(tipo.getName(), null));
            } else if (tipo.isComposite()) {
                ((STypeComposite<?>) tipo).getFields().stream().filter(mtipo -> mtipo instanceof STypeSimple)
                        .forEach(mtipo -> columnTypes.add(new ColumnType(mtipo.getName(), null)));
            }
        } else {
            mapColumns.forEach(
                    (col) -> columnTypes.add(new ColumnType(Optional.ofNullable(col.getTypeName()).orElse(null),
                            col.getCustomLabel(), col.getDisplayValueFunction())));
        }

        for (ColumnType columnType : columnTypes) {
            final String label = columnType.getCustomLabel(model.getObject());
            final String typeName = columnType.getTypeName();
            final IModel<String> labelModel = $m.ofValue(label);
            propertyColumnAppender(builder, labelModel, $m.get(() -> typeName), columnType.getDisplayFunction());
        }

        actionColumnAppender(builder, model, modal, ctx, viewMode, view);
    }

    /**
     * Adiciona as aes a coluna de aes de mestre detalhe.
     */
    private void actionColumnAppender(BSDataTableBuilder<SInstance, ?, ?> builder,
            IModel<? extends SInstance> model, MasterDetailModal modal, WicketBuildContext ctx, ViewMode vm,
            SViewListByMasterDetail view) {
        builder.appendActionColumn($m.ofValue("Aes"), ac -> {
            if (vm.isEdition() && view.isDeleteEnabled()) {
                ac.appendAction(buildRemoveActionConfig(), buildRemoveAction(model, ctx));
            }
            ac.appendAction(buildViewOrEditActionConfig(vm, view), buildViewOrEditAction(modal, ctx));
            ac.appendAction(buildShowErrorsActionConfig(model), buildShowErrorsAction());
        });
    }

    private BSActionPanel.ActionConfig<SInstance> buildRemoveActionConfig() {
        return new BSActionPanel.ActionConfig<SInstance>().styleClasses(Model.of("list-detail-remove"))
                .iconeModel(Model.of(Icone.REMOVE)).titleFunction(rowModel -> "Remover");
    }

    private IBSAction<SInstance> buildRemoveAction(IModel<? extends SInstance> model, WicketBuildContext ctx) {
        return (target, rowModel) -> {
            final SIList<?> list = ((SIList<?>) model.getObject());
            list.remove(list.indexOf(rowModel.getObject()));
            target.add(ctx.getContainer());
            WicketFormProcessing.onFieldProcess(form, target, model);
        };
    }

    private BSActionPanel.ActionConfig<SInstance> buildViewOrEditActionConfig(ViewMode viewMode,
            SViewListByMasterDetail view) {
        final Icone openModalIcon = viewMode.isEdition() && view.isEditEnabled() ? Icone.PENCIL : Icone.EYE;
        return new BSActionPanel.ActionConfig<SInstance>().iconeModel(Model.of(openModalIcon))
                .styleClasses(Model.of("list-detail-edit"))
                .titleFunction(rowModel -> viewMode.isEdition() && view.isEditEnabled() ? "Editar" : "Visualizar");
    }

    private IBSAction<SInstance> buildViewOrEditAction(MasterDetailModal modal, WicketBuildContext ctx) {
        return (target, rowModel) -> modal.showExisting(target, rowModel, ctx);
    }

    private BSActionPanel.ActionConfig<SInstance> buildShowErrorsActionConfig(IModel<? extends SInstance> model) {
        IMappingModel.of(model).map(it -> it.getNestedValidationErrors().size()).getObject();
        return new BSActionPanel.ActionConfig<SInstance>()
                .iconeModel(IReadOnlyModel.of(() -> Icone.EXCLAMATION_TRIANGLE)).styleClasses(Model.of("red"))
                .titleFunction(rowModel -> IMappingModel.of(rowModel)
                        .map(it -> (it.getNestedValidationErrors().size() + " erro(s) encontrado(s)")).getObject())
                .style($m.ofValue(MapperCommons.BUTTON_STYLE));
    }

    private IBSAction<SInstance> buildShowErrorsAction() {
        return new IBSAction<SInstance>() {
            @Override
            public void execute(AjaxRequestTarget target, IModel<SInstance> model) {
                SInstance baseInstance = model.getObject();
                SDocument doc = baseInstance.getDocument();
                Collection<IValidationError> errors = baseInstance.getNestedValidationErrors();
                if ((errors != null) && !errors.isEmpty()) {
                    String alertLevel = errors.stream().map(IValidationError::getErrorLevel)
                            .max(Comparator.naturalOrder())
                            .map(it -> it.le(ValidationErrorLevel.WARNING) ? "alert-warning" : "alert-danger")
                            .orElse(null);

                    final StringBuilder sb = new StringBuilder("<div><ul class='list-unstyled alert ")
                            .append(alertLevel).append("'>");
                    for (IValidationError error : errors) {
                        Optional<SInstance> inst = doc.findInstanceById(error.getInstanceId());
                        inst.ifPresent(sInstance -> sb.append("<li>")
                                .append(SFormUtil.generateUserFriendlyPath(sInstance, baseInstance)).append(": ")
                                .append(error.getMessage()).append("</li>"));
                    }
                    sb.append("</ul></div>");

                    target.appendJavaScript(
                            ";bootbox.alert('" + JavaScriptUtils.javaScriptEscape(sb.toString()) + "');");
                    target.appendJavaScript(Scripts.multipleModalBackDrop());
                }
            }

            @Override
            public boolean isVisible(IModel<SInstance> model) {
                return model != null && model.getObject() != null && model.getObject().hasNestedValidationErrors();
            }
        };
    }

    /**
     * property column isolado em outro mtodo para isolar o escopo de
     * serializao do lambda do appendPropertyColumn
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    private void propertyColumnAppender(BSDataTableBuilder<SInstance, ?, ?> builder, IModel<String> labelModel,
            IModel<String> sTypeNameModel, IFunction<SInstance, String> displayValueFunction) {
        IFunction<SIComposite, SInstance> toInstance = composto -> {
            String sTypeName = sTypeNameModel.getObject();
            if (sTypeName == null || composto == null) {
                return composto;
            }
            SType<?> sType = composto.getDictionary().getType(sTypeName);
            return (SInstance) composto.findDescendant(sType).orElse(null);
        };
        IFunction<SInstance, Object> propertyFunction = o -> displayValueFunction
                .apply(toInstance.apply((SIComposite) o));
        builder.appendColumn(new BSPropertyColumn(labelModel, propertyFunction) {
            @Override
            public IModel getDataModel(IModel rowModel) {
                return new ISInstanceAwareModel<Object>() {
                    @Override
                    public Object getObject() {
                        return propertyFunction.apply((SInstance) rowModel.getObject());
                    }

                    @Override
                    public void setObject(Object object) {
                        throw new UnsupportedOperationException();
                    }

                    @Override
                    public void detach() {
                        rowModel.detach();
                    }

                    @Override
                    public SInstance getSInstance() {
                        return toInstance.apply((SIComposite) rowModel.getObject());
                    }
                };
            }
        });
    }

    private BaseDataProvider<SInstance, ?> newDataProvider() {
        return new SIListDataProvider(lista);
    }

    static class SIListDataProvider extends BaseDataProvider<SInstance, Object> {
        private final IModel<SIList<SInstance>> lista;

        public SIListDataProvider(IModel<SIList<SInstance>> lista) {
            this.lista = lista;
        }

        @Override
        public Iterator<SInstance> iterator(int first, int count, Object sortProperty, boolean ascending) {
            final SIList<SInstance> siList = lista.getObject();
            final List<SInstance> list = new ArrayList<>();
            for (int i = 0; (i < count) && (i + first < siList.size()); i++)
                list.add(siList.get(i + first));
            return list.iterator();
        }

        @Override
        public long size() {
            return lista.getObject().size();
        }

        @Override
        public IModel<SInstance> model(SInstance object) {
            return new SInstanceListItemModel<>(lista, lista.getObject().indexOf(object));
        }
    }
}