org.dominokit.domino.ui.modals.BaseModal Maven / Gradle / Ivy
package org.dominokit.domino.ui.modals;
import elemental2.dom.*;
import jsinterop.base.Js;
import org.dominokit.domino.ui.style.Color;
import org.dominokit.domino.ui.style.Style;
import org.dominokit.domino.ui.style.Styles;
import org.dominokit.domino.ui.utils.BaseDominoElement;
import org.dominokit.domino.ui.utils.DominoDom;
import org.dominokit.domino.ui.utils.DominoElement;
import org.dominokit.domino.ui.utils.Switchable;
import org.jboss.elemento.EventType;
import org.jboss.elemento.IsElement;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import static elemental2.dom.DomGlobal.document;
import static java.util.Objects.nonNull;
import static org.jboss.elemento.Elements.*;
public abstract class BaseModal> extends BaseDominoElement
implements IsModalDialog, Switchable {
private List openHandlers = new ArrayList<>();
private List closeHandlers = new ArrayList<>();
static int Z_INDEX = 1040;
public static class Modal implements IsElement {
private final HTMLDivElement root;
private final HTMLDivElement modalDialog;
private final HTMLDivElement modalHeader;
private final HTMLHeadingElement modalTitle;
private final HTMLDivElement modalBody;
private final HTMLDivElement modalContent;
private final HTMLDivElement modalFooter;
public Modal() {
this.root = div().css("modal", "fade")
.apply(e -> e.tabIndex = -1)
.attr("role", "dialog")
.add(modalDialog = div().css("modal-dialog")
.apply(e -> e.tabIndex = -1)
.attr("role", "document")
.add(modalContent = div().css("modal-content")
.add(modalHeader = div().css("modal-header")
.add(modalTitle = h(4).css("modal-title").element())
.element())
.add(modalBody = div().css("modal-body").element())
.add(modalFooter = div().css("modal-footer").element())
.element())
.element())
.element();
setVisible(root, false);
}
@Override
public HTMLDivElement element() {
return root;
}
public DominoElement getModalTitle() {
return DominoElement.of(modalTitle);
}
public DominoElement getModalBody() {
return DominoElement.of(modalBody);
}
public DominoElement getModalDialog() {
return DominoElement.of(modalDialog);
}
public DominoElement getModalContent() {
return DominoElement.of(modalContent);
}
public DominoElement getModalFooter() {
return DominoElement.of(modalFooter);
}
public DominoElement getModalHeader() {
return DominoElement.of(modalHeader);
}
}
protected Modal modalElement;
private boolean autoClose = true;
private ModalSize modalSize;
private ModalType modalType;
private Color color;
private Element firstFocusElement;
private Element lastFocusElement;
private Element activeElementBeforeOpen;
private List focusElements = new ArrayList<>();
private Text headerText = DomGlobal.document.createTextNode("");
private boolean open = false;
private boolean disabled = false;
private boolean autoAppendAndRemove = true;
private boolean modal = true;
public BaseModal() {
modalElement = new Modal();
modalElement.getModalHeader().hide();
modalElement.getModalTitle().appendChild(headerText);
addTabIndexHandler();
}
public BaseModal(String title) {
this();
showHeader();
modalElement.modalTitle.textContent = title;
}
void addTabIndexHandler() {
element().addEventListener(EventType.keydown.getName(), evt -> {
if(evt instanceof KeyboardEvent) {
KeyboardEvent keyboardEvent = (KeyboardEvent) evt;
switch (keyboardEvent.code) {
case "Tab":
initFocusElements();
if (focusElements.size() <= 1) {
evt.preventDefault();
}
if (keyboardEvent.shiftKey) {
handleBackwardTab(evt);
} else {
handleForwardTab(evt);
}
if (!focusElements.contains(DominoDom.document.activeElement) && nonNull(firstFocusElement)) {
firstFocusElement.focus();
}
break;
case "Escape":
if (isAutoClose()) {
close();
}
break;
default:
break;
}
}
});
}
private void handleBackwardTab(Event evt) {
if (DominoDom.document.activeElement.equals(firstFocusElement)) {
evt.preventDefault();
lastFocusElement.focus();
}
}
private void handleForwardTab(Event evt) {
if (DominoDom.document.activeElement.equals(lastFocusElement)) {
evt.preventDefault();
firstFocusElement.focus();
}
}
@Override
public T appendChild(Node content) {
modalElement.modalBody.appendChild(content);
return (T) this;
}
@Override
public T appendChild(IsElement> content) {
return appendChild(content.element());
}
@Override
public T appendFooterChild(Node content) {
modalElement.modalFooter.appendChild(content);
return (T) this;
}
@Override
public T appendFooterChild(IsElement> content) {
return appendFooterChild(content.element());
}
@Override
public T large() {
return setSize(ModalSize.LARGE);
}
@Override
public T small() {
return setSize(ModalSize.SMALL);
}
public T setSize(ModalSize size) {
DominoElement modalElement = DominoElement.of(this.modalElement);
if (nonNull(modalSize)) {
modalElement.style().remove(modalSize.style);
}
modalElement.style().add(size.style);
this.modalSize = size;
return (T) this;
}
public T setType(ModalType type) {
DominoElement modalElement = DominoElement.of(this.modalElement);
if (nonNull(modalType)) {
modalElement.style().remove(modalType.style);
}
modalElement.style().add(type.style);
this.modalType = type;
return (T) this;
}
@Override
public T setModalColor(Color color) {
if (nonNull(this.color)) {
modalElement.getModalContent().style().remove(this.color.getStyle());
}
modalElement.getModalContent().style().add(color.getStyle());
this.color = color;
return (T) this;
}
@Override
public T setAutoClose(boolean autoClose) {
this.autoClose = autoClose;
return (T) this;
}
@Override
public T open() {
if (isEnabled()) {
style().removeProperty("z-index");
if (autoAppendAndRemove) {
element().remove();
document.body.appendChild(element());
}
initFocusElements();
activeElementBeforeOpen = DominoDom.document.activeElement;
addBackdrop();
style().add(ModalStyles.IN);
style().setDisplay("block");
if (nonNull(firstFocusElement)) {
firstFocusElement.focus();
if (!Objects.equals(DominoDom.document.activeElement, firstFocusElement)) {
if (nonNull(lastFocusElement)) {
lastFocusElement.focus();
}
}
}
openHandlers.forEach(OpenHandler::onOpen);
this.open = true;
ModalBackDrop.push(this);
}
return (T) this;
}
public void addBackdrop() {
if (modal) {
if (ModalBackDrop.openedModalsCount() <= 0 || !DominoElement.of(ModalBackDrop.INSTANCE).isAttached()) {
document.body.appendChild(ModalBackDrop.INSTANCE);
DominoElement.of(document.body).style().add(ModalStyles.MODAL_OPEN);
} else {
Z_INDEX = Z_INDEX + 10;
ModalBackDrop.INSTANCE.style.setProperty("z-index", Z_INDEX + "");
element().style.setProperty("z-index", (Z_INDEX + 10) + "");
}
}
}
public void removeBackDrop() {
if (modal) {
if (ModalBackDrop.openedModalsCount() < 1 || ModalBackDrop.allOpenedNotModals()) {
ModalBackDrop.INSTANCE.remove();
DominoElement.of(document.body).style().remove(ModalStyles.MODAL_OPEN);
} else {
Z_INDEX = Z_INDEX - 10;
ModalBackDrop.INSTANCE.style.setProperty("z-index", Z_INDEX + "");
element().style.setProperty("z-index", (Z_INDEX + 10) + "");
}
}
}
private void initFocusElements() {
NodeList elementNodeList = element().querySelectorAll(
"a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex=\"0\"]");
List elements = elementNodeList.asList();
if (elements.size() > 0) {
focusElements = elements;
firstFocusElement = focusElements.get(0);
lastFocusElement = elements.get(elements.size() - 1);
} else {
lastFocusElement = modalElement.modalContent;
}
}
@Override
public T close() {
element().classList.remove(ModalStyles.IN);
element().style.display = "none";
if (nonNull(activeElementBeforeOpen)) {
activeElementBeforeOpen.focus();
}
if (autoAppendAndRemove) {
element().remove();
}
this.open = false;
if (ModalBackDrop.contains(this)) {
ModalBackDrop.popModal(this);
}
removeBackDrop();
closeHandlers.forEach(CloseHandler::onClose);
return (T) this;
}
public boolean isAutoClose() {
return autoClose;
}
@Override
public T hideFooter() {
modalElement.getModalFooter().style().setDisplay("none");
return (T) this;
}
@Override
public T showFooter() {
modalElement.getModalFooter().style().setDisplay("block");
return (T) this;
}
@Override
public T hideHeader() {
modalElement.getModalHeader().hide();
return (T) this;
}
@Override
public T showHeader() {
modalElement.getModalHeader().show();
return (T) this;
}
@Override
public T hideTitle() {
modalElement.getModalTitle().hide();
return (T) this;
}
@Override
public T showTitle() {
modalElement.getModalTitle().show();
return (T) this;
}
@Override
public T setTitle(String title) {
showHeader();
headerText.textContent = title;
getHeaderElement().clearElement();
getHeaderElement().appendChild(headerText);
return (T) this;
}
@Override
public DominoElement getDialogElement() {
return DominoElement.of(modalElement.modalDialog);
}
@Override
public DominoElement getContentElement() {
return DominoElement.of(modalElement.modalContent);
}
@Override
public DominoElement getHeaderElement() {
return DominoElement.of(modalElement.modalTitle);
}
@Override
public DominoElement getHeaderContainerElement() {
return DominoElement.of(modalElement.modalHeader);
}
@Override
public DominoElement getBodyElement() {
return DominoElement.of(modalElement.modalBody);
}
@Override
public DominoElement getFooterElement() {
return DominoElement.of(modalElement.modalFooter);
}
@Override
public HTMLDivElement element() {
return modalElement.element();
}
/**
* use {@link #addOpenListener(OpenHandler)}
*/
@Override
@Deprecated
public T onOpen(OpenHandler openHandler) {
return addOpenListener(openHandler);
}
@Override
public T addOpenListener(OpenHandler openHandler) {
this.openHandlers.add(openHandler);
return (T) this;
}
/**
* use {@link #addCloseListener(CloseHandler)}
*/
@Override
@Deprecated
public T onClose(CloseHandler closeHandler) {
return addCloseListener(closeHandler);
}
@Override
public T addCloseListener(CloseHandler closeHandler) {
this.closeHandlers.add(closeHandler);
return (T) this;
}
@Override
public T removeOpenHandler(OpenHandler openHandler) {
this.openHandlers.remove(openHandler);
return (T) this;
}
@Override
public T removeCloseHandler(CloseHandler closeHandler) {
this.closeHandlers.remove(closeHandler);
return (T) this;
}
public boolean isOpen() {
return open;
}
@Override
public T enable() {
this.disabled = false;
return (T) this;
}
@Override
public T disable() {
this.disabled = true;
return (T) this;
}
@Override
public boolean isEnabled() {
return !disabled;
}
@Override
public T setAutoAppendAndRemove(boolean autoAppendAndRemove) {
this.autoAppendAndRemove = autoAppendAndRemove;
return (T) this;
}
@Override
public T centerVertically() {
Style.of(modalElement.modalDialog).add(Styles.vertical_center);
return (T) this;
}
@Override
public T deCenterVertically() {
Style.of(modalElement.modalDialog).remove(Styles.vertical_center);
return (T) this;
}
@Override
public boolean getAutoAppendAndRemove() {
return this.autoAppendAndRemove;
}
public boolean isModal() {
return modal;
}
public T setModal(boolean modal) {
this.modal = modal;
return (T) this;
}
}