Skip to content

Commit

Permalink
FormGeneration and FieldsetGeneration API (#613)
Browse files Browse the repository at this point in the history
  • Loading branch information
sdelamo authored Nov 8, 2023
1 parent fd7cee4 commit c984a04
Show file tree
Hide file tree
Showing 275 changed files with 15,557 additions and 8 deletions.
12 changes: 12 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,15 @@ project(":test-suite-graal") {
}
}
}

if (System.getenv("SONAR_TOKEN") != null) {
// exclude TCK from coverage
def coverageExcludes = [
"**/tck/**",
]
sonarqube {
properties {
property "sonar.exclusions", coverageExcludes.join(",")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
plugins {
id("io.micronaut.build.internal.views-base")
id "java-library"
id("io.micronaut.build.internal.views-tests")
}
dependencies {
testAnnotationProcessor(mnValidation.micronaut.validation.processor)
annotationProcessor(mnValidation.micronaut.validation)
testImplementation(mnData.micronaut.data.jdbc)
testImplementation(mn.micronaut.http.server)
testRuntimeOnly(mnLogging.logback.classic)
testAnnotationProcessor(mn.micronaut.inject.java)

testImplementation(projects.micronautViewsFieldset)
testImplementation(projects.micronautViewsFieldsetTck)
testImplementation(libs.junit.jupiter.api)
testImplementation(mnTest.micronaut.test.junit5)
testImplementation(libs.junit.jupiter.engine)
testImplementation(libs.junit.platform.engine)
}
test {
useJUnitPlatform()
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
plugins {
id "io.micronaut.build.internal.views-base"
id "io.micronaut.build.internal.module"
id("io.micronaut.build.internal.views-base")
id("io.micronaut.build.internal.module")
}
1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ githubSlug=micronaut-projects/micronaut-views
developers=Graeme Rocher

testsviewsThymeleaf=views-thymeleaf/src/test
testssuitefieldsetthymeleaf=test-suite-thymeleaf-fieldset/src/test
testsviewsSoy=views-soy/src/test
testsviewsHandlebars=views-handlebars/src/test
testsviewsVelocity=views-velocity/src/test
Expand Down
10 changes: 7 additions & 3 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ micronaut = "4.1.11"
micronaut-platform = "4.0.4"
micronaut-docs = '2.0.0'
micronaut-test = "4.0.1"

micronaut-data = "4.0.4"
micronaut-sql = "5.0.1"
micronaut-security = "4.1.0"
micronaut-serde = "2.3.0"
micronaut-validation = "4.0.3"
micronaut-gradle-plugin = "4.1.2"

testcontainers = "1.19.0"
groovy = "4.0.14"
spock = "2.3-groovy-4.0"

Expand All @@ -31,9 +32,11 @@ micronaut-logging = "1.1.2"
[libraries]
# Core
micronaut-core = { module = 'io.micronaut:micronaut-core-bom', version.ref = 'micronaut' }

testcontainers-bom = { module = "org.testcontainers:testcontainers-bom", version.ref = "testcontainers" }
micronaut-sql = { module = "io.micronaut.sql:micronaut-sql-bom", version.ref = "micronaut-sql" }
micronaut-logging = { module = "io.micronaut.logging:micronaut-logging-bom", version.ref = "micronaut-logging" }
micronaut-security = { module = "io.micronaut.security:micronaut-security-bom", version.ref = "micronaut-security" }
micronaut-data = { module = "io.micronaut.data:micronaut-data-bom", version.ref = "micronaut-data" }
micronaut-serde = { module = "io.micronaut.serde:micronaut-serde-bom", version.ref = "micronaut-serde" }
micronaut-validation = { module = "io.micronaut.validation:micronaut-validation-bom", version.ref = "micronaut-validation" }
micronaut-graal = {group = "io.micronaut", name = "micronaut-graal", version.ref = "micronaut" }
Expand All @@ -52,6 +55,7 @@ managed-velocity-engine-core = { module = "org.apache.velocity:velocity-engine-c

junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api" }
junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine" }
junit-platform-engine = { module = "org.junit.platform:junit-platform-suite-engine" }
pebble = { module = "io.pebbletemplates:pebble", version.ref = "pebble" }
thymeleaf-extras-java8time = { module = "org.thymeleaf.extras:thymeleaf-extras-java8time", version.ref = "thymeleaf-extra-java8time" }
kotlin-stdlib-jdk8 = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" }
Expand Down
6 changes: 6 additions & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ rootProject.name = 'views-parent'
include 'views-bom'
include 'views-core'
include 'views-soy'
include 'views-fieldset'
include 'views-fieldset-tck'
include 'views-freemarker'
include 'views-handlebars'
include 'views-thymeleaf'
Expand All @@ -29,6 +31,8 @@ include "test-suite"
include "test-suite-http"
include "test-suite-groovy"
include "test-suite-kotlin"
include "test-suite-thymeleaf-fieldset"
include "test-suite-freemarker-fieldset"
include "test-suite-graal:test-suite-graal-common"
include "test-suite-graal:test-suite-graal-freemarker"
include "test-suite-graal:test-suite-graal-handlebars"
Expand All @@ -42,6 +46,8 @@ include "test-suite-graal:test-suite-graal-soy"
micronautBuild {
useStandardizedProjectNames=true
importMicronautCatalog()
importMicronautCatalog("micronaut-data")
importMicronautCatalog("micronaut-sql")
importMicronautCatalog("micronaut-security")
importMicronautCatalog("micronaut-serde")
importMicronautCatalog("micronaut-validation")
Expand Down
5 changes: 5 additions & 0 deletions src/main/docs/guide/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ views:
model:
title: Working with Models
custom: Dynamically Enriching Models
fieldset:
title: Fieldset Generation
fieldsetExample: Form Generation Example
fieldsetAnnotations: Fieldset Annotations
fieldsetFetcher: Radio, Checkbox and Option Fetcher
turbo:
title: Turbo
turboFrameView: TurboFrameView annotation
Expand Down
9 changes: 9 additions & 0 deletions src/main/docs/guide/views/fieldset.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
WARNING: Fieldset API is experimental and subject to change.

The api:views.fields.FieldsetGenerator[] API simplifies the generation of an HTML Fieldset representation for a given type or instance. It leverages the https://docs.micronaut.io/latest/guide/#introspectionBuilders[introspection builder support].

The api:views.fields.FormGenerator[] API wraps the previous API and simplifies the generation of an HTML form.

To use these APIs, you need the dependency the following dependency:

dependency:micronaut-views-fieldset[groupId="io.micronaut.views"]
65 changes: 65 additions & 0 deletions src/main/docs/guide/views/fieldset/fieldsetAnnotations.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
Sometimes, more than the Java type is needed to define the input type. For example, you may want to render a login form such as:

[source, html]
----
<form action="/login" method="post">
<div class="mb-3">
<label for="username" class="form-label">Username</label>
<input type="text" name="username" value="" id="username" class="form-control" required="required"/>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" name="password" value="" id="password" class="form-control" required="required"/>
</div>
<input type="submit" value="Submit" class="btn btn-primary"/>
</form>
----

In Java, you will create a representation for the form submission and annotate the `password` field with ann:views.fields.annotations.InputPassword[]. Something like:

[source,java]
----
include::{testssuitefieldsetthymeleaf}/java/io/micronaut/views/fields/thymeleaf/Login.java[]
----

The following annotations are available:

|===
|Annotation | Description

|ann:views.fields.annotations.InputCheckbox[]
| Annotation to specify a field is a https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox[checkbox input].

|ann:views.fields.annotations.InputEmail[]
| Annotation to specify a field is an https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/email[email input].

|ann:views.fields.annotations.InputHidden[]
| Annotation to specify a field is a https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/hidden[hidden input].

|ann:views.fields.annotations.InputNumber[]
| Annotation to specify a field is a https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/number[number input].

|ann:views.fields.annotations.InputPassword[]
| Annotation to specify a field is a https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/password[password input].

|ann:views.fields.annotations.InputRadio[]
| Annotation to specify a field is a https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/radio[radio input].

|ann:views.fields.annotations.InputTel[]
| Annotation to specify a field is a https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/tel[telephone input].

|ann:views.fields.annotations.InputText[]
| Annotation to specify a field is a https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/text[text input].

|ann:views.fields.annotations.InputUrl[]
| Annotation to specify a field is an https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/url[url input].

|ann:views.fields.annotations.Select[]
| Annotation to specify a field is an https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select[HTML select element].

|ann:views.fields.annotations.Textarea[]
| Annotation to specify a field is a https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea[textarea].

|ann:views.fields.annotations.TrixEditor[]
| Annotation to mark a field as a https://trix-editor.org[Trix editor].
|===
42 changes: 42 additions & 0 deletions src/main/docs/guide/views/fieldset/fieldsetExample.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
Imagine, you want to create an application which displays a form such as:

[source, html]
----
<form action="/books/save" method="post">
<div class="mb-3">
<label for="title" class="form-label">Title</label>
<input type="text" name="title" value="" id="title" minlength="2" maxlength="255" class="form-control" required="required"/>
</div>
<div class="mb-3">
<label for="pages" class="form-label">Pages</label>
<input type="number" name="pages" value="" id="pages" min="1" max="21450" class="form-control" required="required"/>
</div>
<input type="submit" value="Submit" class="btn btn-primary"/>
</form>
----

In Java, you will create a representation for the form submission. Something like:

[source,java]
----
include::{testssuitefieldsetthymeleaf}/java/io/micronaut/views/fields/thymeleaf/BookSave.java[]
----

NOTE: The field types and the validation annotations in the previous code sample influence the form generation.

Then using the form generation API, create a controller such as:

[source,java]
----
include::{testssuitefieldsetthymeleaf}/java/io/micronaut/views/fields/thymeleaf/BookController.java[tag=clazz]
----

https://micronaut.io/launch?features=views-thymeleaf[Micronaut Launch] or the Micronaut Command Line Interface (CLI) will generate Thymeleaf fragments to render a form when you select the `views-thymeleaf` feature.

Thanks to those fragments, rendering the form for the `/books/create` route in the previous example is really simple:

[source,html]
.src/main/resources/views/books/create.html
----
include::{testssuitefieldsetthymeleaf}/resources/views/books/create.html[]
----
33 changes: 33 additions & 0 deletions src/main/docs/guide/views/fieldset/fieldsetFetcher.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
The ann:views.fields.annotations.InputCheckbox[], ann:views.fields.annotations.InputRadio[], and ann:views.fields.annotations.Select[] annotations allow you to specify a fetcher class to load data necessary for the form.

Imagine you want a form to associate an author with a book form, such as:

[source, html]
----
<form action="/books/authors/save" method="post">
<input type="hidden" name="bookId" value="1"/>
<div class="mb-3">
<label for="authorId" class="form-label">Author Id</label>
<select name="authorId" id="authorId" class="form-select" required="required">
<option value="1">Kishori Sharan</option>
<option value="2">Peter Späth</option>
<option value="3">Sam Newman</option>
</select>
</div>
<input type="submit" value="Submit" class="btn btn-primary"/>
</form>"
----

In Java, you will create a representation for the form submission. Something like:

[source,java]
----
include::{testssuitefieldsetthymeleaf}/java/io/micronaut/views/fields/thymeleaf/BookAuthorSave.java[]
----

The `fetcher` member of the `Select` annotation allows you to specify a class. `AuthorFetcher` is a `Singleton` of type `OptionFetcher`, and you could write it, for example, like this:

[source,java]
----
include::{testssuitefieldsetthymeleaf}/java/io/micronaut/views/fields/thymeleaf/AuthorFetcher.java[]
----
7 changes: 7 additions & 0 deletions test-suite-freemarker-fieldset/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
plugins {
id("io.micronaut.build.internal.views-fieldset-tck")
}

dependencies {
testImplementation(projects.micronautViewsFreemarker)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.micronaut.views.fields.thymeleaf;

import org.junit.platform.suite.api.IncludeClassNamePatterns;
import org.junit.platform.suite.api.SelectPackages;
import org.junit.platform.suite.api.Suite;
import org.junit.platform.suite.api.SuiteDisplayName;

@Suite
@IncludeClassNamePatterns("io.micronaut.views.fields.tck.InputHiddenViewRenderTest")
@SelectPackages({
"io.micronaut.views.fields.tck",
})
@SuiteDisplayName("Fieldset TCK for Freemarker")
class FreemarkerSuite {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
micronaut.views.freemarker.default-extension=html
micronaut.views.freemarker.incompatible-improvements=2.3.32
13 changes: 13 additions & 0 deletions test-suite-freemarker-fieldset/src/test/resources/logback.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="STDOUT" />
</root>
<logger name="com.projectcheckins.logging" level="TRACE"/>
<logger name="io.micronaut.http.client" level="TRACE"/>
<logger name="io.micronaut.data.query" level="TRACE"/>
</configuration>
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<#import "fieldsetmacro.html" as m>
<@m.fieldset el=el/>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<#macro fieldset el>
<#list el.fields()>
<#items as field>
<#if field.tag.toString() == "input" && field.type.toString() == "hidden">
<#import "inputhiddenmacro.html" as m>
<@m.inputhidden el=field/>
</#if>
</#items>
</#list>
</#macro>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<form action="${form.action()}" method="${form.method()}">
<#import "fieldsetmacro.html" as m>
<@m.fieldset el=form.fieldset()/>
</form>
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<#import "inputhiddenmacro.html" as m>
<@m.inputhidden el=el/>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<#macro inputhidden el>
<input type="hidden" name="${el.name()}" value="${el.value()!}"/>
</#macro>
1 change: 1 addition & 0 deletions test-suite-http/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ dependencies {
testRuntimeOnly(mnLogging.logback.classic)

testImplementation(projects.micronautViewsCore)
testImplementation(projects.micronautViewsFieldset)
testImplementation(projects.micronautViewsFreemarker)
// testImplementation(projects.micronautViewsHandlebars)
testImplementation(projects.micronautViewsJte)
Expand Down
16 changes: 16 additions & 0 deletions test-suite-thymeleaf-fieldset/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
plugins {
id("io.micronaut.build.internal.views-fieldset-tck")
}

dependencies {
testImplementation(projects.micronautViewsThymeleaf)
testAnnotationProcessor(mnData.micronaut.data.processor)
testImplementation(mnData.micronaut.data.jdbc)
testImplementation(mn.micronaut.http.client)
testImplementation(mn.micronaut.http.server.netty)
testAnnotationProcessor(mnSerde.micronaut.serde.processor)
testImplementation(mnSerde.micronaut.serde.jackson)
testImplementation(mnSql.micronaut.jdbc.hikari)
testImplementation(platform(mnSql.micronaut.sql.bom))
testRuntimeOnly("com.h2database:h2")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.micronaut.views.fields.thymeleaf;

import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.data.annotation.GeneratedValue;
import io.micronaut.data.annotation.Id;
import io.micronaut.data.annotation.MappedEntity;

@MappedEntity
public record Author(@Nullable @Id @GeneratedValue(GeneratedValue.Type.AUTO) Long id,
@NonNull String title) {
}
Loading

0 comments on commit c984a04

Please sign in to comment.