1

vaadin 23.

We are storing html in a database which we need to render within a vaadin component.

The html includes a web component 'text-block'

The text-block can contain nested children:

<p>
<text-block page="PackageSearch" block="empty-search-Sign up for free" class="gimps"> 
    <div class="glimps-tips" id="glimps-tips">
       <h3>Sign up for free</h3> 
       Create a watch and get notifications when a new package version is released.<br>   
    </div>
  </text-block>
<p>

The problem is that when the <text-block> is rendered in the browser the the child nodes are rendered as siblings rather than child nodes:

<p>
<text-block page="PackageSearch" block="empty-search-Sign up for free" class="gimps"> 
</text-block>
<div class="glimps-tips" id="glimps-tips">
   <h3>Sign up for free</h3> 
   Create a watch and get notifications when a new package version is released.<br>   
</div>
</p>

I've tracked the problem down to the fact that the Vaadin Element class uses Jsoup which doesn't like web-components so as part of its 'validation' it re-orders the components.

I believe that vaadin is using `Element.getOuterHtml()` to do the final rendering of the html which calls:
  public String getOuterHTML() {
        return ElementUtil.toJsoup(new Document(""), this).outerHtml();
    }

I've tried creating a class RawHtml:
import com.vaadin.flow.component.html.Div;

public class RawHtml extends Div
{
    private static final long serialVersionUID = 1L;

    public RawHtml(String html)
    {
        getElement().setProperty("innerHTML", html);

    }
    
    public String html() {
        return getElement().getProperty("innerHTML");
    }
    
    @Override
    public String toString() {
        return html();
    }
}

I then use this class to add the html to a VerticalLayout

var body = new VerticalLayout();
body.add(new RawHtml(< html with textblock and nested div as above>);

The result is the same. The div is moved outside of the text-block.

My theory is that vaadin does a final rendering of the html using jsoup before sending it to the browser.

How do I get around this.

Edit: updated question in response to Leif suggestion.

Edit 2:

I've now traced the html back up through the vaadin stack to the call in UidlRequestHandler.commitJsonResponse.

Fragment passed to the json argument to commitJsonResponse has the div correctly nested within the text block:



{"node":267,"type":"put","key":"innerHTML","feat":1,
"value":"<div>
<span><p>
<a href=\"/Blog#link-in-page\">hi</a> 
<text-block page=\"PackageSearch\" block=\"empty-search-Sign up for free\" class=\"gimps\">\n   
<div>\n    
<div class=\"glimps-tips\" id=\"glimps-tips\">\n     
<h3>Sign up for free</h3> Create a watch and get notifications when a new package version is released.<br> 
Publish your own private packages.<br> Access your API documentation online.<br> Share packages with your team.<br> 
Be more productive<br>\n    
</div>\n   
</div>\n  
</text-block>

So it now looks like my problem isn't server side but rather client side.

Here is a screen capture of what the browser is generating:

enter image description here

I can also see that the browser has received the correct html:

enter image description here

So the re-ordering must be happening on the browser.

Does vaadin do any manipulation of the html when adding it to the DOM? This feels unlikely.

It seems to suggest that the browser is doing the re-ordering which would suggest that my web component implementation is wrong?

Edit 3: So my next thought was that my web-component was broken. To validate it I use the this test site: https://stackblitz.com/edit/nested-slots-subkxj?file=my-element.js,index.html,package.json

Entered my web component and proved that it works correctly (the div renders in the slot).

Here is my web component:


import { html, LitElement } from 'lit';

export class TextBlock extends LitElement {

 
  render() {
    return html`
    <div class='me'>
      <slot name="one"></slot>
    </div>

    `;
  }
}
customElements.define('text-block', TextBlock);

And here is how it was rendered:

enter image description here

Brett Sutton
  • 3,900
  • 2
  • 28
  • 53

1 Answers1

3

You're not stating which Vaadin component you're using for rendering the HTML content, so I will assume it's the built-in Html component. This component uses JSoup to parse the HTML to be able to set the root tag of the HTML as its own tag name instead wrapping it in another element and then set the rest of the HTML as its own content.

If you want to have an HTML snippet passed to the browser as-is, then you can do that by creating you own wrapper component that sets the raw HTML string as its innerHTML property through the Element API.

public class RawHtml extends Div {
  public void setHtml(String html) {
    getElement().setProperty("innerHTML", html);
  }
}
Leif Åstrand
  • 7,820
  • 13
  • 19
  • this didn't work. Seem my updated question. – Brett Sutton Dec 01 '22 at 00:03
  • It seems like your HTML is invalid in the sense that the browser itself will reorder it regardless of what you do. You can test this by creating an empty HTML document with only the snippet you provided and see that it's reordered even in that case. – Leif Åstrand Dec 02 '22 at 12:11