Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,11 @@ public static Optional<LinkkiComponentDefinition> findComponentDefinition(Annota
.map(annotation -> getComponentDefinition(annotation, annotatedElement));
}

public static List<Annotation> getComponentDefinitionAnnotations(AnnotatedElement annotatedElement) {
return LINKKI_COMPONENT_ANNOTATION
.findAnnotatedAnnotationsOn(annotatedElement).toList();
}

/**
* Finds the annotation of the {@link AnnotatedElement} that defines a
* {@link LinkkiComponentDefinition}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ private static <C, W extends ComponentWrapper> W createComponent(
return componentWrapper;
}

private static String getComponentId(BoundProperty boundProperty, Object pmo) {
public static String getComponentId(BoundProperty boundProperty, Object pmo) {
return boundProperty.getPmoProperty().isEmpty()
? Sections.getSectionId(pmo)
: boundProperty.getPmoProperty();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package org.linkki.core.ui.aspects;

import java.util.Optional;
import java.util.function.Consumer;

import org.apache.commons.lang3.StringUtils;
import org.linkki.core.binding.descriptor.aspect.Aspect;
import org.linkki.core.binding.descriptor.aspect.base.ModelToUiAspectDefinition;
import org.linkki.core.binding.wrapper.ComponentWrapper;
import org.linkki.core.ui.aspects.annotation.BindHelperText;
import org.linkki.core.vaadin.component.base.LinkkiText;

import com.vaadin.flow.component.HasHelper;
import com.vaadin.flow.component.HasTheme;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.combobox.ComboBoxVariant;
import com.vaadin.flow.component.combobox.MultiSelectComboBox;
import com.vaadin.flow.component.combobox.MultiSelectComboBoxVariant;
import com.vaadin.flow.component.textfield.TextArea;
import com.vaadin.flow.component.textfield.TextAreaVariant;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.component.textfield.TextFieldVariant;

import edu.umd.cs.findbugs.annotations.NonNull;

public class HelperTextAspectDefinition extends ModelToUiAspectDefinition<String> {

private static final String NAME = "helperText";

private final BindHelperText annotation;

public HelperTextAspectDefinition(BindHelperText annotation) {
this.annotation = annotation;
}

private void setThemeVariant(@NonNull HasTheme component) {
if (ComboBox.class.isAssignableFrom(component.getClass())) {
((ComboBox<?>)component).addThemeVariants(ComboBoxVariant.LUMO_HELPER_ABOVE_FIELD);
} else if (MultiSelectComboBox.class.isAssignableFrom(component.getClass())) {
((MultiSelectComboBox<?>)component).addThemeVariants(MultiSelectComboBoxVariant.LUMO_HELPER_ABOVE_FIELD);
} else if (TextArea.class.isAssignableFrom(component.getClass())) {
((TextArea)component).addThemeVariants(TextAreaVariant.LUMO_HELPER_ABOVE_FIELD);
} else if (TextField.class.isAssignableFrom(component.getClass())) {
((TextField)component).addThemeVariants(TextFieldVariant.LUMO_HELPER_ABOVE_FIELD);
} else {
component.setThemeName("helper-above-field", true);
}
}

@Override
public Consumer<String> createComponentValueSetter(ComponentWrapper componentWrapper) {
final var helper = extractHelper(componentWrapper);

return getStringConsumer(helper);
}

@NonNull
private HasHelper extractHelper(ComponentWrapper componentWrapper) {
if (!(componentWrapper.getComponent() instanceof HasHelper)) {
throw new IllegalArgumentException("Component " + componentWrapper.getComponent().getClass().getSimpleName() + //
" does not implement HasHelper");
}

return (HasHelper)componentWrapper.getComponent();
}

@NonNull
private Consumer<String> getStringConsumer(HasHelper hasHelper) {
return text -> {
if (annotation.placeAboveElement() && hasHelper instanceof HasTheme) {
setThemeVariant((HasTheme)hasHelper);
}

if (annotation.htmlContent()) {
final var component = Optional.of(hasHelper)
.map(HasHelper::getHelperComponent)
.filter(LinkkiText.class::isInstance)
.map(LinkkiText.class::cast)
.orElseGet(() -> createHelperComponent(hasHelper));

setIcon(component);
setInnerHtml(text, component);
} else {
hasHelper.setHelperText(text);
}
};
}

private void setIcon(LinkkiText component) {
if (annotation.showIcon()) {
component.setIcon(annotation.icon());
component.setIconPosition(annotation.iconPosition());
} else {
component.setIcon(null);
}
}

private void setInnerHtml(String text, LinkkiText element) {
element.setText(text, true);
}

private LinkkiText createHelperComponent(HasHelper hasHelper) {
final var component = new LinkkiText();

hasHelper.setHelperComponent(component);

return component;
}

@Override
public Aspect<String> createAspect() {
return switch (annotation.helperTextType()) {
case AUTO -> annotation.value() == null || StringUtils.isEmpty(annotation.value()) ?
Aspect.of(NAME) :
Aspect.of(NAME, annotation.value());
case STATIC -> Aspect.of(NAME, annotation.value());
case DYNAMIC -> Aspect.of(NAME);
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package org.linkki.core.ui.aspects;



import static org.linkki.core.ui.aspects.annotation.BindToggletip.ToogletipPosition.PREFIX;
import static org.linkki.core.ui.aspects.annotation.BindToggletip.ToogletipPosition.SUFFIX;

import java.util.Collections;
import java.util.function.Consumer;

import org.apache.commons.lang3.StringUtils;
import org.linkki.core.binding.descriptor.aspect.Aspect;
import org.linkki.core.binding.descriptor.aspect.base.ModelToUiAspectDefinition;
import org.linkki.core.binding.wrapper.ComponentWrapper;
import org.linkki.core.ui.aspects.annotation.BindToggletip;
import org.linkki.core.ui.wrapper.VaadinComponentWrapper;
import org.linkki.core.vaadin.component.ComponentFactory;

import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.shared.HasPrefix;
import com.vaadin.flow.component.shared.HasSuffix;
import com.vaadin.flow.component.shared.HasTooltip;
import com.vaadin.flow.component.shared.Tooltip;

import edu.umd.cs.findbugs.annotations.NonNull;

public class ToggletipAspectDefinition extends ModelToUiAspectDefinition<String> {

private static final String NAME = "toggletip";
private final BindToggletip annotation;
private Tooltip tooltip;
private Button button;

public ToggletipAspectDefinition(BindToggletip annotation) {
this.annotation = annotation;
}

public static Tooltip extractTooltip(ComponentWrapper componentWrapper) {
if (!(componentWrapper.getComponent() instanceof HasTooltip component)) {
throw new IllegalArgumentException("Component " + componentWrapper.getComponent().getClass().getSimpleName() + //
" does not implement HasTooltip");
}

return component.getTooltip();
}

@Override
public Consumer<String> createComponentValueSetter(ComponentWrapper componentWrapper) {
return getStringConsumer(componentWrapper);
}

private void initializeButton(ComponentWrapper componentWrapper) {
if (button == null) {
button = createButton(componentWrapper);
tooltip = extractTooltip(componentWrapper);

if (tooltip != null) {
tooltip//
.withPosition(annotation.tooltipPosition())
.withManual(true);

button.addClickListener(event -> tooltip.setOpened(!tooltip.isOpened()));
}
}
}

private Button createButton(ComponentWrapper componentWrapper) {
if (componentWrapper instanceof VaadinComponentWrapper) {
final var component = ((VaadinComponentWrapper)componentWrapper).getComponent();
if ((component instanceof HasPrefix && annotation.toggletipPosition() == PREFIX) || (component instanceof HasSuffix
&& annotation.toggletipPosition() == SUFFIX)) {
final var newButton = ComponentFactory.newButton(annotation.icon()
.create(), Collections.emptyList());
if (annotation.toggletipPosition() == PREFIX) {
((HasPrefix)component).setPrefixComponent(newButton);
} else if (annotation.toggletipPosition() == SUFFIX) {
((HasSuffix)component).setSuffixComponent(newButton);
}

return newButton;
} else {
//
throw new IllegalArgumentException("Component %s does not implement %s or %s".formatted(component.getClass()
.getSimpleName(), HasPrefix.class.getSimpleName(), HasSuffix.class.getSimpleName()));
}

} else {
throw new IllegalArgumentException("ComponentWrapper " + componentWrapper.getClass().getSimpleName() + //
" is not a VaadinComponentWrapper");
}
}

@NonNull
private Consumer<String> getStringConsumer(ComponentWrapper componentWrapper) {
return text -> {
initializeButton(componentWrapper);
tooltip.setText(text);
};
}

@Override
public Aspect<String> createAspect() {
return switch (annotation.toggletipType()) {
case AUTO -> annotation.value() == null || StringUtils.isEmpty(annotation.value()) ?
Aspect.of(NAME) :
Aspect.of(NAME, annotation.value());
case STATIC -> Aspect.of(NAME, annotation.value());
case DYNAMIC -> Aspect.of(NAME);
};
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package org.linkki.core.ui.aspects;

import static org.linkki.core.ui.aspects.ToggletipAspectDefinition.extractTooltip;
import static org.linkki.core.ui.aspects.annotation.BindTooltip.DEFAULT_DELAY;

import java.util.function.Consumer;
import java.util.function.IntConsumer;

import org.apache.commons.lang3.StringUtils;
import org.linkki.core.binding.descriptor.aspect.Aspect;
import org.linkki.core.binding.descriptor.aspect.base.ModelToUiAspectDefinition;
import org.linkki.core.binding.wrapper.ComponentWrapper;
import org.linkki.core.ui.aspects.annotation.BindTooltip;
import org.linkki.util.Consumers;

import com.vaadin.flow.component.shared.Tooltip;

public class TooltipAspectDefinition extends ModelToUiAspectDefinition<String> {

public static final String NAME = "tooltip";

private final BindTooltip annotation;

public TooltipAspectDefinition(BindTooltip annotation) {
this.annotation = annotation;
}

@SuppressWarnings("checkstyle:WhitespaceAround")
@Override
public Consumer<String> createComponentValueSetter(ComponentWrapper componentWrapper) {
final var tooltip = extractTooltip(componentWrapper);
if (tooltip == null) {
return Consumers.nopConsumer();
}

return getStringConsumer(tooltip);
}

@edu.umd.cs.findbugs.annotations.NonNull
private Consumer<String> getStringConsumer(Tooltip tooltip) {
return label -> {
tooltip//
.withText(label)
.withPosition(annotation.position());

setIfPresent(tooltip::setFocusDelay, annotation.focusDelay());
setIfPresent(tooltip::setHideDelay, annotation.hideDelay());
setIfPresent(tooltip::setHoverDelay, annotation.hoverDelay());
};
}

private void setIfPresent(IntConsumer consumer, int value) {
if (value != DEFAULT_DELAY) {
consumer.accept(value);
}
}

@Override
public Aspect<String> createAspect() {
return switch (annotation.tooltipType()) {
case AUTO -> annotation.value() == null || StringUtils.isEmpty(annotation.value()) ?
Aspect.of(NAME) :
Aspect.of(NAME, annotation.value());
case STATIC -> Aspect.of(NAME, annotation.value());
case DYNAMIC -> Aspect.of(NAME);
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*******************************************************
* Copyright (c) Faktor Zehn GmbH - www.faktorzehn.de
*
* All Rights Reserved - Alle Rechte vorbehalten.
*******************************************************/

package org.linkki.core.ui.aspects.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.apache.commons.lang3.StringUtils;
import org.linkki.core.binding.descriptor.aspect.LinkkiAspectDefinition;
import org.linkki.core.binding.descriptor.aspect.annotation.AspectDefinitionCreator;
import org.linkki.core.binding.descriptor.aspect.annotation.LinkkiAspect;
import org.linkki.core.ui.aspects.HelperTextAspectDefinition;
import org.linkki.core.ui.aspects.types.HelperTextType;
import org.linkki.core.ui.aspects.types.IconPosition;
import org.linkki.core.util.HtmlSanitizer;

import com.vaadin.flow.component.HasHelper;
import com.vaadin.flow.component.icon.VaadinIcon;

import edu.umd.cs.findbugs.annotations.NonNull;

/**
* Setzt einen Helper Text an der Component. Diese muss {@link HasHelper} implementieren.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(value = { ElementType.FIELD, ElementType.METHOD })
@LinkkiAspect(BindHelperText.BindHelperTextAspectDefinition.class)
public @interface BindHelperText {

String value() default StringUtils.EMPTY;

/** Defines how the tooltip text should be retrieved */
HelperTextType helperTextType() default HelperTextType.AUTO;

/**
* When set to {@code true}, the label's content will be displayed as HTML, otherwise as plain text.
* The HTML content is automatically {@link HtmlSanitizer#sanitizeText(String) sanitized}. <br>
* Note that <b>user-supplied strings have to be {@link HtmlSanitizer#escapeText(String)
* escaped}</b> when including them in the HTML content. Otherwise, they will also be interpreted as
* HTML.
* <p>
* HTML content is not compatible with some annotations that manipulate the resulting component,
* like {@link BindIcon}.
*/
boolean htmlContent() default false;

boolean placeAboveElement() default false;

boolean showIcon() default false;
VaadinIcon icon() default VaadinIcon.INFO_CIRCLE_O;
IconPosition iconPosition() default IconPosition.LEFT;

class BindHelperTextAspectDefinition implements AspectDefinitionCreator<BindHelperText> {

@Override
public LinkkiAspectDefinition create(@NonNull BindHelperText annotation) {
return new HelperTextAspectDefinition(annotation);
}

}
}
Loading