org.patternfly.component.alert.Alert Maven / Gradle / Ivy
/*
* 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.alert;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jboss.elemento.Attachable;
import org.jboss.elemento.Id;
import org.jboss.elemento.logger.Logger;
import org.patternfly.component.BaseComponent;
import org.patternfly.component.Closeable;
import org.patternfly.component.ComponentType;
import org.patternfly.component.Expandable;
import org.patternfly.component.Severity;
import org.patternfly.component.WithIcon;
import org.patternfly.component.WithIdentifier;
import org.patternfly.component.button.Button;
import org.patternfly.core.Aria;
import org.patternfly.core.ComponentContext;
import org.patternfly.core.Dataset;
import org.patternfly.handler.CloseHandler;
import org.patternfly.handler.ToggleHandler;
import org.patternfly.style.Classes;
import org.patternfly.style.Modifiers.Inline;
import org.patternfly.style.Modifiers.Plain;
import elemental2.dom.Element;
import elemental2.dom.Event;
import elemental2.dom.HTMLDivElement;
import elemental2.dom.HTMLElement;
import elemental2.dom.HTMLParagraphElement;
import elemental2.dom.MutationRecord;
import static elemental2.dom.DomGlobal.clearTimeout;
import static elemental2.dom.DomGlobal.setTimeout;
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.insertFirst;
import static org.jboss.elemento.Elements.p;
import static org.jboss.elemento.Elements.removeChildrenFrom;
import static org.jboss.elemento.Elements.span;
import static org.jboss.elemento.EventType.click;
import static org.jboss.elemento.EventType.mouseout;
import static org.jboss.elemento.EventType.mouseover;
import static org.patternfly.component.alert.AlertDescription.alertDescription;
import static org.patternfly.component.button.Button.button;
import static org.patternfly.core.Aria.atomic;
import static org.patternfly.core.Aria.expanded;
import static org.patternfly.core.Aria.label;
import static org.patternfly.core.Aria.live;
import static org.patternfly.handler.CloseHandler.fireEvent;
import static org.patternfly.handler.CloseHandler.shouldClose;
import static org.patternfly.icon.IconSets.fas.angleRight;
import static org.patternfly.icon.IconSets.fas.times;
import static org.patternfly.style.Classes.alert;
import static org.patternfly.style.Classes.component;
import static org.patternfly.style.Classes.icon;
import static org.patternfly.style.Classes.modifier;
import static org.patternfly.style.Classes.screenReader;
import static org.patternfly.style.Classes.toggle;
import static org.patternfly.style.Classes.truncate;
import static org.patternfly.style.Variable.componentVar;
/**
* An alert is a notification that provides brief information to the user without blocking their workflow.
*
* @see https://www.patternfly.org/components/alert
*/
public class Alert extends BaseComponent implements
ComponentContext,
Inline,
Plain,
Closeable,
Expandable, Attachable,
WithIdentifier,
WithIcon {
// ------------------------------------------------------ factory
public static Alert alert(Severity type, String title) {
return new Alert(type, Id.unique(ComponentType.Alert.id), title);
}
public static Alert alert(Severity type, String identifier, String title) {
return new Alert(type, identifier, title);
}
// ------------------------------------------------------ instance
private static final Logger logger = Logger.getLogger(Alert.class.getName());
public static final int DEFAULT_TIMEOUT = 8_000; // ms
static final int NO_TIMEOUT = -1; // ms
static final int MIN_TIMEOUT = 1_000; // ms
int timeout;
boolean expandable;
Button closeButton;
private double timeoutHandle;
private final String identifier;
private final Severity severity;
private final String title;
private final HTMLElement iconContainer;
private final HTMLParagraphElement titleElement;
private final Map data;
private final List> closeHandler;
private Button toggleButton;
private AlertDescription description;
private ToggleHandler toggleHandler;
Alert(Severity severity, String identifier, String title) {
super(ComponentType.Alert, div().css(component(alert), severity.status.modifier())
.data(Dataset.identifier, identifier)
.aria(label, severity.aria)
.element());
this.identifier = identifier;
this.severity = severity;
this.title = title;
this.data = new HashMap<>();
this.timeoutHandle = 0;
this.timeout = NO_TIMEOUT;
this.expandable = false;
this.closeHandler = new ArrayList<>();
add(iconContainer = div().css(component(alert, icon))
.add(severity.icon.get().element())
.element());
add(titleElement = p().css(component(alert, Classes.title))
.add(span().css(screenReader)
.textContent(severity.aria + ":"))
.add(title)
.element());
Attachable.register(this, this);
}
@Override
public void attach(MutationRecord mutationRecord) {
if (timeout > MIN_TIMEOUT) {
startTimeout();
on(mouseover, e -> stopTimeout());
on(mouseout, e -> startTimeout());
}
}
@Override
public void detach(MutationRecord mutationRecord) {
clearTimeout(timeoutHandle);
}
// ------------------------------------------------------ add
public Alert addActionGroup(AlertActionGroup actionGroup) {
return add(actionGroup);
}
/**
* Wraps the description inside a <p/>
element, adds it to a {@link AlertDescription} and finally adds it
* to this alert. Useful if your description is just a simple string.
*/
public Alert addDescription(String description) {
return add(alertDescription().add(p().textContent(description)));
}
public Alert addDescription(AlertDescription description) {
return add(description);
}
// override to ensure internal wiring
public Alert add(AlertDescription description) {
this.description = description;
this.description.element().hidden = element().classList.contains(modifier(Classes.expandable)) && !expanded();
add(description.element());
return this;
}
// ------------------------------------------------------ builder
public Alert closable() {
return closable(null);
}
public Alert closable(CloseHandler closeHandler) {
insertAfter(div().css(component(alert, Classes.action))
.add(closeButton = button().icon(times().element()).plain()
.aria(label, "Close " + severity.aria + ": " + title)
.on(click, event -> close(event, true)))
.element(), titleElement);
return onClose(closeHandler);
}
@Override
public Alert icon(Element icon) {
removeChildrenFrom(iconContainer);
iconContainer.appendChild(icon);
return this;
}
@Override
public Alert removeIcon() {
logger.warn("Removing the icon is not supported for alert %o", element());
return this;
}
public Alert expandable() {
return expandable(null);
}
public Alert expandable(ToggleHandler toggleHandler) {
css(modifier(Classes.expandable));
insertFirst(element(), div().css(component(alert, toggle))
.add(toggleButton = button().plain()
.on(click, e -> toggle())
.aria(expanded, false)
.aria(label, severity.aria + ": " + title + " details")
.add(span().css(component(alert, toggle, icon))
.add(angleRight().element())))
.element());
this.expandable = true;
this.toggleHandler = toggleHandler;
return this;
}
/**
* Make this alert a live region alert. Live region alerts allow you to expose dynamic content changes in a way that can be
* announced by assistive technologies.
*
* Set the following ARIA attributes:
*
* aria-live: "polite"
* aria-atomic: false
*
*
* @see https://www.patternfly.org/components/alert/accessibility#accessibility
* @see https://www.patternfly.org/components/alert/accessibility#aria-live
*/
public Alert liveRegion() {
aria(live, "polite");
aria(atomic, false);
return this;
}
public Alert timeout() {
return timeout(DEFAULT_TIMEOUT);
}
public Alert timeout(int timeout) {
this.timeout = timeout;
return this;
}
public Alert truncate() {
return truncate(1);
}
public Alert truncate(int lines) {
// TODO Add tooltip
titleElement.classList.add(modifier(truncate));
if (lines != 1) {
componentVar(component(alert, Classes.title), "max-lines").applyTo(titleElement).set(lines);
}
return this;
}
@Override
public Alert store(String key, T value) {
data.put(key, value);
return this;
}
@Override
public Alert that() {
return this;
}
// ------------------------------------------------------ aria
public Alert ariaLabel(String label) {
return aria(Aria.label, label);
}
public Alert ariaCloseLabel(String label) {
if (closeButton != null) {
closeButton.aria(Aria.label, label);
}
return this;
}
// ------------------------------------------------------ events
@Override
public Alert onClose(CloseHandler closeHandler) {
if (closeHandler != null) {
this.closeHandler.add(closeHandler);
}
return this;
}
// ------------------------------------------------------ api
@Override
public String identifier() {
return identifier;
}
@Override
public boolean has(String key) {
return data.containsKey(key);
}
@SuppressWarnings("unchecked")
public T get(String key) {
if (data.containsKey(key)) {
return (T) data.get(key);
}
return null;
}
@Override
public void close(Event event, boolean fireEvent) {
if (shouldClose(this, closeHandler, event, fireEvent)) {
stopTimeout();
AlertGroup alertGroup = lookupComponent(ComponentType.AlertGroup, true);
if (alertGroup != null) {
alertGroup.closeAlert(this);
} else {
failSafeRemoveFromParent(this);
}
fireEvent(this, closeHandler, event, fireEvent);
}
}
@Override
public void collapse(boolean fireEvent) {
if (!expandable) {
logger.warn("Alert %o is not expandable. Please use Alert.expandable() to make this an expandable alert.",
element());
return;
}
Expandable.collapse(element(), toggleButton.element(), description.element());
if (fireEvent && toggleHandler != null) {
toggleHandler.onToggle(new Event(""), this, false);
}
}
@Override
public void expand(boolean fireEvent) {
if (!expandable) {
logger.warn("Alert %o is not expandable. Please use Alert.expandable() to make this an expandable alert.",
element());
return;
}
Expandable.expand(element(), toggleButton.element(), description.element());
if (fireEvent && toggleHandler != null) {
toggleHandler.onToggle(new Event(""), this, true);
}
}
// ------------------------------------------------------ internal
private void startTimeout() {
timeoutHandle = setTimeout((o) -> close(), timeout);
}
private void stopTimeout() {
clearTimeout(timeoutHandle);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy