Skip to content

Commit

Permalink
Separate core and thymeleaf functionality into modules
Browse files Browse the repository at this point in the history
  • Loading branch information
xhaggi committed Aug 24, 2023
1 parent 201b947 commit c97c2eb
Show file tree
Hide file tree
Showing 51 changed files with 346 additions and 240 deletions.
152 changes: 82 additions & 70 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,33 @@

# Spring Boot and Thymeleaf library for htmx

This library provides helper classes and a [Thymeleaf](https://www.thymeleaf.org/) dialect
This project provides annotations, helper classes and a [Thymeleaf](https://www.thymeleaf.org/) dialect
to make it easy to work with [htmx](https://htmx.org/)
in a [Spring Boot](https://spring.io/projects/spring-boot) application.

More information about htmx can be viewed on [their website](https://htmx.org/).

## Installation
## Maven configuration

The library is available
The project provides the following libraries, which are available
on [Maven Central](https://mvnrepository.com/artifact/io.github.wimdeblauwe/htmx-spring-boot-thymeleaf),
so it is easy to add the dependency to your project.
so it is easy to add the desired dependency to your project.

### htmx-spring-boot

Provides annotations and helper classes.

```xml
<dependency>
<groupId>io.github.wimdeblauwe</groupId>
<artifactId>htmx-spring-boot</artifactId>
<version>LATEST_VERSION_HERE</version>
</dependency>
```

### htmx-spring-boot-thymeleaf

Provides a [Thymeleaf](https://www.thymeleaf.org/) dialect to easily work with htmx attributes.
```xml
<dependency>
<groupId>io.github.wimdeblauwe</groupId>
Expand All @@ -28,7 +43,7 @@ so it is easy to add the dependency to your project.

### Configuration

The included Spring Boot autoconfiguration will enable the htmx integrations.
The included Spring Boot Auto-configuration will enable the htmx integrations.

### Request Headers

Expand Down Expand Up @@ -88,13 +103,67 @@ public String hxUpdateUser(){
}
```

### Processors
### OOB Swap support

htmx supports updating multiple targets by returning multiple partials in a single response with
[`hx-swap-oob`](https://htmx.org/docs/#oob_swaps). Return partials using this library use the `HtmxResponse` as a return
type:

```java
@GetMapping("/partials/main-and-partial")
public HtmxResponse getMainAndPartial(Model model){
model.addAttribute("userCount", 5);
return new HtmxResponse()
.addTemplate("users :: list")
.addTemplate("users :: count");
}
```

An `HtmxResponse` can be formed from view names, as above, or fully resolved `View` instances, if the controller knows how
to do that, or from `ModelAndView` instances (resolved or unresolved). For example:

```java
@GetMapping("/partials/main-and-partial")
public HtmxResponse getMainAndPartial(Model model){
return new HtmxResponse()
.addTemplate(new ModelAndView("users :: list")
.addTemplate(new ModelAndView("users :: count", Map.of("userCount",5));
}
```

Using `ModelAndView` means that each fragment can have its own model (which is merged with the controller model before rendering).

### Spring Security

The library has an `HxRefreshHeaderAuthenticationEntryPoint` that you can use to have htmx force a full page browser
refresh in case there is an authentication failure.
If you don't use this, then your login page might be appearing in place of a swap you want to do somewhere.
See [htmx-authentication-error-handling](https://www.wimdeblauwe.com/blog/2022/10/04/htmx-authentication-error-handling/)
blog post for detailed information.
To use it, add it to your security configuration like this:
```java
@Bean
public SecurityFilterChain filterChain(HttpSecurity http)throws Exception{
// probably some other configurations here
var entryPoint = new HxRefreshHeaderAuthenticationEntryPoint();
var requestMatcher = new RequestHeaderRequestMatcher("HX-Request");
http.exceptionHandling(exception ->
exception.defaultAuthenticationEntryPointFor(entryPoint, requestMatcher));
return http.build();
}
```
### Thymeleaf
_See [Attribute Reference](https://htmx.org/reference/#attributes) for the related htmx documentation._
Thymeleaf processors are provided to allow Thymeleaf to be able to perform calculations and expressions
The Thymeleaf dialect has appropriate processors that enable Thymeleaf to perform calculations and expressions
in htmx-related attributes.
Note the `:` colon instead of the typical hyphen.
>**Note** The `:` colon instead of the typical hyphen.
- `hx:get`: This is a Thymeleaf processing enabled attribute
- `hx-get`: This is just a static attribute if you don't need the Thymeleaf processing
Expand All @@ -108,18 +177,17 @@ For example, this Thymeleaf template:
Will be rendered as:

```html

<div hx-get="/users/123" hx-target="#otherElement">Load user details</div>
```

The included Thymeleaf dialect has corresponding processors for most of the `hx-*` attributes.
The Thymeleaf dialect has corresponding processors for most of the `hx-*` attributes.
Please [open an issue](https://github.com/wimdeblauwe/htmx-spring-boot-thymeleaf/issues) if something is missing.

> **Note**
> Be careful about using `#` in the value. If you do `hx:target="#mydiv"`, then this will not work as Thymeleaf uses
> the `#` symbol for translation keys. Either use `hx-target="#mydiv"` or `hx:target="${'#mydiv'}"`

### Map support for hx:vals
#### Map support for hx:vals

The [hx-vals](https://htmx.org/attributes/hx-vals/) attribute allows to add to the parameters that will be submitted
with the AJAX request. The value of the attribute should be a JSON string.
Expand All @@ -129,74 +197,17 @@ The library makes it a bit easier to write such a JSON string by adding support
For example, this Thymeleaf expression:

```html

<div hx:vals="${ {id: user.id } }"></div>
```

will render as:

```html

<div hx-vals="{&amp;quot;id&amp;quot;: 1234 }"></div>
```

(Given `user.id` has the value `1234`)

### OOB Swap support

htmx supports updating multiple targets by returning multiple partial response with
[`hx-swap-oob`](https://htmx.org/docs/#oob_swaps). Return partials using this library use the `HtmxResponse` as a return
type:

```java
@GetMapping("/partials/main-and-partial")
public HtmxResponse getMainAndPartial(Model model){
model.addAttribute("userCount",5);
return new HtmxResponse()
.addTemplate("users :: list")
.addTemplate("users :: count");
}
```

An `HtmxResponse` can be formed from view names, as above, or fully resolved `View` instances, if the controller knows how
to do that, or from `ModelAndView` instances (resolved or unresolved). For example:

```java
@GetMapping("/partials/main-and-partial")
public HtmxResponse getMainAndPartial(Model model){
return new HtmxResponse()
.addTemplate(new ModelAndView("users :: list")
.addTemplate(new ModelAndView("users :: count", Map.of("userCount",5));
}
```

Using `ModelAndView` means that each fragment can have its own model (which is merged with the controller model before rendering).

### Spring Security

The library has an `HxRefreshHeaderAuthenticationEntryPoint` that you can use to have htmx force a full page browser
refresh in case there is an authentication failure.
If you don't use this, then your login page might be appearing in place of a swap you want to do somewhere.
See [htmx-authentication-error-handling](https://www.wimdeblauwe.com/blog/2022/10/04/htmx-authentication-error-handling/)
blog post for detailed information.
To use it, add it to your security configuration like this:
```java
@Bean
public SecurityFilterChain filterChain(HttpSecurity http)throws Exception{
// probably some other configurations here
http...
var entryPoint=new HxRefreshHeaderAuthenticationEntryPoint();
var requestMatcher=new RequestHeaderRequestMatcher("HX-Request");
http.exceptionHandling(exception->
exception.defaultAuthenticationEntryPointFor(entryPoint,
requestMatcher));
return http.build();
}
```
## Articles

Links to articles and blog posts about this library:
Expand All @@ -209,7 +220,8 @@ Links to articles and blog posts about this library:

| htmx-spring-boot-thymeleaf | Spring Boot | Minimum Java version |
|---------------------------------------------------------------------------------------|-------------|----------------------|
| [2.2.0](https://github.com/wimdeblauwe/htmx-spring-boot-thymeleaf/releases/tag/2.2.0) | 3.x. | 17 |
| [3.0.0](https://github.com/wimdeblauwe/htmx-spring-boot-thymeleaf/releases/tag/2.1.0) | 3.1.x | 17 |
| [2.2.0](https://github.com/wimdeblauwe/htmx-spring-boot-thymeleaf/releases/tag/2.2.0) | 3.0.x | 17 |
| [1.0.0](https://github.com/wimdeblauwe/htmx-spring-boot-thymeleaf/releases/tag/1.0.0) | 2.7.x | 11 |

## Contributing
Expand All @@ -228,7 +240,7 @@ To release a new version of the project, follow these steps:

1. Update `pom.xml` with the new version and commit
2. Tag the commit with the version (e.g. `1.0.0`) and push the tag.
3. Create a new release in GitHub via https://github.com/wimdeblauwe/htmx-spring-boot-thymeleaf/releases/new
3. Create a new release in GitHub via https://github.com/wimdeblauwe/htmx-spring-boot/releases/new
- Select the newly pushed tag
- Update the release notes. This should automatically start
the [release action](https://github.com/wimdeblauwe/htmx-spring-boot-thymeleaf/actions).
Expand Down
59 changes: 59 additions & 0 deletions htmx-spring-boot-thymeleaf/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>io.github.wimdeblauwe</groupId>
<artifactId>htmx-spring-boot-parent</artifactId>
<version>3.0.0-SNAPSHOT</version>
</parent>

<groupId>io.github.wimdeblauwe</groupId>
<artifactId>htmx-spring-boot-thymeleaf</artifactId>
<name>Spring Boot library for htmx and Thymeleaf</name>
<description>Spring Boot library to make it easy to work with htmx and Thymeleaf</description>

<dependencies>
<dependency>
<groupId>io.github.wimdeblauwe</groupId>
<artifactId>htmx-spring-boot</artifactId>
<version>${parent.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.github.wimdeblauwe.hsbt.thymeleaf;
package io.github.wimdeblauwe.htmx.spring.boot.thymeleaf;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package io.github.wimdeblauwe.hsbt.thymeleaf;
package io.github.wimdeblauwe.htmx.spring.boot.thymeleaf;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.github.wimdeblauwe.hsbt.mvc.HtmxSpringStandardExressionObjectFactory;
import org.thymeleaf.dialect.AbstractProcessorDialect;
import org.thymeleaf.dialect.IExpressionObjectDialect;
import org.thymeleaf.expression.IExpressionObjectFactory;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package io.github.wimdeblauwe.hsbt.thymeleaf;
package io.github.wimdeblauwe.htmx.spring.boot.thymeleaf;

import io.github.wimdeblauwe.hsbt.mvc.HtmxHandlerMethodArgumentResolver;
import io.github.wimdeblauwe.htmx.spring.boot.mvc.HtmxHandlerMethodArgumentResolver;
import jakarta.servlet.http.HttpServletRequest;
import org.thymeleaf.context.IExpressionContext;
import org.thymeleaf.context.IWebContext;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.github.wimdeblauwe.hsbt.thymeleaf;
package io.github.wimdeblauwe.htmx.spring.boot.thymeleaf;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.AutoConfiguration;
Expand All @@ -7,7 +7,7 @@

@AutoConfiguration
@ConditionalOnWebApplication
public class HtmxThymeleafConfiguration {
public class HtmxThymeleafAutoConfiguration {
@Bean
public HtmxDialect htmxDialect(ObjectMapper mapper) {
return new HtmxDialect(mapper);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
io.github.wimdeblauwe.htmx.spring.boot.thymeleaf.HtmxThymeleafAutoConfiguration
Original file line number Diff line number Diff line change
@@ -1,24 +1,17 @@
package io.github.wimdeblauwe.hsbt;
package io.github.wimdeblauwe.htmx.spring.boot.thymeleaf;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.context.annotation.Bean;

import io.github.wimdeblauwe.hsbt.mvc.PartialsController.TodoRepository;

/**
* Just created this here to make Spring Boot test slices work
*/
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class}) // Security is on by default
@SpringBootApplication(exclude = SecurityAutoConfiguration.class) // Security is on by default
public class DummyApplication {

public static void main(String[] args) {
SpringApplication.run(DummyApplication.class);
}

@Bean
TodoRepository repository() {
return () -> 2;
}
}
Loading

0 comments on commit c97c2eb

Please sign in to comment.