Web Resources

This page explains the programmatic registration of web resources with Across Web. On this page you will learn:

  • how you can register web resources like javascript or css files programmatically

  • how to use these web resources in a Thymeleaf template

  • which default web resource types are available

  • how you can bundle web resources together in packages

  • how you can create a custom web resource type

See the chapter on static resources if you are looking for information on bundling static resource files, configuring url versioning or resource caching.

Registering web resources

Across Web allows for programmatically registering web resources like javascript or css files through the use of a WebResourceRegistry.

A WebResourceRegistry is created for every request, and added web resources are grouped in collections called buckets.

A view template would then render all resources of a specific bucket in their corresponding location.

Extensibility

Though most commonly used for javascript and css files with typical HTML page head and page-end locations, you can simply create your own buckets and add custom web resource types.

A typical example

The following is a simple example registering a CSS file in the WebResourceRegistry.

@Controller
public class MyController
{
	@ModelAttribute
	public void registerWebResources( WebResourceRegistry webResourceRegistry ) {
		webResourceRegistry.apply( (1)
			WebResourceRule.add( WebResource.css( "@static:/my-resources/css/my.css" ) ) (2)
			                .withKey( "my-css" ) (3)
			                .toBucket( WebResource.CSS ) (4)
		);
	}
}
1 Web resources are usually managed by applying one or more WebResourceRules to the registry.
2 This adds a web resource of type CssWebResourceBuilder which represents a CSS file and will be rendered as a <link> element. In this case the css file is embedded as a module static resource.
3 We give this web resource a unique key which identifies it in the registry. If we were to omit the key, the url of the css file would be used as default key (@static:/my-resources/css/my.css).
4 We add this web resource to a bucket named css (we use the constant as this is one of the default buckets).

Accessing the WebResourceRegistry

A new WebResourceRegistry is attached to every request as a request attribute. This is done by the WebResourceRegistryInterceptor which is automatically added by Across Web for all regular request mappings.

Accessing the WebResourceRegistry manually can be done in several ways:

  • fetching it directly from the request using WebResourceUtils.currentRegistry() or WebResourceUtils.getRegistry(request)

  • adding it as a handler method parameter on your @RequestMapping or @ModelAttribute methods

  • when working with view elements, the WebResourceRegistry is available as a property on the ViewElementBuilderContext

  • listening to the BuildRegistryEvent<WebResourceRegistry>, you can register default web resources whenever a registry is created

  • layout templates usually publish a BuildTemplateWebResourcesEvent for registering the template specific web resources

Adding web resources

There are two common ways to add a web resource to a bucket:

  • directly as a WebResourceReference using WebResourceRegistry.addResourceToBucket( WebResourceReference, String bucket )

  • by applying a WebResourceRule (WebResourceRule.add( ViewElementBuilder )) which provides a more fluent API

Additionally you can also use a web resource packages to bundle multiple web resource registrations together.

Examples adding web resources
public void registerWebResources( WebResourceRegistry webResourceRegistry ) {
    webResourceRegistry.addResourceToBucket( (1)
        WebResourceReference.builder().viewElementBuilder( WebResource.css( "@static:/my-resources/css/one.css" ) ).build(),
        WebResource.CSS
    );

    webResourceRegistry.apply( (2)
        WebResourceRule.add( WebResource.css( "@static:/my-resources/css/two.css" ) ).toBucket( WebResource.CSS ),
        WebResourceRule.add( WebResource.javascript( "//some.cdn.com/some-javascript.js" ) ).toBucket( WebResource.JAVASCRIPT ),
    );
}
1 register a single web resource directly by adding the WebResourceReference to a specific bucket
2 using the more fluent WebResourceRule

Adding a web resource requires at least 2 parameters:

  1. the ViewElementBuilder which will determine the actual rendering of the web resource (eg. <link> tag)

  2. the id of the bucket where the resource should be added

See the web resource types section for an overview of the default resource types available.

Web resource keys

When adding a web resource, you can optionally provide a key identifying that resource in the specific bucket. This is considered a best practice, as it allows you to identify and optionally update a previously registered web resource.

Unless explicitly configured otherwise, a web resource will not be added if another with that key already exists in the same bucket.

Identifying web resources is required for two common use cases:

  • components can easily define web resource dependencies but you can avoid having double entries to the same resources

  • you can perform global web resource updates (eg. bumping a version)

Using web resource key to update a javascript version
@EventListener( "#template.templateName == 'default-template'" ) (1)
public void templateWebResources( BuildTemplateWebResourcesEvent template ) {
    template.applyResourceRules(
        WebResourceRule.add( WebResource.javascript( "https://code.jquery.com/jquery-2.2.4.js" ) )
                        .withKey( "jquery" )
                        .toBucket( WebResource.JAVASCRIPT )
    );
}

@Controller
class MyController {
    @ModelAttribute (2)
    public void controllerSpecificWebResources( WebResourceRegistry webResourceRegistry ) {
        webResourceRegistry.apply(
            WebResourceRule.add( WebResource.javascript( "https://code.jquery.com/jquery-3.4.1.js" ) )
                            .withKey( "jquery" )
                            .toBucket( WebResource.JAVASCRIPT )
                            .replaceIfPresent( true ) (3)
        );
    }
}
1 the original template registers version 2.2.4 of JQuery
2 the specific controller replaces the JQuery version with 3.4.1
3 specifying replaceIfPresent(true) is required as otherwise the original web resource would be kept
Default keys

A ViewElementBuilder can provide a default web resource key by implementing the WebResourceKeyProvider interface.

Ordering web resources

By default web resources are rendered in the order in which they were registered. You can influence this order by setting:

  • an explicit order index

  • the key of another web resource which should be rendered before the one added

  • the key of another web resource which should be rendered after the one added

If the dependent resource is not present, the before or after has no effect.

When replacing a web resource, the original registration order is kept. If no explicit ordering attributes have been specified, a replaced web resource will be in the same position as the original.
Example specifying web resource dependency
public void registerMyPlugins( WebResourceRegistry webResourceRegistry ) {
    webResourceRegistry.apply(
        WebResourceRule.add( WebResource.javascript( "@static:/my-resources/js/my-jquery-plugin.js" ) )
                        .toBucket( WebResource.JAVASCRIPT )
                        .after( "jquery" ) (1)
    );
}
1 it does not matter if the jquery resource is already present or added later, my custom web resource will be rendered after the jquery one

Deleting web resources

You can delete a web resource by key:

  • from all buckets directly with WebResourceRegistry.removeResourceWithKey( key )

  • from a specific bucket with WebResourceRegistry.removeResourceWithKeyFromBucket( key, bucket )

  • from either all buckets or a specific bucket by using WebResourceRule.remove()

Default buckets

Across Web identifies some default buckets, which represent typical HTML web page locations where web resources are rendered:

Bucket name Constant Typical use

head

WebResource.HEAD

Represents the <head> section of a HTML page.

css

WebResource.CSS

Represents the location for CSS includes. Usually also inside the <head> section of a HTML page.

javascript

WebResource.JAVASCRIPT

Represents the location for Javascript includes that should load at the beginning of the page. Usually also inside the <head> section of a HTML page.

javascript-page-end

WebResource.JAVASCRIPT_PAGE_END

Represents the location for Javascript includes at the end of the page markup. Usually these are added right before the closing </body> tag of a page.

Buckets are simply identifiable collections of web resources. If and where they are rendered depends entirely on the output template. Across Web itself does not provide any default template, these are typically created inside the application or other modules (eg. AdminWebModule).

Creating a bucket

Creating a custom bucket does not require any special action. The first time a web resource is added to a bucket, the collection for that bucket is created.

Rendering web resources

A web resource is always rendered using a ViewElementBuilder and can be any type of ViewElement.

Calling WebResourceRegistry.getResourcesForBucket( bucket ) will give you a single ViewElement which contains all the web resources of the bucket, in the correct order.

Thymeleaf integration

The Across Web Thymeleaf dialect contains a helper tag across:web-resources for rendering all resources of a named bucket.

Example default buckets rendering in a Thymeleaf template
<html>
    <head>
        <title>My Site</title>
        ...
        <across:web-resources bucket="head" />
        <across:web-resources bucket="javascript" />
        <across:web-resources bucket="css" />
    </head>
    <body>
        ...
        <across:web-resources bucket="javascript-page-end" />
    </body>
</html>

Web resource types

The following lists the default web resources types which are provided by Across Web.

CSS resources

Can be used for registering either inline CSS (<style> tags) or linked CSS files (<link> tags). In case of a linked CSS file, the url of the file will also be the default web resource key.

See CssWebResourceBuilder javadoc for all details.

  • Java

  • Output

webResourceRegistry.apply(
    WebResourceRule.add( WebResource.css( "@webjars:/some-webjar" ) ).toBucket( WebResource.CSS ),
    WebResourceRule.add( WebResource.css().url( "@static:/my-module/css/print.css" ).media( "print" ) ).toBucket( WebResource.CSS ),
    WebResourceRule.add( WebResource.css().inline( "body {background-color: powderblue;}" ) ).toBucket( WebResource.CSS ),
);
<link rel='stylesheet' type='text/css' href='/webjars/some-webjar'/>
<link rel='stylesheet' media='print' type='text/css' href='/across/resources/my-module/css/print.css'/>
<style type='text/css'>body {background-color: powderblue;}</style>

Can be used for any type of <link> tag rendering. If an url is specified, that url will be the default web resource key.

See LinkWebResourceBuilder javadoc for all details.

  • Java

  • Output

webResourceRegistry.apply(
    WebResource.link( "/favicon.ico" ).rel( "icon" ).crossOrigin( "anonymous" ).media( "image/x-icon" )
);
<link rel="icon" href="/favicon.ico" crossorigin='anonymous' type="image/x-icon">

Javascript resources

Can be used to add (Javascript) <script> tags. Supports both urls and inline script data.

In case of an included javascript file, the url is used as default web resource key.

See JavascriptWebResourceBuilder javadoc for all details.

  • Java

  • Output

webResourceRegistry.apply(
 WebResource.javascript( "@webjars:/some-webjar" ).async().defer(),
 WebResource.javascript().inline( "alert('hello');" ),

 // something a bit more advanced
 WebResource.javascript()
           .inline( new TextViewElementBuilder().text( "<hello>" ) )
           .type( MediaType.APPLICATION_JSON )
           .crossOrigin( "anonymous" )
           .attribute( "data-something", 1 )
);
<script src='/webjars/some-webjar' async='async' defer='defer' type='text/javascript' />
<script type='text/javascript'>alert('hello')</script>
<script type='application/json' crossorigin='anonymous' data-something='1'>&lt;hello&gt;</script>

Global JSON data

Can be used to write a simple Java object as global JSON data in a HTML page.

See WebResource.globalJsonData() javadoc for all details.

  • Java

  • Output

Map<String, String> statics = new HashMap<String, String>()
{{
    put( "static", "/" );
    put( "admin", "/admin" );
}};

webResourceRegistry.apply(
    // single object using WebResource.globalJsonData()
    JavascriptWebResourceBuilder.globalJsonData( "MyApp.StaticPaths", statics ),

    // multiple objects wrapped in a single <script> tag
    // by using JavascriptWebResourceBuilder.globalJsonData() directly
    WebResource.javascript()
               .inline(
                       new ContainerViewElementBuilder()
                               .add( JavascriptWebResourceBuilder.globalJsonData( "MyApp.one", 1 ) )
                               .add( JavascriptWebResourceBuilder.globalJsonData( "MyApp.two", 2 ) )
               )
);
<script type='text/javascript'>
// exposes global variable MyApp.StaticPaths which contains fields 'static' and 'admin'
(function( _data ) { _data[ "StaticPaths" ] = {"static":"/","admin":"/admin"}; })( window["MyApp"] = window["MyApp"] || {} );
</script>

<script type='text/javascript'>
// exposes global variables MyApp.one and MyApp.two
(function( _data ) { _data[ "one" ] = 1; })( window["MyApp"] = window["MyApp"] || {} );
(function( _data ) { _data[ "two" ] = 2; })( window["MyApp"] = window["MyApp"] || {} );
</script>

Meta tags

Can be used to render <meta> tags. The value of name or http-equiv will serve as the default web resource key.

See MetaWebResourceBuilder javadoc for all details.

  • Java

  • Output

webResourceRegistry.apply(
    WebResource.meta().name( "keywords" ).content( "one, two" )
    WebResource.meta().refresh( "30; http://www.google.be" ),

);
<meta name="keywords" content="one, two"/>
<meta http-equiv="refresh" content="30; http://www.google.be" />

Creating a web resource type

Creating a custom web resource type does not require any special action. Any ViewElementBuilder implementation can be used as web resource, see the ViewElement documentation for more details on how to use these components.

An example:

  • Java

  • Output

webResourceRegistry.apply(
    WebResourceRule
        .add(
               builderContext -> new NodeViewElementBuilder( "base"  )
                                    .attribute( "href", "https://www.w3schools.com/images/" )
                                    .build( builderContext )
        )
        .withKey( "base-href" )
        .toBucket( "head" )
);
<base href="https://www.w3schools.com/images/"  />

Specifically for web resources, you can consider additionally implementing WebResourceKeyProvider on your ViewElementBuilder.

Web resource packages

For more comprehensive web resources management, you can use named packages. A WebResourcePackage represents a bundle of web resource rules that should all be applied at once. A package is identified by a unique name and registered in a WebResourcePackageManager, which in turn is attached to the WebResourceRegistry.

Just like individual web resources, an entire package can be added to the registry.

Register a custom WebResourcePackage
@Configuration
public class MyWebConfiguration
{
    @Autowired
    void registerMyResourcesPackage( WebResourcePackageManager packageManager ) {
        packageManager.register(
                "my-resources", (1)
                WebReourcePackage.of( (2)
                     WebResourceRule.addPackage( "base-resources" ), (3)
                     WebResourceRule.add( WebResource.css( "@static:/my/css/my.css" ) ).withKey( "my-css" ).toBucket( CSS )
                )
        );
    }
}
1 we register a package called "my-resources" in the package manager, so consuming code can do WebResourceRule.addPackage( "my-resources" )
2 our package itself is a collection of WebResourceRules that should be executed
3 in this particular case the first rule installs another package called base-resources

Extending existing packages

Sometimes you want to add one or more resources to a previously defined package.

You can combine multiple packages together with WebResourcePackage.combine().

You can also extend a registered WebResourcePackage with WebResourcePackageManager.extendPackage( packageName, rules ). This will combine the original package with the new set of rules, and register it as a new package under the original package name.

Configuring default web resources

Default web resources are usually attached to a layout template. You can provide global defaults (added to every created WebResourceRegistry) by subscribing to the BuildRegistryEvent<WebResourceRegistry>. This event is published by the WebResourceRegistryInterceptor when a new registry is initialized for a request.