ViewElement infrastructure

AcrossWebModule provides a programmatic model for rendering UI components. A single UI component in this context is called a ViewElement.

ViewElement

ViewElement implementations usually correspond to one or more HTML nodes. AcrossWebModule comes with a default set ViewElement implementation for rendering Bootstrap markup elements. This is done through a collection of Thymeleaf processors for rendering ViewElement components.

You can render a ViewElement available on the model anywhere in a Thymeleaf template by using the across:view tag.

<across:view element="${myViewElement}" />
You usually do not want to cache and reuse ViewElement components. It is better to cache ViewElementBuilder.

Default properties

In its most simple form, a ViewElement has the following properties:

name

An optional internal name of the element. This name can be used to retrieve the element from a ContainerViewElement. Use ContainerViewElementUtils to query and modify containers.

See developing-applications.adoc#development-mode for more information to retrieve generated view names.

elementType

A required type identification for the element.

customTemplate

An optional template name. If a custom template is specified, it will be used to render the ViewElement instead of the default processor. By default only Thymeleaf templates are supported.

Custom template

Every ViewElement allows you to configure a customTemplate. Only Thymeleaf fragments are supported, if you specify a Thymeleaf template without a fragment, a render(component) fragment will be appended. The component variable will always contain the ViewElement instance that is being rendered.

You can use a different input variable by specifying the ${component} manually in your template specification.

Examples:
  • th/mymodule/mytemplate results in th/mymodule/mytemplate :: render(component)

  • th/mymodule/mytemplate :: myfragment results in th/mymodule/mytemplate :: myfragment(component)

  • th/mymodule/mytemplate :: myfragment(${someModelAttribute},${component}) results in th/mymodule/mytemplate :: myfragment(attributeValue,component)

You should only use model attributes that are sure to be available when the template is being rendered. It is usually best to pass the required values as attributes on the ViewElement itself.

You can use the TemplateViewElement if you only want to render a custom template and optionally pass it some attributes.

ViewElementBuilder

A ViewElementBuilder is a simple API for creating a ViewElement instance based on a configuration and a given ViewElementBuilderContext.

The ViewElementBuilderContext represents the runtime context when creating the element. It is a way to pass attributes required for building the elements, and it also gives access to default request related beans like the WebResourceRegistry or the WebAppLinkBuilder.

AcrossWebModule comes with a number of default ViewElementBuilder implementations for both simple elements and more complex components.

Simple ContainerViewElementBuilder example
ViewElementBuilderContext builderContext = new DefaultViewElementBuilderContext(); (1)
builderContext.setAttribute( "username", "John Doe" );

return new ContainerViewElementBuilder() (2)
          .add( TextViewElement.text( "Hello, " ) ) (3)
          .add( bc -> TextViewElement.text( bc.getAttribute( "username" ) ) ) (4)
          .build( builderContext ); (5)
1 We create a custom ViewElementBuilderContext and set the username attribute.
2 Create a new ContainerViewElementBuilder instance.
3 Add a TextViewElement with some static text.
4 Add a child ViewElementBuilder which in turn creates a TextViewElement using the builder context username attribute as text value.
5 Build the actual ContainerViewElement. This will recursively call build( builderContext ) on the child builder, resulting in a single ContainerViewElement with 2 TextViewElement children.

Global ViewElementBuilderContext

The ViewElementBuilder interface provides a default no-arg build() method that will attempt to retrieve a global ViewElementBuilderContext. It will do so by calling ViewElementBuilderContext.retrieveGlobalBuilderContext() which usually returns the one attached to the current request or current thread.

If no global builder context is available, a new DefaultViewElementBuilderContext will be used instead.

Using a global ViewElementBuilderContext is a good way to share contextual variables across all elements being built. See the ViewElementBuilderContextInterceptor for an interceptor that creates a global ViewElementBuilderContext.

For performance reasons it is usually better to manage the ViewElementBuilderContext scope yourself: either provide a ViewElementBuilderContext directly or ensure there is a global one available.

ViewElementBuilderContext in controllers

If there is a global ViewElementBuilderContext available, you can also ViewElementBuilderContext as a method argument in web controller methods.

The ViewElementBuilderContext provides a buildLink(String) method that will resolve a link using the WebAppLinkBuilder attribute that is available on the builder context. By default the request-bound WebAppLinkBuilder is already set.

Development mode rendering

If development mode is active, all ViewElement names will be rendered in the markup. Start and end of the element rendering will be marked by a HTML comment. If the ViewElement is a node (xml-type element) it will also have a data attribute data-ax-dev-view-element containing the name.

Example markup when rendered in development mode
<!--[ax:title]-->
<input name="entity.title" id="entity.title" data-ax-dev-view-element="title" type="text" class="form-control" value="" required="required" />
<!--[/ax:title]-->
It is not required for a ViewElement to have a name, nor is it required for that name to be unique.