Dynamic HTML using a Vaadin LitRenderer

2.2k views Asked by At

Using Vaadin 22+

I'm trying to display some dynamic html in a Vaadin Grid, using a LitRenderer.

This could previously be achieved using the now-deprecated TemplateRenderer using this hack

var templateRenderer = TemplateRenderer.<Event>of("""
<div>
  <ul inner-h-t-m-l="[[item.html]]">
  
  </ul>
</div>
""").withProperty("html", item -> {
  String listItems = "";
  for (EventItem eventItem : item.getEventItems()) {
    listItems += "<li>"+eventItem.getValue()+"</li>";
  }
  return listItems;
});

/*
  Produces html like
  <div>
    <ul>
      <li>Thing 1</li>
      <li>Thing 3</li>
      <li>Thing 845</li>
    </ul>
  </div>
  */

The important part here was setting the innerHTML of the wrapping element.

Is there a way to do this using the LitRenderer? I see that Lit-Template itself has the unsafeHTML directive for just this kind of purpose. But I've had no luck trying to get it or another method working.

Thanks

Updated: 2021.3.2 to fix the example code

1

There are 1 answers

5
Jean-Christophe Gueriaud On

You can use a lit renderer without unsafeHTML to render your example:

grid.addColumn(LitRenderer.<String>of(" <ul>" +
                "        ${item.eventItems.map((eventItem) =>" +
                "            html`<li>${eventItem}</li>`\n" +
                "        )}\n" +
                "    </ul>").withProperty("eventItems", i -> {
                    return List.of("item 1", "item 2", "item 3");
        }));

If you really want to display custom html, you can use a Component Renderer:

grid.addColumn(new ComponentRenderer<>(i -> {
            List<String> strings = List.of("item 1", "item 2", "item 3");
            String listItems = "";
            for (String eventItem : strings) {
                listItems += "<li>"+eventItem +"</li>";
            }
            return new Html("<ul>"+listItems+"</ul>");
        }));

And if you don't want to use a Component Renderer you can create a custom lit component with unsafeHTML:

import {css, html, LitElement} from 'lit';
import {unsafeHTML} from 'lit/directives/unsafe-html.js';
import { customElement } from 'lit/decorators.js';

@customElement('unsafe-html-component')
export class MyTestComponent extends LitElement {

    static get styles() {
        return css`
        :host {
            display: block;
        }
        `;
    }
    private html:string = "";

    render() {
        return html`<ul>${unsafeHTML(this.html)}</ul>`;
    }

}

And you can use it in Java:

grid.addColumn(LitRenderer.<String>of(" <unsafe-html-component .html=${item.html}></unsafe-html-component>")
                .withProperty("html", i -> {
                    List<String> strings = List.of("item 1", "item 2", "item 3");
                    String listItems = "";
                    for (String eventItem : strings) {
                        listItems += "<li>"+eventItem +"</li>";
                    }
                    return listItems;
        }));

EDIT: There is also a fourth solution:

grid.addColumn(LitRenderer.<String>of(" <ul .innerHTML=${item.html}></ul>")
                .withProperty("html", i -> {
                    List<String> strings = List.of("item 1", "item 2", "item 3");
                    String listItems = "";
                    for (String eventItem : strings) {
                        listItems += "<li>"+eventItem +"</li>";
                    }
                    return listItems;
        }));

Here is a full example with the 4 ways to display your example without the TemplateRenderer:

import com.vaadin.flow.component.Html;
import com.vaadin.flow.component.dependency.JsModule;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.data.renderer.ComponentRenderer;
import com.vaadin.flow.data.renderer.LitRenderer;
import com.vaadin.flow.router.Route;

import java.util.ArrayList;
import java.util.List;
@JsModule("./unsafe-html-component.ts")
@Route("template-grid")
public class GridView extends VerticalLayout {

    public GridView() {
        Grid<String> grid = new Grid<>();
        grid.addColumn(LitRenderer.<String>of(" <ul>" +
                "        ${item.eventItems.map((eventItem) =>" +
                "            html`<li>${eventItem}</li>`\n" +
                "        )}\n" +
                "    </ul>").withProperty("eventItems", i -> {
                    return List.of("item 1", "item 2", "item 3");
        }));
        grid.addColumn(LitRenderer.<String>of(" <unsafe-html-component .html=${item.html}></unsafe-html-component>")
                .withProperty("html", i -> {
                    List<String> strings = List.of("item 1", "item 2", "item 3");
                    String listItems = "";
                    for (String eventItem : strings) {
                        listItems += "<li>"+eventItem +"</li>";
                    }
                    return listItems;
        }));
        grid.addColumn(new ComponentRenderer<>(i -> {
            List<String> strings = List.of("item 1", "item 2", "item 3");
            String listItems = "";
            for (String eventItem : strings) {
                listItems += "<li>"+eventItem +"</li>";
            }
            return new Html("<ul>"+listItems+"</ul>");
        }));
        grid.addColumn(LitRenderer.<String>of(" <ul .innerHTML=${item.html}></ul>")
                .withProperty("html", i -> {
                    List<String> strings = List.of("item 1", "item 2", "item 3");
                    String listItems = "";
                    for (String eventItem : strings) {
                        listItems += "<li>"+eventItem +"</li>";
                    }
                    return listItems;
        }));
        add(grid);

        List<String> strings = new ArrayList<>();
        for (int i = 0; i < 30; i++) {
            strings.add(i + "");
        }

        grid.setItems(strings);
        grid.setSizeFull();
        setSizeFull();
    }
}

There is a ticket on github with more solutions: https://github.com/vaadin/flow-components/issues/2753