org.opensingular.lib.wicket.util.modal.BSModalBorder.java Source code

Java tutorial

Introduction

Here is the source code for org.opensingular.lib.wicket.util.modal.BSModalBorder.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.lib.wicket.util.modal;

import org.apache.wicket.AttributeModifier;
import org.apache.wicket.Component;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.ajax.markup.html.form.AjaxButton;
import org.apache.wicket.behavior.AttributeAppender;
import org.apache.wicket.behavior.Behavior;
import org.apache.wicket.event.IEvent;
import org.apache.wicket.feedback.IFeedbackMessageFilter;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.head.CssHeaderItem;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.border.Border;
import org.apache.wicket.markup.html.form.Button;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.FormComponent;
import org.apache.wicket.markup.html.form.ILabelProvider;
import org.apache.wicket.markup.html.link.AbstractLink;
import org.apache.wicket.markup.html.panel.Fragment;
import org.apache.wicket.markup.repeater.RepeatingView;
import org.apache.wicket.model.AbstractReadOnlyModel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.ResourceModel;
import org.apache.wicket.util.visit.IVisitor;
import org.opensingular.lib.commons.lambda.IConsumer;
import org.opensingular.lib.wicket.util.ajax.AjaxErrorEventPayload;
import org.opensingular.lib.wicket.util.feedback.BSFeedbackPanel;
import org.opensingular.lib.wicket.util.feedback.NotContainedFeedbackMessageFilter;
import org.opensingular.lib.wicket.util.jquery.JQuery;

import java.io.Serializable;

import static org.opensingular.lib.wicket.util.util.WicketUtils.$b;

@SuppressWarnings({ "serial" })
public class BSModalBorder extends Border {

    private static final String BUTTON_LABEL = "label";

    public enum ButtonStyle {
        //@formatter:off
        EMPTY(""), DEFAULT("btn-default"), PRIMARY("btn-primary"), LINK("btn-link"), DANGER("btn-danger"), BLUE(
                "blue"), CANCEL("cancel-btn"), CONFIRM("confirm-btn");
        //@formatter:on

        private String cssClass;

        ButtonStyle(String cssClass) {
            this.cssClass = cssClass;
        }

        public String getCssClass() {
            return cssClass;
        }

        public IModel<String> cssClassModel() {
            return new AbstractReadOnlyModel<String>() {
                @Override
                public String getObject() {
                    return getCssClass();
                }
            };
        }
    }

    public static enum Size {
        NORMAL("modal-belver"), LARGE("modal-lg"), SMALL("modal-sm"), FULL("modal-full"), FIT("modal-fit");
        protected final String styleClass;

        private Size(String styleClass) {
            this.styleClass = styleClass;
        }
    }

    private static final String DIALOG = "dialog";
    private static final String CLOSE_ICON = "closeIcon";
    private static final String COMPRESS_ICON = "compressIcon";
    private static final String EXPAND_ICON = "expandIcon";
    private static final String TITLE = "title";
    private static final String HEADER = "header";
    private static final String FOOTER = "footer";

    private Size size = Size.NORMAL;
    private boolean dismissible = false;
    private boolean withAutoFocus = true;

    private final RepeatingView buttonsContainer = new RepeatingView("buttons");
    protected BSFeedbackPanel feedbackGeral = newFeedbackPanel("feedbackGeral", this, newIFeedbackMessageFilter());

    protected BSFeedbackPanel newFeedbackPanel(String id, BSModalBorder fence,
            IFeedbackMessageFilter messageFilter) {
        return new BSFeedbackPanel(id, fence, messageFilter);
    }

    protected IFeedbackMessageFilter newIFeedbackMessageFilter() {
        return new NotContainedFeedbackMessageFilter(getBodyContainer());
    }

    private final Component closeIcon;
    private final Component compressIcon;
    private final Component expandIcon;
    private IConsumer<AjaxRequestTarget> closeIconCallBack;

    public BSModalBorder(String id) {
        this(id, null);
    }

    public BSModalBorder(String id, IModel<?> model) {
        super(id, model);
        setOutputMarkupId(true).setOutputMarkupPlaceholderTag(true);

        final IModel<String> modalSizeModel = new AbstractReadOnlyModel<String>() {
            @Override
            public String getObject() {
                return size.styleClass;
            }
        };

        final WebMarkupContainer dialog = new WebMarkupContainer(DIALOG);
        final WebMarkupContainer header = new WebMarkupContainer(HEADER);
        final WebMarkupContainer body = new WebMarkupContainer(BODY);
        final WebMarkupContainer footer = new WebMarkupContainer(FOOTER);

        closeIcon = newCloseIcon(CLOSE_ICON);
        compressIcon = newCompressIcon(COMPRESS_ICON);
        expandIcon = newExpandIcon(EXPAND_ICON);
        final Component title = newTitle(TITLE, getDefaultModel());
        final Fragment buttonsFragment = new Fragment("buttons", "buttonsFragment", this);

        header.setOutputMarkupId(true);
        footer.setOutputMarkupId(true);

        addToBorder(dialog.add(header.add(closeIcon).add(compressIcon).add(expandIcon).add(title)).add(body)
                .add(footer.add(feedbackGeral).add(buttonsFragment.add(buttonsContainer)))
                .add(new AttributeAppender("class", modalSizeModel, " "))

        );

        dialog.add($b.onReadyScript(comp -> JQuery.$(comp) + ".on('keypress', function (e) {"
                + "  var buttons = $(this).find('.btn-primary:visible');"
                + "  if (e.target.tagName.toLowerCase() != 'textarea' && buttons.length > 0 && e.which === 13) {"
                + "    e.preventDefault();" + "    $(buttons[buttons.length - 1]).click();" + "  }" + "});"));

        add(new AttributeAppender("class", Model.of("modal fade modal-scroll"), " "));
        add(new AttributeAppender("style", Model.of("visibility:visible"), ";"));
        add(new AttributeModifier("tabindex", "-1"));
        add($b.onReadyScript(this::getShowJavaScriptCallback));

        setVisible(false);
        setMinimizable(false);
    }

    @Override
    public void onEvent(IEvent<?> event) {
        Object payload = event.getPayload();
        if (payload instanceof AjaxErrorEventPayload) {
            AjaxRequestTarget target = ((AjaxErrorEventPayload) payload).getTarget();
            refreshContent(target);
            event.stop();
        }
    }

    @Override
    protected void onComponentTag(ComponentTag tag) {
        super.onComponentTag(tag);

        tag.put("tabindex", "-1");
        tag.put("role", DIALOG);
        tag.put("aria-labelledby", getTitle().getMarkupId());
        tag.put("aria-hidden", "true");
    }

    public boolean anyMessage() {
        return feedbackGeral.anyMessage();
    }

    public BSModalBorder addButton(ButtonStyle style, String labelKey, AjaxButton button) {
        IModel<String> model = null;
        if (labelKey != null) {
            model = new ResourceModel(labelKey);
        }
        return addButton(style, model, button);
    }

    public BSModalBorder addButton(ButtonStyle style, IModel<String> label, Button button) {
        if (label != null) {
            button.setLabel(label);
        }
        buttonsContainer.addOrReplace(button.add(newButtonLabel(BUTTON_LABEL, button))
                .add(new AttributeAppender("class", style.cssClassModel(), " ")));

        return this;
    }

    public BSModalBorder addButton(ButtonStyle style, AjaxButton button) {
        return addButton(style, (String) null, button);
    }

    public BSModalBorder addLink(ButtonStyle style, String labelKey, AjaxLink<?> button) {
        IModel<String> model = null;
        if (labelKey != null) {
            model = new ResourceModel(labelKey);
        }
        return addLink(style, model, button);
    }

    public BSModalBorder addLink(ButtonStyle style, IModel<String> label, AjaxLink<?> button) {
        buttonsContainer.addOrReplace(button.add(newLinkLabel(BUTTON_LABEL, button, label))
                .add(new AttributeAppender("class", style.cssClassModel(), " ")));
        return this;
    }

    public BSModalBorder addLink(ButtonStyle style, AjaxLink<?> button) {
        return addLink(style, (String) null, button);
    }

    public BSModalBorder removeButtons() {
        buttonsContainer.removeAll();
        return this;
    }

    public BSModalBorder setRenderModalBodyTag(boolean render) {
        getModalBody().setRenderBodyOnly(!render);
        return this;
    }

    public BSModalBorder setRenderModalFooterTag(boolean render) {
        getModalFooter().setRenderBodyOnly(!render);
        return this;
    }

    protected Label newButtonLabel(String id, ILabelProvider<String> button) {
        return new Label(id, new LabelModel(button));
    }

    protected Label newLinkLabel(String id, AbstractLink button, IModel<String> label) {
        return new Label(id, label);
    }

    @Override
    public BSModalBorder add(Behavior... behaviors) {
        return (BSModalBorder) super.add(behaviors);
    }

    @Override
    public BSModalBorder add(Component... children) {
        /* XXX: Verificar o problema que est ocorrendo na pgina de processos! */
        if (children.length > 0 && "buttonsFragment".equals(children[0].getId())) {
            return this;
        }
        return (BSModalBorder) super.add(children);
    }

    @Override
    public BSModalBorder addOrReplace(Component... children) {
        return (BSModalBorder) super.addOrReplace(children);
    }

    @Override
    public BSModalBorder setDefaultModel(IModel<?> model) {
        return (BSModalBorder) super.setDefaultModel(model);
    }

    public BSModalBorder setSize(Size size) {
        this.size = size;
        return this;
    }

    public BSModalBorder setCloseIconVisible(boolean visible) {
        closeIcon.setVisible(visible);
        return this;
    }

    public boolean isCloseIconVisible() {
        return closeIcon.isVisible();
    }

    public BSModalBorder setCloseIconCallback(IConsumer<AjaxRequestTarget> closeIconCallBack) {
        this.closeIconCallBack = closeIconCallBack;
        return this;
    }

    public BSModalBorder setMinimizable(boolean minimizable) {
        compressIcon.setVisible(minimizable);
        expandIcon.setVisible(minimizable);
        return this;
    }

    public BSModalBorder setDismissible(boolean dismissible) {
        this.dismissible = dismissible;
        return this;
    }

    public boolean isDismissible() {
        return dismissible;
    }

    public BSModalBorder setTitleText(IModel<String> titleModel) {
        getTitle().setDefaultModel(titleModel);
        return this;
    }

    public void show(AjaxRequestTarget target) {
        this.setVisible(true);
        if (target != null) {
            target.add(this);
        }
    }

    public void hide(AjaxRequestTarget target) {
        // limpo os valores, pois erros de validacao impedem o formulario de se ser recarregado 
        clearInputs();

        this.setVisible(false);

        if (target != null) {
            final String blockingFunction = "hide_hidden_wicket_modal";
            target.prependJavaScript(blockingFunction + "|" + getHideJavaScriptCallback(blockingFunction));
            target.add(this);
        }
    }

    public void clearInputs() {
        visitChildren((IVisitor<Component, Void>) (comp, visit) -> {
            if (comp instanceof Form) {
                ((Form<?>) comp).clearInput();
                visit.dontGoDeeper();
            } else if (comp instanceof FormComponent) {
                ((FormComponent<?>) comp).clearInput();
            }
        });
    }

    public void focusFirstComponent(AjaxRequestTarget target) {
        getBodyContainer().visitChildren(FormComponent.class,
                (IVisitor<FormComponent<?>, Void>) (object, visit) -> {
                    if (object.isEnabledInHierarchy()) {
                        target.focusComponent(object);
                        visit.stop();
                    }
                });
    }

    public void refreshContent(AjaxRequestTarget target) {
        target.add(getModalHeader(), getModalFooter());
        for (Component child : getBodyContainer()) {
            target.add(child);
        }
    }

    public String getShowJavaScriptCallback() {
        StringBuilder sb = JQuery.$(this).append(".modal({").append("keyboard:").append(isDismissible())
                .append(",backdrop:").append(isDismissible() ? "true" : "'static'").append("})");
        if (withAutoFocus) {
            sb.append(
                    "" + "\n.on('shown.bs.modal',function(evt) {" + "\n $(this).find('.modal-body, .modal-footer')"
                            + "\n  .find('input:not([type=hidden]),select,textarea,button,object,a')"
                            + "\n  .filter(':visible')" + "\n  .first()" + "\n  .each(function(){ this.focus(); });"
                            + "\n})");
        }
        return sb.toString();
    }

    public CharSequence getHideJavaScriptCallback() {
        return getHideJavaScriptCallback(null);
    }

    public CharSequence getHideJavaScriptCallback(String blockingFunction) {
        StringBuilder sb = JQuery.$(this);

        if (blockingFunction != null)
            sb.append(".one('hidden.bs.modal', ").append(blockingFunction).append(')');

        return sb.append(".modal('hide')");

    }

    public final MarkupContainer getModalHeader() {
        return (MarkupContainer) get(DIALOG).get(HEADER);
    }

    public final MarkupContainer getModalBody() {
        return (MarkupContainer) get(DIALOG).get(BODY);
    }

    public final MarkupContainer getModalFooter() {
        return (MarkupContainer) get(DIALOG).get(FOOTER);
    }

    public final Component getCloseIcon() {
        return get(DIALOG).get(HEADER).get(CLOSE_ICON);
    }

    public Component getTitle() {
        return getModalHeader().get(TITLE);
    }

    protected Component newCloseIcon(String id) {
        return new AjaxLink<Void>(id) {
            @Override
            public void onClick(AjaxRequestTarget target) {
                onCloseClicked(target);
            }
        };
    }

    protected Component newCompressIcon(String id) {
        return new WebMarkupContainer(id).add($b.onReadyScript(comp -> JQuery.$(comp) + ""
                + ".on('click', function() {" + JQuery.$(expandIcon) + ".show();" + JQuery.$(compressIcon)
                + ".hide();" + JQuery.$(getModalBody()) + ".slideUp();" + JQuery.$(getModalFooter()) + ".slideUp();"
                + " $('.modal-backdrop.fade.in').css('opacity',0.2);" + "})" + ";")).add(new Behavior() {
                    @Override
                    public void renderHead(Component component, IHeaderResponse response) {
                        super.renderHead(component, response);
                        response.render(
                                CssHeaderItem.forCSS(
                                        "" + ".modal-header-icon {" + " background-color: transparent;"
                                                + " float: right;" + " border: 0;" + " margin: 0;" + " padding: 0;"
                                                + " border-image: none;" + " line-height: 14px;"
                                                + " margin-top: -4px;" + " margin-right: 8px;" + " color: #ccc;"
                                                + "}" + ".modal-header-icon:hover {" + " color: #888;" + "}",
                                        "ModalBorder_modal-header-icon"));
                    }
                });
    }

    protected Component newExpandIcon(String id) {
        return new WebMarkupContainer(id).add($b.onReadyScript(comp -> JQuery.$(comp) + ""
                + ".on('click', function() {" + JQuery.$(expandIcon) + ".hide();" + JQuery.$(compressIcon)
                + ".show();" + JQuery.$(getModalBody()) + ".slideDown();" + JQuery.$(getModalFooter())
                + ".slideDown();" + " $('.modal-backdrop.fade.in').css('opacity','');" + "})"
                + ".css('display','none')" + ";"));
    }

    protected Component newTitle(String id, IModel<?> titleModel) {
        return new Label(id, titleModel);
    }

    protected void onCloseClicked(AjaxRequestTarget target) {
        if (closeIconCallBack != null) {
            closeIconCallBack.accept(target);
        }
        hide(target);
    }

    private static final class LabelModel extends AbstractReadOnlyModel<String> {
        private final LabelModelProvider provider;

        public LabelModel(ILabelProvider<String> provider) {
            this.provider = () -> {
                IModel<String> label = provider.getLabel();
                return (label != null) ? label.getObject() : null;
            };
        }

        @Override
        public String getObject() {
            return String.valueOf(provider.get());
        }

        interface LabelModelProvider extends Serializable {
            Object get();
        }
    }

    public boolean isWithAutoFocus() {
        return withAutoFocus;
    }

    public BSModalBorder setWithAutoFocus(boolean withAutoFocus) {
        this.withAutoFocus = withAutoFocus;
        return this;
    }
}