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.
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.
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()
orWebResourceUtils.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 theViewElementBuilderContext
-
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
usingWebResourceRegistry.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.
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:
-
the
ViewElementBuilder
which will determine the actual rendering of the web resource (eg.<link>
tag) -
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)
@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 |
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. |
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 |
---|---|---|
|
|
Represents the |
|
|
Represents the location for CSS includes.
Usually also inside the |
|
|
Represents the location for Javascript includes that should load at the beginning of the page.
Usually also inside the |
|
|
Represents the location for Javascript includes at the end of the page markup.
Usually these are added right before the closing |
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). |
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.
<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.
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>
Link tags
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.
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.
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'><hello></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.
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>
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:
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.
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.