Mutate AppLayout defined in typescript via Java Component.

I have been experimenting with experimental support for hyrbid react in vaadin 24, and even though it is experimental it seems to be working faily well.

As part of this experiment I am trying to convert the business app starter application, which currently uses Java-based layouts, into a hybrid application with a React front-end. The reason for this is to evaluate the effort required to transition one of my current flow applications running in production to a React hybrid application in the future, when it becomes generally available.

One requirement is to convert the layouts defined in the Flow app to use the App Flow layout. So far, this has worked reasonably well, but I am unsure how to attach the Application Bar to the navigation bar within the App Flow layout.

Here is my crude attempt to attach the Application Bar to the App Layout Navbar:

{/* Content of app-bar goes here */}

In the java components:

addAttachListener(event → {
String jsCode = String.format(“”"
const appBar = $0;
const appLayout = document.body.querySelector(‘vaadin-app-layout’);
const placeholderDiv = appLayout.querySelector(‘%s’);
// If placeholderDiv has any children, remove them
while (placeholderDiv.firstChild) {
placeholderDiv.removeChild(placeholderDiv.firstChild);
}
placeholderDiv.appendChild(appBar);
“”", APPBAR_PLACEHOLDER_ID);
UI.getCurrent().getPage().executeJs(jsCode, appHeaderInner.getElement());

I am wondering if there is better way to do that? thanks for any ideas.

I apologise for tagging you, @practical-rat

Assuming that the app’s layout is defined in TypeScript, which is straightforward to work with in pure React/Hilla, how should we handle mutating parts of the layout, such as the navbar, in Java views if the same layout component is shared in both java/hilla-react?

When working with pure Lit, it was possible via LitTemplate to implement something like this:

https://vaadin.com/docs/latest/create-ui/templates

Define the layouts of your views declaratively and implement the UI logic in Java.

Any insights on this would be appreciated.

@original-uguisu, WDYT is the best practice/recommendation here?

Shouldn’t it be possible to directly implement the navbar in TypeScript in the MainLayout? Is there any need for Java to add this?

I assume the question is how to keep some parts related to the main layout in Java even though the core of the layout is implemented in Hilla.

The “proper” way of doing that is still on our roadmap in the form of making it easy to add use Flow components from Hilla. You can do that manually already by exporting it as a web component and jumping through some hoops to use that web component from React. We did also find some timing issues that might cause problems when we internally prototyped this approach.

The described approach using executeJs to inject a DOM element managed by Flow into a specific location in the DOM structure creaed through Hilla is also generally a good approach here even though I agree that it looks a bit ugly. You don’t show how the appHeaderInner component is attached but I would suggest using the low-level Element::appendVirtualChild API to attach it to ui.getElement() since that signals to Flow that it shouldn’t even try to attach the DOM element anywhere on its own.

Sounds like a new era for CustomLayout to shine? :wink:

CustomLayout won’t (directly) help in this case since the initial DOM structure is owned by React. There isn’t even any Java instance that directly corresponds to that React component instance. Might still be some benefits from the same approach with injecting a Flow element (tree) into some arbitrary element that is managed by JavaScript. I would spontaneously reuse terminology from e.g. React for this and call it a “portal”.

I immediately fell in love with the portal concept. The original case from this post could then be implemented like this;

Portal portal = new Portal(UI.getCurrent(), "#app-bar-container");
portal.add(appHeaderInner);

UI.getCurrent() is the lifecycle owner and "#app-bar-container" is a CSS selector.

From a high level perspective, this sounds “easy” to implement based on the concept you named above with append virtual children

Thanks everyone for the time and inputs here.

“I assume the question is how to keep some parts related to the main layout in Java even though the core of the layout is implemented in Hilla.”

!00% Correct; the core of the layout is in React, but I want to mutate/control some parts of it through Java.

“You don’t show how the appHeaderInner component is attached”

I initially attach this to the DOM, and then later, I append it to the app flow’s navigation bar. I was expecting this not to work :sweat_smile: , but to my surprise, the browser removes it from the original parent and reattaches it to the element I want.

if (appHeaderInner == null) {
appHeaderInner = new Div();
appHeaderInner.addClassName(“app-header-inner”);
getElement().insertChild(0, appHeaderInner.getElement());
}


addAttachListener

“using the low-level Element::appendVirtualChild API to attach it to ui.getElement()”

This is an excellent suggestion; I will explore it. I assume there is Java Element API to do that?

@original-uguisu Portal concept seems quite appropriate for use cases like it.

Unrelated to this topic:
@quirky-zebra had sent me link to the ideas of using signals, it was excellent read, some new concepts unique to the full stack hillvaadin. Additionally, I noticed that you have already created some proof of concept work with the Preact library. I’m looking forward to seeing this gain traction, as state management in React can be complex, especially for full-stack developers who jump between backend and frontend.

https://cookbook.vaadin.com/custom-layout has a good example how append virtual children can be used

Thanks @quirky-zebra you are a walking encyclopedia of Vaadin/Hilla knowledge.

After giving it some thought, I think the Portal API should be more flexible on these two dimensions:

  • More flexible ways of defining exactly how someComponent should be attached relative to the reference location. I could imagine at least these different strategies
    • Append as a child
    • Replace any existing children
    • Insert as a sibling before or after. These could be particularly useful combine with the Lit trick of using comment nodes as the reference.
  • More flexible ways of finding the target element rather than only using CSS selectors. The ultimate format is probably a (owner: Element) => Promise<Node> callback (passed as an excuteJs string) and then we can provide shorthands for doing things like CSS selectors on top of that.

With those additions, the API could use a fluent approach.

Portal.forOwner(owningComponent).jsSelector("return owner.querySelector('#foo')").insertBefore(someComponent);

or with a shorthand for the selector and a different attach strategy

Portal.forOwner(owningComponent).idSelector("foo").replaceContent(someComponent);

Great minds think alike :slightly_smiling_face: Htmx does something similar

https://htmx.org/docs/#targets

Extended CSS Selectors

In htmx, hx-target and most attributes that take a CSS selector support an “extended” CSS syntax:

  • this: Indicates that the element with the hx-target attribute is the target itself.
  • closest <CSS selector>: Finds the closest ancestor element that matches the given CSS selector. Example: closest tr targets the closest table row.
  • next <CSS selector>: Finds the next sibling element in the DOM that matches the given CSS selector.
  • previous <CSS selector>: Finds the previous sibling element in the DOM that matches the given CSS selector.
  • find <CSS selector>: Finds the first child descendant element that matches the given CSS selector. Example: find tr targets the first child table row.

Additionally, a CSS selector can be wrapped in < and /> characters, which is similar to the query literal syntax of hyperscript.

Swapping

htmx allows different methods to swap HTML content returned into the DOM. The hx-swap attribute can be used with the following values:

  • innerHTML: (Default) Places the content inside the target element.
  • outerHTML: Replaces the entire target element with the returned content.
  • afterbegin: Inserts the content before the first child inside the target element.
  • beforebegin: Inserts the content before the target element within its parent.
  • beforeend: Appends the content after the last child inside the target element.
  • afterend: Appends the content after the target element within its parent.
  • delete: Removes the target element from the DOM, regardless of the response.
  • none: Does not append content from the response (though Out of Band Swaps and Response Headers will still be processed).

I am not sure if this helps or confuses even more as there are so many avenues how to do things. It is possible to make React render a view as web component. You can then use that web component in Flow.

`import TodoView from ‘./views/todo/TodoView’;
import { createRoot } from ‘react-dom/client’;
import { utility } from ‘@vaadin/vaadin-lumo-styles/utility.js’;
import { badge } from ‘@vaadin/vaadin-lumo-styles/badge.js’;

class AppExport extends HTMLElement {
mountPoint!: HTMLSpanElement;

connectedCallback() {
const mountPoint = document.createElement(‘span’);
this.attachShadow({ mode: ‘open’ }).appendChild(mountPoint);

const root = createRoot(mountPoint!);
root.render(<TodoView />);
if (utility.styleSheet && badge.styleSheet)
  this.shadowRoot!.adoptedStyleSheets = [utility.styleSheet, badge.styleSheet];

}
}
export default AppExport;

window.customElements.get(‘app-export’) || window.customElements.define(‘app-export’, AppExport);`

@yummy-rhino

AppLayout is the react version of vaadin-app-layout web component which I am trying to modify/control.

re-converting back to web component seems counter intuitive unless I am missing something.

I am just wondering, should you consider following other options 1. Have AppLayout fully in Java 2. Build custom app layout either with Java or React that fits better to your requirements.

I had a similar conversation in this thead about using java only layouts in hybrid react/flow application.
https://discord.com/channels/732335336448852018/1191992657782779954