org.dominokit.domino.ui.modals.BaseModal Maven / Gradle / Ivy
/*
* Copyright © 2019 Dominokit
*
* 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.dominokit.domino.ui.modals;
import static elemental2.dom.DomGlobal.document;
import static java.util.Objects.nonNull;
import static org.jboss.elemento.Elements.*;
import elemental2.dom.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.dominokit.domino.ui.grid.flex.FlexDirection;
import org.dominokit.domino.ui.grid.flex.FlexItem;
import org.dominokit.domino.ui.grid.flex.FlexLayout;
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;
/**
* A base implementaton for components to show a pop-up
*
* @param the type of the component extending from this class
*/
public abstract class BaseModal>
extends BaseDominoElement implements IsModalDialog, Switchable {
private List openHandlers = new ArrayList<>();
private List closeHandlers = new ArrayList<>();
static int Z_INDEX = 1040;
/** a component that contains the modal elements */
public static class Modal implements IsElement {
private final DominoElement root;
private final DominoElement modalDialog;
private final FlexLayout modalContent;
private final FlexItem modalHeader;
private final DominoElement modalTitle;
private final FlexItem modalBody;
private final FlexItem modalFooter;
/** */
public Modal() {
root =
DominoElement.div().setTabIndex(-1).css("modal", "fade").setAttribute("role", "dialog");
modalDialog =
DominoElement.div().setTabIndex(-1).css("modal-dialog").setAttribute("role", "document");
modalContent =
FlexLayout.create().setDirection(FlexDirection.TOP_TO_BOTTOM).css("modal-content");
modalHeader = FlexItem.create().css("modal-header");
modalTitle = DominoElement.of(h(4)).css("modal-title");
modalBody = FlexItem.create().setFlexGrow(1).css("modal-body");
modalFooter = FlexItem.create().css("modal-footer");
root.appendChild(
modalDialog.appendChild(
modalContent
.appendChild(modalHeader.appendChild(modalTitle))
.appendChild(modalBody)
.appendChild(modalFooter)));
root.hide();
}
/** {@inheritDoc} */
@Override
public HTMLDivElement element() {
return root.element();
}
/**
* @return the {@link HTMLHeadingElement} that contains the title text wrapped as {@link
* DominoElement}
*/
public DominoElement getModalTitle() {
return DominoElement.of(modalTitle);
}
/** @return the {@link HTMLDivElement} of the modal body wrapped as a {@link DominoElement} */
public DominoElement getModalBody() {
return DominoElement.of(modalBody);
}
/**
* @return the {@link HTMLDivElement} that contains the the header and the body wrapped as
* {@link DominoElement}
*/
public DominoElement getModalDialog() {
return DominoElement.of(modalDialog);
}
/**
* @return the {@link HTMLDivElement} that has content container inside the modal body wrapped
* as {@link DominoElement}
*/
public DominoElement getModalContent() {
return DominoElement.of(modalContent);
}
/** @return the {@link HTMLDivElement} footer element wrapped as {@link DominoElement} */
public DominoElement getModalFooter() {
return DominoElement.of(modalFooter);
}
/**
* @return the {@link HTMLDivElement} that contains the heading element that contains the title
* text wrapped as {@link DominoElement}
*/
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;
private boolean autoFocus = true;
public BaseModal() {
modalElement = new Modal();
modalElement.getModalHeader().hide();
modalElement.getModalTitle().appendChild(headerText);
addTabIndexHandler();
}
/** @param title String modal header title */
public BaseModal(String title) {
this();
showHeader();
modalElement.modalTitle.setTextContent(title);
}
/** Force the tab to navigate inside the modal dialog only */
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();
}
}
/** Appends to the Modal body {@inheritDoc} */
@Override
public T appendChild(Node content) {
modalElement.modalBody.appendChild(content);
return (T) this;
}
/** Appends to the Modal body {@inheritDoc} */
@Override
public T appendChild(IsElement> content) {
return appendChild(content.element());
}
/** Appends to the Modal body {@inheritDoc} */
@Override
public T appendFooterChild(Node content) {
modalElement.modalFooter.appendChild(content);
return (T) this;
}
/** {@inheritDoc} */
@Override
public T appendFooterChild(IsElement> content) {
return appendFooterChild(content.element());
}
/** {@inheritDoc} */
@Override
public T large() {
return setSize(ModalSize.LARGE);
}
/** {@inheritDoc} */
@Override
public T small() {
return setSize(ModalSize.SMALL);
}
/**
* @param size {@link org.dominokit.domino.ui.modals.IsModalDialog.ModalSize}
* @return same Dialog instance
*/
public T setSize(ModalSize size) {
DominoElement modalElement = DominoElement.of(this.modalElement);
if (nonNull(modalSize)) {
modalElement.removeCss(modalSize.style);
}
modalElement.addCss(size.style);
this.modalSize = size;
return (T) this;
}
/**
* @param type {@link org.dominokit.domino.ui.modals.IsModalDialog.ModalType}
* @return same dialog instance
*/
public T setType(ModalType type) {
DominoElement modalElement = DominoElement.of(this.modalElement);
if (nonNull(modalType)) {
modalElement.removeCss(modalType.style);
}
modalElement.addCss(type.style);
this.modalType = type;
return (T) this;
}
/** {@inheritDoc} */
@Override
public T setModalColor(Color color) {
if (nonNull(this.color)) {
modalElement.getModalContent().removeCss(this.color.getStyle());
}
modalElement.getModalContent().addCss(color.getStyle());
this.color = color;
return (T) this;
}
/** {@inheritDoc} */
@Override
public T setAutoClose(boolean autoClose) {
this.autoClose = autoClose;
return (T) this;
}
/** {@inheritDoc} */
@Override
public T open() {
if (isEnabled()) {
removeCssProperty("z-index");
if (autoAppendAndRemove) {
element().remove();
document.body.appendChild(element());
}
initFocusElements();
activeElementBeforeOpen = DominoDom.document.activeElement;
addBackdrop();
style().addCss(ModalStyles.IN);
style().setDisplay("block");
if (nonNull(firstFocusElement) && isAutoFocus()) {
firstFocusElement.focus();
if (!Objects.equals(DominoDom.document.activeElement, firstFocusElement)) {
if (nonNull(lastFocusElement)) {
lastFocusElement.focus();
}
}
}
openHandlers.forEach(OpenHandler::onOpen);
this.open = true;
ModalBackDrop.push(this);
}
ModalBackDrop.showHideBodyScrolls();
return (T) this;
}
private void addBackdrop() {
if (modal) {
if (ModalBackDrop.openedModalsCount() <= 0
|| !DominoElement.of(ModalBackDrop.INSTANCE).isAttached()) {
DominoElement.body().appendChild(ModalBackDrop.INSTANCE);
} else {
Z_INDEX = Z_INDEX + 10;
ModalBackDrop.INSTANCE.style.setProperty("z-index", Z_INDEX + "");
element().style.setProperty("z-index", (Z_INDEX + 10) + "");
}
}
}
private void removeBackDrop() {
if (modal) {
if (ModalBackDrop.openedModalsCount() < 1 || ModalBackDrop.allOpenedNotModals()) {
ModalBackDrop.INSTANCE.remove();
} 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.element();
}
}
/** {@inheritDoc} */
@Override
public T close() {
if (this.open) {
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);
}
ModalBackDrop.showHideBodyScrolls();
return (T) this;
}
/**
* @return boolean
* @see #setAutoClose(boolean)
*/
public boolean isAutoClose() {
return autoClose;
}
/** {@inheritDoc} */
@Override
public T hideFooter() {
modalElement.getModalFooter().style().setDisplay("none");
return (T) this;
}
/** {@inheritDoc} */
@Override
public T showFooter() {
modalElement.getModalFooter().style().setDisplay("block");
return (T) this;
}
/** {@inheritDoc} */
@Override
public T hideHeader() {
modalElement.getModalHeader().hide();
return (T) this;
}
/** {@inheritDoc} */
@Override
public T showHeader() {
modalElement.getModalHeader().show();
return (T) this;
}
/** {@inheritDoc} */
@Override
public T hideTitle() {
modalElement.getModalTitle().hide();
return (T) this;
}
/** {@inheritDoc} */
@Override
public T showTitle() {
modalElement.getModalTitle().show();
return (T) this;
}
/** {@inheritDoc} */
@Override
public T setTitle(String title) {
showHeader();
headerText.textContent = title;
getHeaderElement().clearElement();
getHeaderElement().appendChild(headerText);
return (T) this;
}
/** {@inheritDoc} */
@Override
public DominoElement getDialogElement() {
return DominoElement.of(modalElement.modalDialog);
}
/** {@inheritDoc} */
@Override
public DominoElement getContentElement() {
return DominoElement.of(modalElement.modalContent);
}
/** {@inheritDoc} */
@Override
public DominoElement getHeaderElement() {
return DominoElement.of(modalElement.modalTitle);
}
/** {@inheritDoc} */
@Override
public DominoElement getHeaderContainerElement() {
return DominoElement.of(modalElement.modalHeader);
}
/** {@inheritDoc} */
@Override
public DominoElement getBodyElement() {
return DominoElement.of(modalElement.modalBody);
}
/** {@inheritDoc} */
@Override
public DominoElement getFooterElement() {
return DominoElement.of(modalElement.modalFooter);
}
/** {@inheritDoc} */
@Override
public HTMLDivElement element() {
return modalElement.element();
}
/** {@inheritDoc} */
@Override
public T addOpenListener(OpenHandler openHandler) {
this.openHandlers.add(openHandler);
return (T) this;
}
/** {@inheritDoc} */
@Override
public T addCloseListener(CloseHandler closeHandler) {
this.closeHandlers.add(closeHandler);
return (T) this;
}
/** {@inheritDoc} */
@Override
public T removeOpenHandler(OpenHandler openHandler) {
this.openHandlers.remove(openHandler);
return (T) this;
}
/** {@inheritDoc} */
@Override
public T removeCloseHandler(CloseHandler closeHandler) {
this.closeHandlers.remove(closeHandler);
return (T) this;
}
/** @return boolean, true if the modal is currently open */
public boolean isOpen() {
return open;
}
/** {@inheritDoc} */
@Override
public T enable() {
this.disabled = false;
return (T) this;
}
/** {@inheritDoc} */
@Override
public T disable() {
this.disabled = true;
return (T) this;
}
/** {@inheritDoc} */
@Override
public boolean isEnabled() {
return !disabled;
}
/** {@inheritDoc} */
@Override
public T setAutoAppendAndRemove(boolean autoAppendAndRemove) {
this.autoAppendAndRemove = autoAppendAndRemove;
return (T) this;
}
/** {@inheritDoc} */
@Override
public T centerVertically() {
Style.of(modalElement.modalDialog).addCss(Styles.vertical_center);
return (T) this;
}
/** {@inheritDoc} */
@Override
public T deCenterVertically() {
Style.of(modalElement.modalDialog).removeCss(Styles.vertical_center);
return (T) this;
}
/** {@inheritDoc} */
@Override
public boolean getAutoAppendAndRemove() {
return this.autoAppendAndRemove;
}
/** @return boolean, true if this modal will show an overlay to block the content behind it. */
public boolean isModal() {
return modal;
}
/**
* @param modal boolean,true to make this modal show an overlay to block the content behind it
* when it is open
* @return same dialog instance
*/
public T setModal(boolean modal) {
this.modal = modal;
return (T) this;
}
/** @return boolean, true if the modal should auto-focus first focusable element when opened. */
public boolean isAutoFocus() {
return autoFocus;
}
/**
* @param autoFocus boolean, true if the modal should auto-focus first focusable element when
* opened.
* @return same dialog instance
*/
public T setAutoFocus(boolean autoFocus) {
this.autoFocus = autoFocus;
return (T) this;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy