Skip to content

Instantly share code, notes, and snippets.

@TatuLund
Created March 7, 2025 11:07
Show Gist options
  • Save TatuLund/e4a66e42a00cb0e4173d770ee5f870e8 to your computer and use it in GitHub Desktop.
Save TatuLund/e4a66e42a00cb0e4173d770ee5f870e8 to your computer and use it in GitHub Desktop.
Vaadin's RichTextEditor does not have label, errorText, invalid styles and helperText. CustomField is the simplest way to add those, here is example RteField
package com.example.application.views;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import com.vaadin.flow.component.Html;
import com.vaadin.flow.component.checkbox.Checkbox;
import com.vaadin.flow.component.customfield.CustomField;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.richtexteditor.RichTextEditor;
import com.vaadin.flow.data.binder.Binder;
import com.vaadin.flow.router.Route;
@Route(value = "rich-text", layout = MainLayout.class)
public class RichTextEditorView extends VerticalLayout {
public RichTextEditorView() {
setMargin(true);
setSpacing(true);
var rteField = new RteField();
rteField.setLabel("Html");
rteField.setHelperText("Input something");
var binder = new Binder<Data>();
binder.forField(rteField).asRequired("Required")
.withValidator(
value -> Jsoup.parse(value).select("strong").size() > 0,
"Bold required")
.withValidator(
value -> Jsoup.parse(value).select("h1").size() > 0,
"H1 required")
.bind(Data::getHtml, Data::setHtml);
var html = new Html("<div></div>");
binder.addValueChangeListener(e -> {
html.setHtmlContent(
String.format("<div>%s</div>", rteField.getValue()));
});
var readOnly = new Checkbox("ReadOnly");
readOnly.addValueChangeListener(
e -> rteField.setReadOnly(e.getValue()));
var disabled = new Checkbox("Disabled");
disabled.addValueChangeListener(
e -> rteField.setEnabled(!e.getValue()));
add(readOnly, disabled, rteField, html);
}
public class Data {
private String html;
public String getHtml() {
return html;
}
public void setHtml(String html) {
this.html = html;
}
}
public class RteField extends CustomField<String> {
private RichTextEditor rte;
public RteField() {
rte = new RichTextEditor();
add(rte);
}
@Override
public void setReadOnly(boolean readOnly) {
rte.setReadOnly(readOnly);
}
@Override
public void onEnabledStateChanged(boolean enabled) {
rte.setEnabled(enabled);
}
@Override
public void setInvalid(boolean invalid) {
// Use these in styles.css:
//
// vaadin-rich-text-editor[invalid] {
// border: 1px solid var(--lumo-error-color);
// background: var(--lumo-error-color-10pct);
// }
super.setInvalid(invalid);
if (invalid) {
rte.getElement().setAttribute("invalid", "");
} else {
rte.getElement().removeAttribute("invalid");
}
}
@Override
public boolean isEmpty() {
Document document = Jsoup.parse(getValue());
// Get non-normalized text including spaces and newlines
// Note that <br>s count as newlines
String text = document.body().wholeText();
// Remove first newline occurrence as Quill editor adds a single
// <br> in
// every element even without the user having typed anything
text = text.replaceFirst("\n", "");
boolean hasText = !text.isEmpty();
boolean hasImages = document.selectFirst("img") != null;
return !hasText && !hasImages;
}
@Override
public String getEmptyValue() {
// If current value is satisfying isEmpty condition, use it to
// satisfy Binder's asRequired.
return isEmpty() ? getValue() : "";
}
@Override
protected String generateModelValue() {
return rte.getValue();
}
@Override
protected void setPresentationValue(String newPresentationValue) {
rte.setValue(newPresentationValue);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment