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

org.patternfly.component.modal.Modal Maven / Gradle / Ivy

There is a newer version: 0.2.11
Show newest version
/*
 *  Copyright 2023 Red Hat
 *
 *  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
 *
 *      https://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.patternfly.component.modal;

import java.util.ArrayList;
import java.util.List;

import org.gwtproject.event.shared.HandlerRegistration;
import org.jboss.elemento.Attachable;
import org.jboss.elemento.Id;
import org.jboss.elemento.Key;
import org.jboss.elemento.logger.Logger;
import org.patternfly.component.Closeable;
import org.patternfly.component.ComponentDelegate;
import org.patternfly.component.ComponentType;
import org.patternfly.component.backdrop.Backdrop;
import org.patternfly.core.Aria;
import org.patternfly.handler.CloseHandler;
import org.patternfly.style.Classes;
import org.patternfly.style.Size;

import elemental2.dom.Event;
import elemental2.dom.HTMLElement;
import elemental2.dom.MutationRecord;

import static elemental2.dom.DomGlobal.document;
import static org.jboss.elemento.Elements.div;
import static org.jboss.elemento.Elements.failSafeRemoveFromParent;
import static org.jboss.elemento.Elements.insertAfter;
import static org.jboss.elemento.Elements.insertBefore;
import static org.jboss.elemento.Elements.setVisible;
import static org.jboss.elemento.EventType.bind;
import static org.jboss.elemento.EventType.click;
import static org.jboss.elemento.EventType.keydown;
import static org.patternfly.component.backdrop.Backdrop.backdrop;
import static org.patternfly.component.button.Button.button;
import static org.patternfly.component.modal.ModalHeader.modalHeader;
import static org.patternfly.core.Aria.describedBy;
import static org.patternfly.core.Aria.label;
import static org.patternfly.core.Aria.labelledBy;
import static org.patternfly.core.Aria.modal;
import static org.patternfly.core.Attributes.role;
import static org.patternfly.core.Roles.dialog;
import static org.patternfly.core.Validation.verifyEnum;
import static org.patternfly.handler.CloseHandler.fireEvent;
import static org.patternfly.handler.CloseHandler.shouldClose;
import static org.patternfly.icon.IconSets.fas.times;
import static org.patternfly.layout.bullseye.Bullseye.bullseye;
import static org.patternfly.style.Classes.alignTop;
import static org.patternfly.style.Classes.close;
import static org.patternfly.style.Classes.component;
import static org.patternfly.style.Classes.modalBox;
import static org.patternfly.style.Classes.modifier;
import static org.patternfly.style.Size.lg;
import static org.patternfly.style.Size.md;
import static org.patternfly.style.Size.sm;
import static org.patternfly.style.TypedModifier.swap;
import static org.patternfly.style.Variable.componentVar;
import static org.patternfly.style.Variables.MaxWidth;
import static org.patternfly.style.Variables.Width;

/**
 * A modal displays important information to a user without requiring them to navigate to a new page.
 *
 * @see https://www.patternfly.org/components/modal
 */
public class Modal extends ComponentDelegate implements Attachable, Closeable {

    // ------------------------------------------------------ factory

    public static Modal modal() {
        return new Modal();
    }

    // ------------------------------------------------------ instance

    private static final Logger logger = Logger.getLogger(Modal.class.getName());
    private final Backdrop backdrop;
    private final HTMLElement closeContainer;
    private final List> closeHandler;
    private boolean open;
    private boolean hideClose;
    private ModalHeader header;
    private ModalBody body;
    private ModalFooter footer;
    private HTMLElement target;
    private HandlerRegistration escapeHandler;
    boolean autoClose;

    Modal() {
        super(ComponentType.Modal);
        this.open = false;
        this.autoClose = false;
        this.hideClose = false;
        this.closeHandler = new ArrayList<>();

        HTMLElement modalElement;
        this.backdrop = backdrop()
                .add(bullseye()
                        .add(modalElement = div().css(component(modalBox))
                                .attr(role, dialog)
                                .aria(modal, true)
                                .add(closeContainer = div().css(component(modalBox, close))
                                        .add(button()
                                                .plain()
                                                .icon(times())
                                                .aria(label, "Close")
                                                .on(click, e -> close(e, true)))
                                        .element())
                                .element()));
        delegateTo(modalElement);
        Attachable.register(modalElement, this);
    }

    @Override
    public void attach(MutationRecord mutationRecord) {
        if (hideClose) {
            setVisible(closeContainer, false);
        }
        if (!element().hasAttribute(labelledBy)) {
            if (header != null && header.title != null) {
                String titleId;
                if (header.title.element().hasAttribute("id")) {
                    titleId = header.title.element().getAttribute("id");
                } else {
                    titleId = Id.unique(componentType().id, "header", "title");
                }
                aria(labelledBy, titleId);
                if (header.title.severity != null) {
                    css(header.title.severity.status.modifier());
                }
            }
        }
        if (!element().hasAttribute(Aria.describedBy)) {
            if (body != null) {
                String bodyId;
                if (body.element().hasAttribute("id")) {
                    bodyId = body.element().getAttribute("id");
                } else {
                    bodyId = Id.unique(componentType().id, "body");
                }
                aria(describedBy, bodyId);
            }
        }
    }

    // ------------------------------------------------------ add

    public Modal appendToBody() {
        return appendTo(document.body);
    }

    public Modal appendTo(HTMLElement element) {
        target = element;
        return this;
    }

    public Modal addHeader(String title) {
        return add(modalHeader().addTitle(title));
    }

    public Modal addHeader(ModalHeader header) {
        return add(header);
    }

    public Modal add(ModalHeader header) {
        if (this.header != null) {
            logger.warn("Header already added to modal %o", element());
        }
        this.header = header;
        insertAfter(header, closeContainer);
        return this;
    }

    public Modal addBody(ModalBody body) {
        return add(body);
    }

    public Modal add(ModalBody body) {
        if (this.body != null) {
            logger.warn("Body already added to modal %o", element());
        }
        this.body = body;
        if (header != null) {
            insertAfter(body, header.element());
        } else if (footer != null) {
            insertBefore(body, footer.element());
        } else {
            insertAfter(body, closeContainer);
        }
        return this;
    }

    public Modal addFooter(ModalFooter footer) {
        return add(footer);
    }

    public Modal add(ModalFooter footer) {
        if (this.footer != null) {
            logger.warn("Footer already added to modal %o", element());
        }
        this.footer = footer;
        if (body != null) {
            insertAfter(footer, body.element());
        } else if (header != null) {
            insertAfter(footer, header.element());
        } else {
            insertAfter(footer, closeContainer);
        }
        return this;
    }

    // ------------------------------------------------------ builder

    public Modal autoClose(boolean autoClose) {
        this.autoClose = autoClose;
        return this;
    }

    public Modal hideClose() {
        this.hideClose = true;
        return this;
    }

    public Modal maxWidth(String maxWidth) {
        // --pf-v5-c-modal-box--MaxWidth
        return componentVar(component(modalBox), MaxWidth).applyTo(this).set(maxWidth);
    }

    public Modal size(Size size) {
        if (verifyEnum(element(), "size", size, sm, md, lg)) {
            swap(this, element(), size, Size.values());
        }
        return this;
    }

    public Modal top() {
        return css(modifier(alignTop));
    }

    public Modal width(String width) {
        // --pf-v5-c-modal-box--Width
        return componentVar(component(modalBox), Width).applyTo(this).set(width);
    }

    @Override
    public Modal that() {
        return this;
    }

    // ------------------------------------------------------ events

    @Override
    public Modal onClose(CloseHandler closeHandler) {
        if (closeHandler != null) {
            this.closeHandler.add(closeHandler);
        }
        return null;
    }

    // ------------------------------------------------------ api

    public void open() {
        if (open) {
            logger.warn("Modal %o already open", element());
        } else {
            storeComponent();
            HTMLElement failSafeTarget = failSafeTarget();
            failSafeTarget.appendChild(backdrop.element());
            failSafeTarget.classList.add(component(Classes.backdrop, Classes.open));
            if (autoClose) {
                escapeHandler = bind(failSafeTarget, keydown, e -> {
                    if (Key.Escape.match(e) && autoClose) {
                        close(e, true);
                    }
                });
            }
            open = true;
        }
    }

    @Override
    public void close(Event event, boolean fireEvent) {
        if (open && shouldClose(this, closeHandler, event, fireEvent)) {
            failSafeRemoveFromParent(backdrop);
            failSafeTarget().classList.remove(component(Classes.backdrop, Classes.open));
            if (escapeHandler != null) {
                escapeHandler.removeHandler();
            }
            fireEvent(this, closeHandler, event, fireEvent);
            open = false;
        }
    }

    public ModalBody body() {
        return body;
    }

    // ------------------------------------------------------ internal

    private HTMLElement failSafeTarget() {
        return target == null ? document.body : target;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy