You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@causeway.apache.org by da...@apache.org on 2024/05/26 09:51:53 UTC
(causeway) branch CAUSEWAY-2873 updated (4182e4ccfa -> b49ddafff1)
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a change to branch CAUSEWAY-2873
in repository https://gitbox.apache.org/repos/asf/causeway.git
from 4182e4ccfa CAUSEWAY-2873: 02-05, 03-04
new de1e120b19 CAUSEWAY-2873: adds 03-06
new fa90b7fa44 CAUSEWAY-2873: 03-06
new 0a8829e410 CAUSEWAY-2873: completes 03-08
new ebb1786b32 CAUSEWAY-2873: 03-09
new a881ffbad6 CAUSEWAY-2873: removes old exercise
new a0721e3eaa CAUSEWAY-2873: 03-13
new 4eac9d1ceb CAUSEWAY-2873: attempts to improve styling of asciidoc plugin
new b49ddafff1 CAUSEWAY-2873: 03-14
The 8 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.asciidoctorconfig | 6 +
antora/.asciidoctor-styles/asciidoc-classic.css | 527 ++++++++++
antora/.asciidoctor-styles/boot-flatly.css | 350 +++++++
.../images/03-06/home-page-default-columns.png | Bin 0 -> 21722 bytes
.../images/03-07/new-parameters-are-optional.png | Bin 0 -> 8602 bytes
...antuml-md5-5c0ff86629ce9ff23e5d52305493d5c6.svg | 1 +
...antuml-md5-7e47437805fde198d7791595949fcf17.png | Bin 0 -> 4464 bytes
.../petclinic/pages/020-the-petclinic-domain.adoc | 4 +-
.../petclinic/pages/030-petowner-entity.adoc | 1035 ++++++++++++--------
.../modules/petclinic/pages/100-todo.adoc | 11 +-
10 files changed, 1514 insertions(+), 420 deletions(-)
create mode 100644 antora/.asciidoctor-styles/asciidoc-classic.css
create mode 100644 antora/.asciidoctor-styles/boot-flatly.css
create mode 100644 antora/components/tutorials/modules/petclinic/images/03-06/home-page-default-columns.png
create mode 100644 antora/components/tutorials/modules/petclinic/images/03-07/new-parameters-are-optional.png
create mode 100644 antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-5c0ff86629ce9ff23e5d52305493d5c6.svg
create mode 100644 antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-7e47437805fde198d7791595949fcf17.png
(causeway) 06/08: CAUSEWAY-2873: 03-13
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch CAUSEWAY-2873
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit a0721e3eaa859ad5d126562fefad1c60a639b530
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Sun May 26 09:41:50 2024 +0100
CAUSEWAY-2873: 03-13
---
.../petclinic/pages/030-petowner-entity.adoc | 466 +++++++++++++--------
.../modules/petclinic/pages/100-todo.adoc | 6 +-
2 files changed, 296 insertions(+), 176 deletions(-)
diff --git a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
index f0b01273b5..33c41617cd 100644
--- a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
@@ -611,7 +611,9 @@ Note that Apache Causeway allows services to be injected into entities (actually
@Property
@PropertyLayout(fieldSetId = LayoutConstants.FieldSetId.DETAILS, sequence = "3.1") // <.>
public long getDaysSinceLastVisit() {
- return Math.abs(ChronoUnit.DAYS.between(getLastVisit(), clockService.getClock().nowAsLocalDate()));
+ return getLastVisit() != null
+ ? ChronoUnit.DAYS.between(getLastVisit(), clockService.getClock().nowAsLocalDate())
+ : null;
}
----
<.> positioned just after the `lastVisit` property
@@ -650,34 +652,22 @@ daysSinceLastVisit
-[#exercise-3-8-add-other-properties-for-petowner]
-== Ex 3.8: Add other properties for PetOwner
+[#exercise-3-10-use-meta-annotations-to-reduce-duplication]
+== Ex 3.10: Use meta-annotations to reduce duplication
-Let's add the two remaining properties for `PetOwner`:
+There is some duplication between menu:PetOwners[create] action and the `PetOwner` class; both define a "name" parameter along with other optional parameters, "telephoneNumber, "emailAddress" and "knownAs".
-[plantuml]
-----
-hide empty members
-hide methods
+With "name" you might have noticed that the `@Name` meta-annotation that came with the starter, and which centralizes the domain knowledge about what a "name" is.
-class Owner {
- +id
- ..
- #lastName
- #firstName
- ..
- -phoneNumber
- -emailAddress
-}
-----
+In this exercise we'll use the same approach with the "telephoneNumber".
+(We won't do "emailAddress" or "knownAs" though - we'll explore an even more powerful way to reduce duplication in the following exercise).
-They are `phoneNumber` and `emailAddress`.
=== Solution
[source,bash]
----
-git checkout tags/03-08-add-remaining-PetOwner-properties
+git checkout tags/03-10-use-meta-annotations-to-reduce-duplication
mvn clean install
mvn -pl spring-boot:run
----
@@ -690,73 +680,73 @@ mvn -pl spring-boot:run
.PhoneNumber.java
----
@Property(
- editing = Editing.ENABLED, // <.>
+ editing = Editing.ENABLED,
+ maxLength = PhoneNumber.MAX_LEN,
+ optionality = Optionality.OPTIONAL
+)
+@Parameter(
maxLength = PhoneNumber.MAX_LEN,
optionality = Optionality.OPTIONAL
)
-@Parameter(maxLength = PhoneNumber.MAX_LEN, optionality = Optionality.OPTIONAL)
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface PhoneNumber {
- int MAX_LEN = 30;
+ int MAX_LEN = 40;
}
----
-<.> any properties annotated with this meta-annotation will be editable by default
-* Similarly, create an `@EmailAddress` meta-annotation, defined to be an editable property:
+* update the `telephoneNumber` of `PetOwner` to use the meta-annotation:
+
[source,java]
-.EmailAddress.java
+.PetOwner.java
----
-@Property(
- editing = Editing.ENABLED,
- maxLength = EmailAddress.MAX_LEN,
- optionality = Optionality.OPTIONAL
-)
-@PropertyLayout(named = "E-mail") // <.>
-@Parameter(maxLength = EmailAddress.MAX_LEN, optionality = Optionality.OPTIONAL)
-@ParameterLayout(named = "E-mail") // <.>
-@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE })
-@Retention(RetentionPolicy.RUNTIME)
-public @interface EmailAddress {
-
- int MAX_LEN = 100;
-}
+@PhoneNumber // <.>
+@Column(length = PhoneNumber.MAX_LEN, nullable = true, name = "telephoneNumber") // <.>
+@Getter @Setter
+@PropertyLayout(fieldSetId = "contact", sequence = "1.1") // <.>
+private String telephoneNumber;
----
-<.> xref:refguide:applib:index/annotation/PropertyLayout.adoc#named[@PropertyLayout#named] allows characters to be used that are not valid Java identifiers.
-<.> xref:refguide:applib:index/annotation/ParameterLayout.adoc#named[@ParameterLayout#named] - ditto.
+<.> updated to use meta-annotation
+<.> The JPA implementation used by Apache Causeway (EclipseLink) does not support meta-annotations, so the field must still be annotated with `@Column`.
+We can at least use the `PhoneNumber.MAX_LEN` for the length.
+<.> Any annotations defined at the field level supplement or override those inherited from the meta-annotation.
-* add properties to `PetOwner`:
+* and update the `PetOwners#create()` action method also:
+
[source,java]
-.PetOwner.java
+.PetOwners.java
----
-@PhoneNumber
-@Column(length = PhoneNumber.MAX_LEN, nullable = true)
-@PropertyLayout(fieldSetId = "name", sequence = "1.5")
-@Getter @Setter
-private String phoneNumber;
-
-@EmailAddress
-@Column(length = EmailAddress.MAX_LEN, nullable = true)
-@PropertyLayout(fieldSetId = "name", sequence = "1.6")
-@Getter @Setter
-private String emailAddress;
+@Action(semantics = SemanticsOf.NON_IDEMPOTENT)
+// @ActionLayout(promptStyle = PromptStyle.DIALOG_SIDEBAR)
+public PetOwner create(
+ @Name final String name,
+ @Parameter(maxLength = 40, optionality = Optionality.OPTIONAL)
+ final String knownAs,
+ @PhoneNumber // <.>
+ final String telephoneNumber,
+ @Parameter(maxLength = 40, optionality = Optionality.OPTIONAL)
+ final String emailAddress) {
+ // ...
+}
----
+<.> updated to use meta-annotation
-[#exercise-3-9-validation]
-== Ex 3.9: Validation
+[#exercise-3-11-validation]
+== Ex 3.11: Validation
-At the moment there are no constraints for the format of `phoneNumber` or `emailAddress` properties.
+At the moment there are no constraints for the format of `telePhoneNumber` properties.
We can fix this by adding rules to their respective meta-annotations.
+
+=== Solution
+
[source,bash]
----
-git checkout tags/03-09-validation-rules-using-metaannotations
+git checkout tags/03-11-validation-rules-using-metaannotations
mvn clean install
mvn -pl spring-boot:run
----
@@ -773,49 +763,35 @@ mvn -pl spring-boot:run
editing = Editing.ENABLED,
maxLength = PhoneNumber.MAX_LEN,
optionality = Optionality.OPTIONAL,
- regexPattern = "[+]?[0-9 ]+", // <.>
- regexPatternReplacement = // <.>
- "Specify only numbers and spaces, optionally prefixed with '+'. " +
- "For example, '+353 1 555 1234', or '07123 456789'"
+ regexPattern = PhoneNumber.REGEX_PATTERN, // <.>
+ regexPatternReplacement = PhoneNumber.REGEX_PATTERN_REPLACEMENT // <.>
+)
+@Parameter(
+ maxLength = PhoneNumber.MAX_LEN,
+ optionality = Optionality.OPTIONAL,
+ regexPattern = PhoneNumber.REGEX_PATTERN, // <1>
+ regexPatternReplacement = PhoneNumber.REGEX_PATTERN_REPLACEMENT // <2>
)
-@Parameter(maxLength = PhoneNumber.MAX_LEN, optionality = Optionality.OPTIONAL)
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface PhoneNumber {
- int MAX_LEN = 30;
+ int MAX_LEN = 40;
+ String REGEX_PATTERN = "[+]?[0-9 ]+";
+ String REGEX_PATTERN_REPLACEMENT =
+ "Specify only numbers and spaces, optionally prefixed with '+'. " +
+ "For example, '+353 1 555 1234', or '07123 456789'";
+
}
----
<.> regex constraint
<.> validation message if the constraint is not met
-* Similarly, update `@EmailAddress`:
-+
-[source,java]
-.EmailAddress.java
-----
-@Property(
- editing = Editing.ENABLED,
- maxLength = EmailAddress.MAX_LEN,
- optionality = Optionality.OPTIONAL,
- regexPattern = "[^@]+@[^@]+[.][^@]+", // <.>
- regexPatternReplacement = "Invalid email address" // <.>
-)
-@PropertyLayout(named = "E-mail")
-@Parameter(maxLength = EmailAddress.MAX_LEN, optionality = Optionality.OPTIONAL)
-@ParameterLayout(named = "E-mail")
-@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE })
-@Retention(RetentionPolicy.RUNTIME)
-public @interface EmailAddress {
+Try out the application and check that these rules are applied.
- int MAX_LEN = 100;
-}
-----
-<.> regex constraint.
-(Should really use a more comprehensive regex, eg see https://emailregex.com).
-<.> validation message if the constraint is not met
-Try out the application and check that these rules are applied.
+[#exercise-3-12-more-validation]
+== Ex 3.12: More validation
The `updateName` action also has a validation rule, applied directly to the method:
@@ -834,25 +810,38 @@ public String validate0UpdateName(String newName) { // <.>
<.> validates the "0^th^" parameter of `updateName`.
More details on the validate supporting method can be found xref:refguide:applib-methods:prefixes.adoc#validate[here].
-We can Move this constraint onto the `@LastName` meta-annotation instead:
+In this exercise we'll move this constraint onto the `@Name` meta-annotation instead, using a xref:refguide:applib:index/spec/Specification.adoc[].
+
-* Update the `@LastName` meta-annotation using a xref:refguide:applib-classes:spec.adoc#specification[Specification]:
+=== Solution
+
+[source,bash]
+----
+git checkout tags/03-12-moves-validation-onto-metaannotation
+mvn clean install
+mvn -pl spring-boot:run
+----
+
+=== Task
+
+* Update the `@Name` meta-annotation using a xref:refguide:applib-classes:spec.adoc#specification[Specification]:
+
[source,java]
-.LastName.java
+.Name.java
----
-@Property(maxLength = LastName.MAX_LEN, mustSatisfy = LastName.Spec.class) // <.>
-@Parameter(maxLength = LastName.MAX_LEN, mustSatisfy = LastName.Spec.class) // <1>
-@ParameterLayout(named = "Last Name")
+@Property(maxLength = Name.MAX_LEN, mustSatisfy = Name.Spec.class) // <.>
+@Parameter(maxLength = Name.MAX_LEN, mustSatisfy = Name.Spec.class) // <1>
+@ParameterLayout(named = "Name")
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
-public @interface LastName {
+public @interface Name {
int MAX_LEN = 40;
+ String PROHIBITED_CHARACTERS = "&%$!";
- class Spec extends AbstractSpecification<String> { // <.>
+ class Spec extends AbstractSpecification<String> { // <.>
@Override public String satisfiesSafely(String candidate) {
- for (char prohibitedCharacter : "&%$!".toCharArray()) {
+ for (char prohibitedCharacter : PROHIBITED_CHARACTERS.toCharArray()) {
if( candidate.contains(""+prohibitedCharacter)) {
return "Character '" + prohibitedCharacter + "' is not allowed.";
}
@@ -865,19 +854,221 @@ public @interface LastName {
<.> indicates that the property or parameter value must satisfy the specification below
<.> defines the specification definition, where a non-null value is the reason why the specification is not satisfied.
-* Remove the `validate0UpdateName` from `PetOwner`.
+* Remove the `validate0UpdateName` method and `PROHIBITED_CHARACTERS` constant from `PetOwner`.
+
+* update the `@ActionLayout#describedAs` annotation for "updateName" to use `Name.PROHIBITED_CHARACTERS`
Test the app once more.
-=== Optional exercise
+NOTE: in making this refactoring we actually fixed a bug: there was no validation of the parameter when a new `PetOwner` was created; but now there is.
-NOTE: If you decide to do this optional exercise, make the changes on a git branch so that you can resume with the main flow of exercises later.
-As well as validating the `lastName`, it would be nice to also validate `firstName` with the same rule.
-As the logic is shared, create a new meta-(meta-)annotation called `@Name`, move the specification (and anything else that is common between lastName and firstName) to that new meta annotation, and then meta-annotate `@LastName` and `@FirstName` with `@Name`.
+[#exercise-3-13-scalar-custom-value-types]
+== Ex 3.13: Scalar custom value types
+
+We could use meta-annotations for the "emailAddress" property and parameter, but instead we'll reduce duplication using an even more powerful technique, namely custom value types.
+We'll define a custom class `EmailAddress` with value semantics, allowing validation and any other behaviour to move onto the custom class itself.
+
+Apache Causeway supports both scalar and composite value types.
+For email address, we'll use a single string, so it's a scalar value type.
+
+=== Solution
+
+[source,bash]
+----
+git checkout tags/03-13-scalar-custom-value-type-for-email-address
+mvn clean install
+mvn -pl spring-boot:run
+----
+
+=== Task
+
+* Define the `EmailAddress` value type:
++
+[source,java]
+.EmailAddress.java
+----
+@javax.persistence.Embeddable // <.>
+@org.apache.causeway.applib.annotation.Value // <.>
+@lombok.EqualsAndHashCode // <.>
+public class EmailAddress implements Serializable { // <.>
+
+ static final int MAX_LEN = 100;
+ static final int TYPICAL_LEN = 30;
+ static final Pattern REGEX =
+ Pattern.compile("^[\\w-\\+]+(\\.[\\w]+)*@[\\w-]+(\\.[\\w]+)*(\\.[a-zA-Z]{2,})$");
+
+ public static EmailAddress of(String value) { // <.>
+ if (_Strings.isNullOrEmpty(value)) {
+ return null;
+ }
+ if(!EmailAddress.REGEX.matcher(value).matches()) {
+ throw new RuntimeException("Invalid email format");
+ }
+
+ final var ea = new EmailAddress();
+ ea.value = value;
+ return ea;
+ }
+
+ protected EmailAddress() {} // <.>
+
+ @Getter
+ @Column( length = MAX_LEN, nullable = true, name = "emailAddress") // <1>
+ String value; // <.>
+}
+----
+<.> Required JPA annotations
+<.> Indicates to Causeway that this class is a value type (as opposed to an entity, view model or domain service)
+<.> Value types generally implement equals and hashCode
+<.> Value types generally are serializable
+<.> Validation moves to the factory method
+<.> no-arg constructor is required by JPA
+<.> The single data attribute to be persisted
+
+* Implement a "value semantics provider".
+This tells Causeway how to interact with the value type
++
+[source,java]
+.EmailAddressValueSemantics.java
+----
+@Named(PetOwnerModule.NAMESPACE + ".EmailAddressValueSemantics")
+@Component // <.>
+public class EmailAddressValueSemantics
+ extends ValueSemanticsAbstract<EmailAddress> { // <.>
+
+ @Override
+ public Class<EmailAddress> getCorrespondingClass() {
+ return EmailAddress.class;
+ }
+
+ @Override
+ public ValueType getSchemaValueType() { // <.>
+ return ValueType.STRING;
+ }
+
+ @Override
+ public ValueDecomposition decompose(final EmailAddress value) { // <.>
+ return decomposeAsNullable(value, EmailAddress::getValue, ()->null);
+ }
+
+ @Override
+ public EmailAddress compose(final ValueDecomposition decomposition) { // <4>
+ return composeFromNullable(
+ decomposition, ValueWithTypeDto::getString, EmailAddress::of, ()->null);
+ }
+
+ @Override
+ public DefaultsProvider<EmailAddress> getDefaultsProvider() { // <.>
+ return () -> null;
+ }
+
+ @Override
+ public Renderer<EmailAddress> getRenderer() { // <.>
+ return (context, emailAddress) -> emailAddress == null ? null : emailAddress.getValue();
+ }
+
+ @Override
+ public Parser<EmailAddress> getParser() { // <.>
+ return new Parser<>() {
+
+ @Override
+ public String parseableTextRepresentation(Context context, EmailAddress emailAddress) {
+ return renderTitle(emailAddress, EmailAddress::getValue);
+ }
+
+ @Override
+ public EmailAddress parseTextRepresentation(Context context, String text) {
+ return EmailAddress.of(text);
+ }
+
+ @Override
+ public int typicalLength() {
+ return EmailAddress.TYPICAL_LEN;
+ }
+
+ @Override
+ public int maxLength() {
+ return EmailAddress.MAX_LEN;
+ }
+ };
+ }
+
+ @Override
+ public IdStringifier<EmailAddress> getIdStringifier() { // <.>
+ return new IdStringifier.EntityAgnostic<>() {
+ @Override
+ public Class<EmailAddress> getCorrespondingClass() {
+ return EmailAddressValueSemantics.this.getCorrespondingClass();
+ }
+
+ @Override
+ public String enstring(@NonNull EmailAddress value) {
+ return _Strings.base64UrlEncode(value.getValue());
+ }
+
+ @Override
+ public EmailAddress destring(@NonNull String stringified) {
+ return EmailAddress.of(_Strings.base64UrlDecode(stringified));
+ }
+ };
+ }
+}
+----
+<.> Defined as a Spring `@Component` so that the framework can discover and use this value semantics provider
+<.> Typically inherit from `ValueSemanticsAbstract`, a convenience superclass
+<.> The `schemaValueType` in essence defines the widget that will be used to interact with render the value
+<.> The `ValueDecomposition` is primarily used by the xref:vro::about.adoc[] to convert to/from JSON.
+<.> The `DefaultsProvider` provides an initial value, if any.
+For some values there is often a reasonable default, eg `0` for a number, or `[0,0]` for a coordinate, or today's date.
+<.> The `Renderer` provides string and if required HTML representations of the value
+<.> The `Parser` converts string representations into the value.
+Note how this code delegates back to the `EmailAddress` value type itself
+<.> The `IdStringifier` returns a string representation of the value, in case it is used as an identifier of the object.
+The returned string would appear in URLs or bookmarks, for example.
+
+* update `PetOwner#emailAddress` to use the `EmailAddress` value type:
++
+[source,java]
+.PetOwner.java
+----
+@javax.persistence.Embedded // <.>
+@Getter @Setter
+@Property(editing = Editing.ENABLED, optionality = Optionality.OPTIONAL) // <.>
+@PropertyLayout(fieldSetId = "contact", sequence = "1.2")
+private EmailAddress emailAddress; // <.>
+----
+<.> required JPA annotation
+<.> need to explicitly indicate that this property is optional (previously it was inferred from `@Column(nullable=)`)
+<.> change the type from `String` to `EmailAddress`
+
+* update `PetOwners#create` to use the `EmailAddress` value type:
++
+[source,java]
+.PetOwner.java
+----
+@Action(semantics = SemanticsOf.NON_IDEMPOTENT)
+// @ActionLayout(promptStyle = PromptStyle.DIALOG_SIDEBAR)
+public PetOwner create(
+ @Name final String name,
+ @Parameter(maxLength = 40, optionality = Optionality.OPTIONAL)
+ final String knownAs,
+ @PhoneNumber
+ final String telephoneNumber,
+ @Parameter(optionality = Optionality.OPTIONAL)
+ final EmailAddress emailAddress) { // <.>
+ final var petOwner = PetOwner.withName(name);
+ petOwner.setKnownAs(knownAs);
+ petOwner.setTelephoneNumber(telephoneNumber);
+ petOwner.setEmailAddress(emailAddress);
+ return repositoryService.persist(petOwner);
+}
+----
+<.> Change the parameter' type from `String` to `EmailAddress`
+Run the application and try to enter an invalid
[#exercise-3-10-field-layout]
@@ -983,70 +1174,3 @@ You might find though that the main benefit of the layout file is to declare how
It really is a matter of personal preference which approach you use.
-
-[#exercise-3-11-column-orders]
-== Ex 3.11: Column Orders
-
-The home page of the webapp shows a list of all `PetOwner`s (inherited from the original simple app).
-We also see a list of `PetOwner`s if we invoke menu:Pet Owners[List All].
-
-The first is a "parented" collection (it is parented by the home page view model), the second is a standalone collection (it is returned from an action).
-
-The properties that are shown as columns that are shown is based on two different mechanisms.
-The first is whether the property is visible at all in any tables, which can be specified using `@PropertyLayout(hidden=...)` (see xref:refguide:applib:index/annotation/PropertyLayout.adoc#hidden[@PropertyLayout#hidden]).
-The second is to use a "columnOrder" file.
-
-In this exercise, we'll use the latter approach.
-
-=== Solution
-
-[source,bash]
-----
-git checkout tags/03-11-PetOwner-columnOrder
-mvn clean install
-mvn -pl spring-boot:run
-----
-
-=== Task
-
-* Declare the `id` field of `PetOwner` as a property by adding a getter and other annotations:
-+
-[source,java]
-.PetOwner.java
-----
-@Id
-@GeneratedValue(strategy = GenerationType.AUTO)
-@Column(name = "id", nullable = false)
-@Getter @Setter // <.>
-@PropertyLayout(fieldSetId = "metadata", sequence = "1") // <.>
-private Long id;
-----
-<.> makes field available as a property
-<.> positions property in the metadata fieldset (before `version`).
-
-* update the columnOrder for standalone collections of `PetOrder`:
-+
-[source,java]
-.PetOwner.columnOrder.txt
-----
-name
-id
-#version
-----
-+
-This will show only `name` and `id`; none of the other properties will be visible as columns.
-
-* create a new file `HomePageViewModel#objects.columnOrder.txt` (in the same package as `HomePageViewModel`) to define the columns visible in the `objects` collection of that view model:
-+
-[source,java]
-.HomePageViewModel#objects.columnOrder.txt
-----
-name
-id
-#version
-----
-
-* delete the (unused) `PetOwner#others.columnOrder.txt` file.
-
-Run the application and confirm the columns are as expected.
-You should also be able to update the files and reload changes (on IntelliJ, menu:Run[Debugging Actions > Reload Changed Classes]) and inspect the updates without having to restart the app.
diff --git a/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc b/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc
index 6ebd2b2b7f..760a44e164 100644
--- a/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc
@@ -1,15 +1,11 @@
TODO: ideas for future steps:
-- introduce a meta-annotation
-- introduce a custom type for these
+* more user-friendly error message if hit duplicate (enter two pet owners with same name)
-add validation for telephone number and email address.
-
PetOwner#daysSinceLastVisit could be made into a mixin - eg if these marketing analytics were the responsibility of some other module.
-
hidden properties
(causeway) 01/08: CAUSEWAY-2873: adds 03-06
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch CAUSEWAY-2873
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit de1e120b19db79ac426fb1b5ad0105657b496ed4
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Sat May 25 06:49:55 2024 +0100
CAUSEWAY-2873: adds 03-06
---
.../petclinic/pages/020-the-petclinic-domain.adoc | 4 +-
.../petclinic/pages/030-petowner-entity.adoc | 170 ++++++++++++++-------
2 files changed, 121 insertions(+), 53 deletions(-)
diff --git a/antora/components/tutorials/modules/petclinic/pages/020-the-petclinic-domain.adoc b/antora/components/tutorials/modules/petclinic/pages/020-the-petclinic-domain.adoc
index f7b58db22f..b301f60899 100644
--- a/antora/components/tutorials/modules/petclinic/pages/020-the-petclinic-domain.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/020-the-petclinic-domain.adoc
@@ -397,5 +397,5 @@ protected void execute(final ExecutionContext ec) {
Test that you can quickly create a whole set of `PetOwner` objects
from the "Prototyping" menu:
-* "Prototyping > Run Fixture Script" and select "Domain App Demo", or
-* "Prototyping > Recreate Objects and Return First".
+* menu:Prototyping[Run Fixture Script] and select "Domain App Demo", or
+* menu:Prototyping[Recreate Objects and Return First].
diff --git a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
index d51996df78..7e3eec8025 100644
--- a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
@@ -255,7 +255,7 @@ emailAddress
lastVisit
----
-Confirm that "Pet Owners > List" now shows the additional properties as columns.
+Confirm that menu:Pet Owners[List All] now shows the additional properties as columns.
TIP: if you make changes to this file then your IDE may be able detect the changes automatically.
For example, with IntelliJ you can use "Reload Changed Classes" menu item.
@@ -264,120 +264,188 @@ You can learn more about layout files xref:userguide::ui-layout-and-hints.adoc[h
-[#exercise-3-5-run-default-fixture-at-startup]
-== Ex 3.5: Run default fixture at startup
-Prototyping involves a lot of iteration, meaning make a change, re-run the app, in the app re-create some example objects, inspect the result.
+[#exercise-3-5-initial-fixture-script]
+== Ex 3.5: Initial Fixture Script
-One way to create objects is from the Prototyping menu; you might already have been doing this.
+As we prototype with an in-memory database, it means that we need to setup the database each time we restart the application.
+Using the menu:Prototyping[Fixture Scripts] menu to setup data saves some time, but it would nicer still if that script could be run automatically.
+We can do that by specifying a configuration property.
+
+We can also leverage link:https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.profiles[Spring Boot profiles] to keep this configuration separate.
-xxx
=== Solution
[source,bash]
----
-git checkout tags/03-04-list-new-properties-of-PetOwner
+git checkout tags/03-05-initial-fixture-script
mvn clean install
mvn -pl spring-boot:run
----
+
=== Tasks
+* locate the `application.yml` configuration file (in the webapp module) and create this new file alongside it:
++
+[source,yaml]
+.application-dev.yml
----
+causeway:
+ testing:
+ fixtures:
+ initial-script: domainapp.webapp.application.fixture.scenarios.DomainAppDemo
----
+* modify the startup of your application to enable this profile, using this system prpoerty:
++
+[source]
----
+-Dspring.profiles.active=dev
----
+We also need to make one small modification to the fixture script itself.
+The initial fixtures are run with a built-in user called "\__system", that has no roles and therefore no permissions.
+We either need to change the fixture script to run as a different user that _will_ have permissions (there's a service called xref:refguide:applib:index/services/sudo/SudoService.adoc[] that would let us do that), or, simpler, just temporarily switch off permission checks.
+We'll go with the second option:
+
+* locate the `PetOwner_persona.Builder` class, and make this change:
++
+[source,java]
+.PetOwner_persona.java
----
+@RequiredArgsConstructor
+public enum PetOwner_persona /*...*/ {
+
+ @Accessors(chain = true)
+ public static class Builder extends BuilderScriptWithResult<PetOwner> {
+
+ @Override
+ protected PetOwner buildResult(final ExecutionContext ec) {
+
+ val petOwner = petOwners.create(persona.name); // <.>
+
+ // ...
+ }
+ // ...
+ }
+ // ...
+}
----
-*UP TO HERE*
+<.> Previously this was `wrap(petOwners).create(...)`.
+The `wrap(...)` method uses the xref:refguide:applib:index/services/wrapper/WrapperFactory.adoc[] to wrap the domain object in a proxy, and the proxy enforces all the business rules, including visibility.
-[#exercise-3-4-modify-the-menu-action-to-create-petowners]
-== Ex 3.4: Modify the menu action to create PetOwners
+When you now run the application you should now find that there are 10 `PetOwner` objects already created.
-If we want to create a new `PetOwner` and provide their `firstName`, at the moment it's a two stage process: create the `PetOwner` (using `PetOwners#create` action from the menu), then update their name (using the `updateName` action that we just looked at).
-In this exercise we'll simplify that workflow by allowing the `firstName` to optionally be specified during the initial create.
+[#exercise-3-6-update-home-page-to-show-PetOwners]
+== Ex 3.5: Update Home Page to show PetOwners
+
+Every Causeway app can nominate a home page, basically a xref:userguide::view-models.adoc[view model] that's been annotated with xref:refguide:applib:index/annotation/HomePage.adoc[].
+Currently the home page for our app is the one we inherited from the starter app, showing a list of ``SimpleObject``s.
+
+In this exercise, we'll refactor the home page view model to show a list of ``PetOwner``s instead.
+
=== Solution
[source,bash]
----
-git checkout tags/03-04-modifies-PetOwners-create-action
+git checkout tags/03-06-update-home-page-to-show-pet-owners
mvn clean install
mvn -pl spring-boot:run
----
+
=== Tasks
-* update `Orders#create` action, so that the end user can specify a `firstName` when creating a new `PetOwner`:
+Locate the `HomePageViewModel` class:
+
+* inject `PetOwners` domain service (instead of `SimpleObjects`)
+* change the `title()` implementation
+* rename the collection from `getObjects()` to `getPetOwners()`
+
+The positioning of the collection is also specified in the corresponding layout file, and so also needs updating.
+
+* locate the `HomePageViewModel.layout.xml` file, and update accordingly
+
-[source,java]
-.PetOwners.java
-----
-@Action(semantics = SemanticsOf.NON_IDEMPOTENT)
-@ActionLayout(promptStyle = PromptStyle.DIALOG_SIDEBAR)
-public PetOwner create(
- @LastName final String lastName,
- @FirstName final String firstName) {
- return repositoryService.persist(PetOwner.withName(lastName, firstName));
-}
+[source,xml]
+.HomePageViewModel.layout.xml
+----
+<bs3:col span="6" unreferencedCollections="true">
+ <bs3:row>
+ <bs3:col span="12">
+ <cpt:collection id="petOwners" defaultView="table"/>
+ </bs3:col>
+ </bs3:row>
+</bs3:col>
----
+By default this will show all of the properties of `PetOwner`.
-=== Optional exercise
+We can change this by creating a file `HomePageViewModel#petOwners.columnOrder.txt`, alongside the `HomePageViewModel`:
-NOTE: If you decide to do this optional exercise, make the changes on a git branch so that you can resume with the main flow of exercises later.
+[source,txt]
+.HomePageViewModel#petOwners.columnOrder.txt
+----
+name
+telephoneNumber
+emailAddress
+#attachment
+#lastVisit
+#knownAs
+#version
+----
-It would be nice if the `PetOwner` were identified by both their `firstName` and their `lastName`; at the moment every `PetOwner` must have a unique `lastName`.
-Or, even better would be to introduce some sort of "customerNumber" and use this as the unique identifier.
+TIP: the action "Download .columnOrder.txt files (ZIP)" (available only when prototyping) provides an easy way to obtain this file; you can then update as required.
+----
+----
+----
+----
-[#exercise-3-5-initial-fixture-script]
-== Ex 3.5: Initial Fixture Script
+----
+----
-As we prototype with an in-memory database, it means that we need to setup the database each time we restart the application.
-Using the menu:Prototyping[Fixture Scripts] menu to setup data saves some time, but it would nicer still if that script could be run automatically.
-We can do that by specifying a configuration property.
+TODO: change the home page to list the pet owners by default.
-We can also leverage link:https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.profiles[Spring Boot profiles] to keep this configuration separate.
+*UP TO HERE*
+
+[#exercise-3-4-modify-the-menu-action-to-create-petowners]
+== Ex 3.4: Modify the menu action to create PetOwners
+If we want to create a new `PetOwner` and provide their `firstName`, at the moment it's a two stage process: create the `PetOwner` (using `PetOwners#create` action from the menu), then update their name (using the `updateName` action that we just looked at).
+
+In this exercise we'll simplify that workflow by allowing the `firstName` to optionally be specified during the initial create.
=== Solution
[source,bash]
----
-git checkout tags/03-05-initial-fixture-script
+git checkout tags/03-04-modifies-PetOwners-create-action
mvn clean install
mvn -pl spring-boot:run
----
-
=== Tasks
-* create the following file in `src/main/resources` of the webapp (alongside the existing `application.yml` file):
-+
-[source,yaml]
-.application-dev.yaml
-----
-causeway:
- testing:
- fixtures:
- initial-script: petclinic.webapp.application.fixture.scenarios.PetClinicDemo
-----
-
-* modify the startup of your application to enable this profile, using this system prpoerty:
+* update `Orders#create` action, so that the end user can specify a `firstName` when creating a new `PetOwner`:
+
-[source]
+[source,java]
+.PetOwners.java
----
--Dspring.profiles.active=dev
+@Action(semantics = SemanticsOf.NON_IDEMPOTENT)
+@ActionLayout(promptStyle = PromptStyle.DIALOG_SIDEBAR)
+public PetOwner create(
+ @LastName final String lastName,
+ @FirstName final String firstName) {
+ return repositoryService.persist(PetOwner.withName(lastName, firstName));
+}
----
-When you run the application you should now find that there are 10 `PetOwner` objects already created.
(causeway) 08/08: CAUSEWAY-2873: 03-14
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch CAUSEWAY-2873
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit b49ddafff1c5c27429cb0c3f8e92387f3f630ad5
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Sun May 26 10:51:42 2024 +0100
CAUSEWAY-2873: 03-14
---
.../petclinic/pages/030-petowner-entity.adoc | 205 ++++++++++++++-------
1 file changed, 142 insertions(+), 63 deletions(-)
diff --git a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
index 33c41617cd..496b871771 100644
--- a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
@@ -1071,106 +1071,185 @@ public PetOwner create(
Run the application and try to enter an invalid
-[#exercise-3-10-field-layout]
-== Ex 3.10: Field layout
+[#exercise-3-14-use-layout-xml-file-for-ui-semantics]
+== Ex 3.14: Use layout xml file for UI semantics
-At the moment all the properties of `PetOwner` are grouped into a single fieldset.
-The UI would be improved by grouping properties according to their nature, for example the "phoneNumber" and "emailAddress" in a "Contact Details" fieldset.
+At the moment the associated `.layout.xml` file for `PetOwner` is used to define rows, columns and fieldsets, while the xref:refguide:applib:index/annotation/PropertyLayout.adoc[@PropertyLayout] annotation is grouped to associate properties with those fieldsets.
+
+If we prefer, we can move specify this association within the `PetOwner.layout.xml` file instead.
+And we can also do the same thing associating actions with properties or collections.
+
+This has the benefit of being dynamic; we can move fields around in the layout without having to recompile/restart the application.
-We do this using the associated `PetOwner.layout.xml` file (which defines the positioning of the fieldsets), and also using the annotations within `PetOwner` (which associate the properties to those fieldsets).
=== Solution
[source,bash]
----
-git checkout tags/03-10-PetOwner-fieldsets
+git checkout tags/03-14-PetOwner-fieldsets
mvn clean install
mvn -pl spring-boot:run
----
=== Task
-* modify the `PetOwner.layout.xml`, adding two new `fieldSet` definitions after the first `tabGroup`:
+Associate properties with fieldsets using `.layout.xml`:
+
+* associate `name` property with `identity` fieldset:
+
+** remove `@PropertyLayout` from `PetOwner`:
++
+[source,java]
+.PetOwner.java
+----
+@Name
+@Column(length = Name.MAX_LEN, nullable = false, name = "name")
+@Getter @Setter @ToString.Include
+// @PropertyLayout(fieldSetId = LayoutConstants.FieldSetId.IDENTITY, sequence = "1")
+private String name;
+----
+
+** add to `PetOwner.layout.xml`:
+
[source,xml]
.PetOwner.layout.xml
----
-<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-<bs:grid>
- <bs:row>
- <!-- ... -->
- </bs:row>
- <bs:row>
- <bs:col span="6">
- <bs:tabGroup>
- <!-- ... -->
- </bs:tabGroup>
- <c:fieldSet id="contactDetails" name="Contact Details"/> <!--.-->
- <c:fieldSet id="notes" name="Notes"/> <!--.-->
- </bs:col>
- <bs:col span="6">
- <!-- ... -->
- </bs:col>
- </bs:row>
-</bs:grid>
-----
-<.> fieldSet for contact details
-<.> fieldset for the notes
-
-* modify the `@PropertyLayout` annotation for the properties to associate with these fieldsets:
+<cpt:fieldSet name="Identity" id="identity">
+ <cpt:property id="name"/>
+</cpt:fieldSet>
+----
+
+* similarly for `knownAs`
+* similarly for `telephoneNumber`
+* similarly for `emailAddress`
+* similarly for `notes`
+* similarly for `lastVisit`
+* similarly for `daysSinceLastVisit`
+* similarly for `attachment`
+
+* associate `version` property with `metadata` fieldset, and also explicitly specify the location of the two framework-provided properties that also reside in that fieldset:
+
+** change ``PetOwner#version` property to::
+
[source,java]
.PetOwner.java
----
-// ...
-@PropertyLayout(fieldSetId = "contactDetails", sequence = "1") // <.>
-private String phoneNumber;
-
-// ...
-@PropertyLayout(fieldSetId = "contactDetails", sequence = "2") // <.>
-private String emailAddress;
+@Version
+@Column(name = "version", nullable = false)
+//@PropertyLayout(fieldSetId = "metadata", sequence = "999") // <.>
+@Property // <.>
+@Getter @Setter
+private long version;
+----
+<.> The `@PropertyLayout` was removed...
+<.> ... but `@Property` has been added instead.
++
+This is because at least one of @Property` or `@PropertyLayout` must be present to identify the field as a property (at least using the configuration of the framework is concerned).
-// ...
-@PropertyLayout(fieldSetId = "notes", sequence = "1") // <.>
-private String notes;
+** add to `PetOwner.layout.xml`:
++
+[source,xml]
+.PetOwner.layout.xml
+----
+<cpt:fieldSet name="Metadata" id="metadata">
+ <cpt:property id="logicalTypeName"/> // <.>
+ <cpt:property id="objectIdentifier"/> // <1>
+ <cpt:property id="version"/>
+</cpt:fieldSet>
----
-<.> associates as the 1^st^ property in the "contact details" fieldset
-<.> associates as the 2^nd^ property in the "contact details" fieldset
-<.> associates with the "notes" fieldset
+<.> framework-provided properties.
+If we want the `version` property to appear last in the fieldset, then we need to specify these other properties also.
-Run the application; the layout should look like:
-image::03-10/fieldsets.png[width=800]
+* associate the `updateName` action with the `name` property
+** remove from `PetOwner`:
++
+[source,java]
+.PetOwner.java
+----
+ @Action( ... )
+ @ActionLayout(
+ // associateWith = "name", // <.>
+ ...
+ )
+ public PetOwner updateName(
+ @Name final String name) { ... }
+----
+<.> deleted this line
-The layout file can be reloaded dynamically (on IntelliJ, menu:Run[Debugging Actions > Reload Changed Classes]), so you can inspect any updates without having to restart the app.
-Experiment with this by moving a fieldset into a tab group, or change the width of a column).
+** add to `PetOwner.layout.xml`:
++
+[source,xml]
+.PetOwner.layout.xml
+----
+<cpt:property id="name">
+ <cpt:action id="updateName"/>
+</cpt:property>
+----
+* similarly `updateAttachment` action
+** remove from `PetOwner`:
++
+[source,java]
+.PetOwner.java
+----
+@Action( ... )
+// @ActionLayout( // <.>
+// associateWith = "attachment", // <1>
+// position = ActionLayout.Position.PANEL // <1>
+// ) // <1>
+public PetOwner updateAttachment( ... ) { ... }
+----
+<.> deleted lines
-=== Optional Exercise
+** add to `PetOwner.layout.xml`:
++
+[source,xml]
+.PetOwner.layout.xml
+----
+<cpt:property id="attachment">
+ <cpt:action id="updateAttachment" position="PANEL"/>
+</cpt:property>
+----
-NOTE: If you decide to do this optional exercise, make the changes on a git branch so that you can resume with the main flow of exercises later.
-It is also possible to associate the properties to fieldsets using only the `.layout.xml` file.
-In fact, pretty much all of the metadata in the `@XxxLayout` annotations can be specified in the layout file.
+** remove from `PetOwner`:
++
+[source,java]
+.PetOwner.java
+----
+@Action( ... )
+@ActionLayout(
+ // fieldSetId = LayoutConstants.FieldSetId.IDENTITY, // <.>
+ // position = ActionLayout.Position.PANEL, // <1>
+ describedAs = "Deletes this object from the persistent datastore"
+)
+public void delete() { ... }
+----
+<.> deleted lines
+* add to `PetOwner.layout.xml`:
++
[source,xml]
.PetOwner.layout.xml
----
-<c:fieldSet id="contactDetails" name="Contact Details">
- <c:property id="phoneNumber"/>
- <c:property id="emailAddress"/>
-</c:fieldSet>
-<c:fieldSet id="notes" name="Notes">
- <c:property id="notes"/>
-</c:fieldSet>
+<cpt:fieldSet name="Identity" id="identity">
+ <cpt:action id="delete" position="PANEL"/> <!--.-->
+ <cpt:property id="name">
+ <cpt:action id="updateName"/>
+ </cpt:property>
+ ...
+</cpt:fieldSet>
----
+<.> added, _before_ any of the ``<property>``s
-The `@PropertyLayout` annotations could then be removed.
-
-Using the layout file to specify individual properties provides even more fine-grained control when dynamically reloading, so you could for example switch the order of properties in a fieldset and inspect the changes immediately without having to restart the app.
-You might find though that the main benefit of the layout file is to declare how the different "regions" of the UI fit together in terms of rows, columns, tabs and fieldsets, and then use annotations to slot the properties/actions into those regions.
-It really is a matter of personal preference which approach you use.
+Whether you choose to use layout file only or a mixture of layout file and annotations is a matter of taste.
+Notice that the `.layout.xml` files has elements with the "unreferencedProperties", "unreferencedCollections" or "unreferencedActions" (and is considered invalid if these are missing).
+As you might expect, these tags indicate where to render properties, collections or actions whose placement has not been specified explicitly.
+IMPORTANT::
+This is an important principle of the _naked objects pattern_ ; the domain object should always be rendered in some way or another.
+The presence of UI semantics (`@XxxLayout` annotations or the `.layout.xml` files) merely influence how that rendering is performed.
(causeway) 07/08: CAUSEWAY-2873: attempts to improve styling of asciidoc plugin
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch CAUSEWAY-2873
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit 4eac9d1cebc145b1e9ae1efb5d638ba899f8f7a2
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Sun May 26 10:29:29 2024 +0100
CAUSEWAY-2873: attempts to improve styling of asciidoc plugin
... though with no success.
---
.asciidoctorconfig | 6 +
antora/.asciidoctor-styles/asciidoc-classic.css | 527 ++++++++++++++++++++++++
antora/.asciidoctor-styles/boot-flatly.css | 350 ++++++++++++++++
3 files changed, 883 insertions(+)
diff --git a/.asciidoctorconfig b/.asciidoctorconfig
index 6732835d4d..68dced8e96 100644
--- a/.asciidoctorconfig
+++ b/.asciidoctorconfig
@@ -10,3 +10,9 @@
// see discussion here https://github.com/asciidoctor/asciidoctor-intellij-plugin/issues/548
:kroki-default-options: inline
+:linkcss:
+:stylesdir: {asciidoctorconfigdir}/antora/.asciidoctor-styles
+:stylesheet: asciidoc-classic.css
+// :stylesdir: https://raw.githubusercontent.com/darshandsoni/asciidoctor-skins/gh-pages/css
+// :stylesheet: asciidoc-classic.css
+
diff --git a/antora/.asciidoctor-styles/asciidoc-classic.css b/antora/.asciidoctor-styles/asciidoc-classic.css
new file mode 100644
index 0000000000..be88313e92
--- /dev/null
+++ b/antora/.asciidoctor-styles/asciidoc-classic.css
@@ -0,0 +1,527 @@
+/* Shared CSS for AsciiDoc xhtml11 and html5 backends */
+
+/* Default font. */
+body {
+ font-family: Georgia,serif;
+}
+
+/* Title font. */
+h1, h2, h3, h4, h5, h6,
+div.title, caption.title,
+thead, p.table.header,
+#toctitle,
+#author, #revnumber, #revdate, #revremark,
+#footer {
+ font-family: Arial,Helvetica,sans-serif;
+}
+
+body {
+ margin: 1em 5% 1em 5%;
+}
+
+a {
+ color: blue;
+ text-decoration: underline;
+}
+a:visited {
+ color: fuchsia;
+}
+
+em {
+ font-style: italic;
+ color: navy;
+}
+
+strong {
+ font-weight: bold;
+ color: #083194;
+}
+
+h1, h2, h3, h4, h5, h6 {
+ color: #527bbd;
+ margin-top: 1.2em;
+ margin-bottom: 0.5em;
+ line-height: 1.3;
+}
+
+h1, h2, h3 {
+ border-bottom: 2px solid silver;
+}
+h2 {
+ padding-top: 0.5em;
+}
+h3 {
+ float: left;
+}
+h3 + * {
+ clear: left;
+}
+h5 {
+ font-size: 1.0em;
+}
+
+div.sectionbody {
+ margin-left: 0;
+}
+
+hr {
+ border: 1px solid silver;
+}
+
+p {
+ margin-top: 0.5em;
+ margin-bottom: 0.5em;
+}
+
+ul, ol, li > p {
+ margin-top: 0;
+}
+ul > li { color: #aaa; }
+ul > li > * { color: black; }
+
+.monospaced, code, pre {
+ font-family: "Courier New", Courier, monospace;
+ font-size: inherit;
+ color: navy;
+ padding: 0;
+ margin: 0;
+}
+pre {
+ white-space: pre-wrap;
+}
+
+#author {
+ color: #527bbd;
+ font-weight: bold;
+ font-size: 1.1em;
+}
+#email {
+}
+#revnumber, #revdate, #revremark {
+}
+
+#footer {
+ font-size: small;
+ border-top: 2px solid silver;
+ padding-top: 0.5em;
+ margin-top: 4.0em;
+}
+#footer-text {
+ float: left;
+ padding-bottom: 0.5em;
+}
+#footer-badges {
+ float: right;
+ padding-bottom: 0.5em;
+}
+
+#preamble {
+ margin-top: 1.5em;
+ margin-bottom: 1.5em;
+}
+div.imageblock, div.exampleblock, div.verseblock,
+div.quoteblock, div.literalblock, div.listingblock, div.sidebarblock,
+div.admonitionblock {
+ margin-top: 1.0em;
+ margin-bottom: 1.5em;
+}
+div.admonitionblock {
+ margin-top: 2.0em;
+ margin-bottom: 2.0em;
+ margin-right: 10%;
+ color: #606060;
+}
+
+div.content { /* Block element content. */
+ padding: 0;
+}
+
+/* Block element titles. */
+div.title, caption.title {
+ color: #527bbd;
+ font-weight: bold;
+ text-align: left;
+ margin-top: 1.0em;
+ margin-bottom: 0.5em;
+}
+div.title + * {
+ margin-top: 0;
+}
+
+td div.title:first-child {
+ margin-top: 0.0em;
+}
+div.content div.title:first-child {
+ margin-top: 0.0em;
+}
+div.content + div.title {
+ margin-top: 0.0em;
+}
+
+div.sidebarblock > div.content {
+ background: #ffffee;
+ border: 1px solid #dddddd;
+ border-left: 4px solid #f0f0f0;
+ padding: 0.5em;
+}
+
+div.listingblock > div.content {
+ border: 1px solid #dddddd;
+ border-left: 5px solid #f0f0f0;
+ background: #f8f8f8;
+ padding: 0.5em;
+}
+
+div.quoteblock, div.verseblock {
+ padding-left: 1.0em;
+ margin-left: 1.0em;
+ margin-right: 10%;
+ border-left: 5px solid #f0f0f0;
+ color: #888;
+}
+
+div.quoteblock > div.attribution {
+ padding-top: 0.5em;
+ text-align: right;
+}
+
+div.verseblock > pre.content {
+ font-family: inherit;
+ font-size: inherit;
+}
+div.verseblock > div.attribution {
+ padding-top: 0.75em;
+ text-align: left;
+}
+/* DEPRECATED: Pre version 8.2.7 verse style literal block. */
+div.verseblock + div.attribution {
+ text-align: left;
+}
+
+div.admonitionblock .icon {
+ vertical-align: top;
+ font-size: 1.1em;
+ font-weight: bold;
+ text-decoration: underline;
+ color: #527bbd;
+ padding-right: 0.5em;
+}
+div.admonitionblock td.content {
+ padding-left: 0.5em;
+ border-left: 3px solid #dddddd;
+}
+
+div.exampleblock > div.content {
+ border-left: 3px solid #dddddd;
+ padding-left: 0.5em;
+}
+
+div.imageblock div.content { padding-left: 0; }
+span.image img { border-style: none; vertical-align: text-bottom; }
+a.image:visited { color: white; }
+
+dl {
+ margin-top: 0.8em;
+ margin-bottom: 0.8em;
+}
+dt {
+ margin-top: 0.5em;
+ margin-bottom: 0;
+ font-style: normal;
+ color: navy;
+}
+dd > *:first-child {
+ margin-top: 0.1em;
+}
+
+ul, ol {
+ list-style-position: outside;
+}
+ol.arabic {
+ list-style-type: decimal;
+}
+ol.loweralpha {
+ list-style-type: lower-alpha;
+}
+ol.upperalpha {
+ list-style-type: upper-alpha;
+}
+ol.lowerroman {
+ list-style-type: lower-roman;
+}
+ol.upperroman {
+ list-style-type: upper-roman;
+}
+
+div.compact ul, div.compact ol,
+div.compact p, div.compact p,
+div.compact div, div.compact div {
+ margin-top: 0.1em;
+ margin-bottom: 0.1em;
+}
+
+tfoot {
+ font-weight: bold;
+}
+td > div.verse {
+ white-space: pre;
+}
+
+div.hdlist {
+ margin-top: 0.8em;
+ margin-bottom: 0.8em;
+}
+div.hdlist tr {
+ padding-bottom: 15px;
+}
+dt.hdlist1.strong, td.hdlist1.strong {
+ font-weight: bold;
+}
+td.hdlist1 {
+ vertical-align: top;
+ font-style: normal;
+ padding-right: 0.8em;
+ color: navy;
+}
+td.hdlist2 {
+ vertical-align: top;
+}
+div.hdlist.compact tr {
+ margin: 0;
+ padding-bottom: 0;
+}
+
+.comment {
+ background: yellow;
+}
+
+.footnote, .footnoteref {
+ font-size: 0.8em;
+}
+
+span.footnote, span.footnoteref {
+ vertical-align: super;
+}
+
+#footnotes {
+ margin: 20px 0 20px 0;
+ padding: 7px 0 0 0;
+}
+
+#footnotes div.footnote {
+ margin: 0 0 5px 0;
+}
+
+#footnotes hr {
+ border: none;
+ border-top: 1px solid silver;
+ height: 1px;
+ text-align: left;
+ margin-left: 0;
+ width: 20%;
+ min-width: 100px;
+}
+
+div.colist td {
+ padding-right: 0.5em;
+ padding-bottom: 0.3em;
+ vertical-align: top;
+}
+div.colist td img {
+ margin-top: 0.3em;
+}
+
+@media print {
+ #footer-badges { display: none; }
+}
+
+#toc {
+ margin-bottom: 2.5em;
+}
+
+#toctitle {
+ color: #527bbd;
+ font-size: 1.1em;
+ font-weight: bold;
+ margin-top: 1.0em;
+ margin-bottom: 0.1em;
+}
+
+div.toclevel0, div.toclevel1, div.toclevel2, div.toclevel3, div.toclevel4 {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+div.toclevel2 {
+ margin-left: 2em;
+ font-size: 0.9em;
+}
+div.toclevel3 {
+ margin-left: 4em;
+ font-size: 0.9em;
+}
+div.toclevel4 {
+ margin-left: 6em;
+ font-size: 0.9em;
+}
+
+span.aqua { color: aqua; }
+span.black { color: black; }
+span.blue { color: blue; }
+span.fuchsia { color: fuchsia; }
+span.gray { color: gray; }
+span.green { color: green; }
+span.lime { color: lime; }
+span.maroon { color: maroon; }
+span.navy { color: navy; }
+span.olive { color: olive; }
+span.purple { color: purple; }
+span.red { color: red; }
+span.silver { color: silver; }
+span.teal { color: teal; }
+span.white { color: white; }
+span.yellow { color: yellow; }
+
+span.aqua-background { background: aqua; }
+span.black-background { background: black; }
+span.blue-background { background: blue; }
+span.fuchsia-background { background: fuchsia; }
+span.gray-background { background: gray; }
+span.green-background { background: green; }
+span.lime-background { background: lime; }
+span.maroon-background { background: maroon; }
+span.navy-background { background: navy; }
+span.olive-background { background: olive; }
+span.purple-background { background: purple; }
+span.red-background { background: red; }
+span.silver-background { background: silver; }
+span.teal-background { background: teal; }
+span.white-background { background: white; }
+span.yellow-background { background: yellow; }
+
+span.big { font-size: 2em; }
+span.small { font-size: 0.6em; }
+
+span.underline { text-decoration: underline; }
+span.overline { text-decoration: overline; }
+span.line-through { text-decoration: line-through; }
+
+div.unbreakable { page-break-inside: avoid; }
+
+
+/*
+ * xhtml11 specific
+ *
+ * */
+
+div.tableblock {
+ margin-top: 1.0em;
+ margin-bottom: 1.5em;
+}
+div.tableblock > table {
+ border: 3px solid #527bbd;
+}
+thead, p.table.header {
+ font-weight: bold;
+ color: #527bbd;
+}
+p.table {
+ margin-top: 0;
+}
+/* Because the table frame attribute is overriden by CSS in most browsers. */
+div.tableblock > table[frame="void"] {
+ border-style: none;
+}
+div.tableblock > table[frame="hsides"] {
+ border-left-style: none;
+ border-right-style: none;
+}
+div.tableblock > table[frame="vsides"] {
+ border-top-style: none;
+ border-bottom-style: none;
+}
+
+
+/*
+ * html5 specific
+ *
+ * */
+
+table.tableblock {
+ margin-top: 1.0em;
+ margin-bottom: 1.5em;
+}
+thead, p.tableblock.header {
+ font-weight: bold;
+ color: #527bbd;
+}
+p.tableblock {
+ margin-top: 0;
+}
+table.tableblock {
+ border-width: 3px;
+ border-spacing: 0px;
+ border-style: solid;
+ border-color: #527bbd;
+ border-collapse: collapse;
+}
+th.tableblock, td.tableblock {
+ border-width: 1px;
+ padding: 4px;
+ border-style: solid;
+ border-color: #527bbd;
+}
+
+table.tableblock.frame-topbot {
+ border-left-style: hidden;
+ border-right-style: hidden;
+}
+table.tableblock.frame-sides {
+ border-top-style: hidden;
+ border-bottom-style: hidden;
+}
+table.tableblock.frame-none {
+ border-style: hidden;
+}
+
+th.tableblock.halign-left, td.tableblock.halign-left {
+ text-align: left;
+}
+th.tableblock.halign-center, td.tableblock.halign-center {
+ text-align: center;
+}
+th.tableblock.halign-right, td.tableblock.halign-right {
+ text-align: right;
+}
+
+th.tableblock.valign-top, td.tableblock.valign-top {
+ vertical-align: top;
+}
+th.tableblock.valign-middle, td.tableblock.valign-middle {
+ vertical-align: middle;
+}
+th.tableblock.valign-bottom, td.tableblock.valign-bottom {
+ vertical-align: bottom;
+}
+
+
+/*
+ * manpage specific
+ *
+ * */
+
+body.manpage h1 {
+ padding-top: 0.5em;
+ padding-bottom: 0.5em;
+ border-top: 2px solid silver;
+ border-bottom: 2px solid silver;
+}
+body.manpage h2 {
+ border-style: none;
+}
+body.manpage div.sectionbody {
+ margin-left: 3em;
+}
+
+@media print {
+ body.manpage div#toc { display: none; }
+}
diff --git a/antora/.asciidoctor-styles/boot-flatly.css b/antora/.asciidoctor-styles/boot-flatly.css
new file mode 100644
index 0000000000..e98bf9e8ec
--- /dev/null
+++ b/antora/.asciidoctor-styles/boot-flatly.css
@@ -0,0 +1,350 @@
+/* Based on Flatly from Bootswatch (https://bootswatch.com/flatly/) */
+@import url("//fonts.googleapis.com/css?family=Lato:400,700,400italic");
+
+/* document body (contains all content) */
+body {
+ font-family: "Lato", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-size: 15px;
+ line-height: 1.42857143;
+ color: #2c3e50;
+ background-color: #ffffff;
+ margin-left: 10%;
+ margin-right: 10%;
+}
+
+/* document header (contains title etc) */
+#header {
+ width: 100%;
+}
+#header>h1 {
+ border-bottom: 1px solid #ddddd8;
+ padding-bottom: 8px;
+}
+
+/* headings */
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-family: "Lato", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-weight: 400;
+ line-height: 1.1;
+ color: inherit;
+}
+h1,
+h2,
+h3 {
+ margin-top: 21px;
+ margin-bottom: 10.5px;
+}
+h4,
+h5,
+h6 {
+ margin-top: 10.5px;
+ margin-bottom: 10.5px;
+}
+h1 {
+ font-size: 4em;
+/* font-size: 39px; */
+}
+h2 {
+ font-size: 32px;
+}
+h3 {
+ font-size: 26px;
+}
+h4 {
+ font-size: 19px;
+}
+h5 {
+ font-size: 15px;
+}
+h6 {
+ font-size: 13px;
+}
+
+/* plain paragraph text */
+.paragraph {
+/* font-family: sans-serif; */
+ margin: 0 0 10.5px;
+}
+p {
+/* font-family: sans-serif; */
+ margin: 0 0 10.5px;
+}
+
+/* blockquote text */
+.quoteblock {
+ font-style: italic;
+}
+blockquote {
+ padding: 10.5px 21px;
+ margin: 0 0 21px;
+ font-size: 18.75px;
+ border-left: 5px solid #ecf0f1;
+}
+blockquote p:last-child,
+blockquote ul:last-child,
+blockquote ol:last-child {
+ margin-bottom: 0;
+}
+.blockquote-reverse,
+blockquote.pull-right {
+ padding-right: 15px;
+ padding-left: 0;
+ border-right: 5px solid #ecf0f1;
+ border-left: 0;
+ text-align: right;
+}
+.blockquote-reverse footer:before,
+blockquote.pull-right footer:before,
+.blockquote-reverse small:before,
+blockquote.pull-right small:before,
+.blockquote-reverse .small:before,
+blockquote.pull-right .small:before {
+ content: '';
+}
+.blockquote-reverse footer:after,
+blockquote.pull-right footer:after,
+.blockquote-reverse small:after,
+blockquote.pull-right small:after,
+.blockquote-reverse .small:after,
+blockquote.pull-right .small:after {
+ content: '\00A0 \2014';
+}
+
+/* blockquote attribution text */
+.attribution,
+.cite,
+blockquote footer,
+blockquote small,
+blockquote .small {
+ display: block;
+ font-size: 80%;
+ line-height: 1.42857143;
+ color: #b4bcc2;
+}
+.attribution:before,
+blockquote footer:before,
+blockquote small:before,
+blockquote .small:before {
+ content: '\2014 \00A0';
+}
+
+/* unordered list */
+ul, ol {
+ margin-top: 0;
+ margin-bottom: 10.5px;
+}
+ul ul,
+ol ul,
+ul ol,
+ol ol {
+ margin-bottom: 0;
+}
+
+
+/* links */
+a {
+ color: #18bc9c;
+ background-color: transparent;
+ text-decoration: none;
+}
+a:hover,
+a:focus {
+ color: #18bc9c;
+ text-decoration: underline;
+}
+a:focus {
+ outline: 5px auto -webkit-focus-ring-color;
+ outline-offset: -2px;
+}
+
+/* horizontal rules */
+hr {
+ margin-top: 21px;
+ margin-bottom: 21px;
+ border: 0;
+ border-top: 1px solid #ecf0f1;
+}
+
+/* table */
+table {
+ background-color: transparent;
+ width: 100%;
+ max-width: 100%;
+ margin-bottom: 21px;
+ border-collapse: collapse;
+}
+table col[class*="col-"] {
+ position: static;
+ float: none;
+ display: table-column;
+}
+table td[class*="col-"],
+table th[class*="col-"] {
+ position: static;
+ float: none;
+ display: table-cell;
+}
+
+/* table caption */
+caption {
+ padding-top: 8px;
+ padding-bottom: 8px;
+ color: #b4bcc2;
+ text-align: left;
+}
+
+/* table header row */
+thead {
+ border-bottom: 2px solid #ecf0f1;
+}
+
+/* table header cell */
+th {
+ text-align: left;
+ padding-left: 8px;
+}
+
+/* table footer */
+tfoot {
+ color: #807F81;
+ border-top: 1px solid #ecf0f1;
+}
+
+/* table cell */
+td {
+ border-top: 1px solid #ecf0f1;
+}
+td p {
+ margin: auto;
+ padding: 8px;
+}
+
+/* table body */
+tbody > tr:nth-of-type(odd) {
+ background-color: #f9f9f9;
+}
+tbody > tr:hover {
+ background-color: #ecf0f1;
+}
+
+/* inline code */
+code,
+kbd,
+pre,
+samp {
+ font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
+}
+code {
+ padding: 2px 4px;
+ font-size: 90%;
+ color: #c7254e;
+ background-color: #f9f2f4;
+ border-radius: 4px;
+}
+kbd {
+ padding: 2px 4px;
+ font-size: 90%;
+ color: #ffffff;
+ background-color: #333333;
+ border-radius: 0;
+ -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25);
+ box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25);
+}
+kbd kbd {
+ padding: 0;
+ font-size: 100%;
+ font-weight: bold;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+}
+pre {
+ display: block;
+ padding: 10.5px;
+ margin: 0 0 10.5px;
+ font-size: 15px;
+ line-height: 1.42857143;
+ word-break: break-all;
+ word-wrap: break-word;
+ color: #333333;
+ background-color: #f5f5f5;
+ border: 1px solid #cccccc;
+ border-radius: 4px;
+}
+pre code {
+ padding: 0;
+ font-size: inherit;
+ color: inherit;
+ white-space: pre-wrap;
+ background-color: transparent;
+ border-radius: 0;
+}
+
+/* image */
+img {
+ max-width: 100%;
+ vertical-align: middle;
+}
+
+/* footer section */
+#footer {
+ margin-top: 22px;
+ padding: 14px 16px;
+ color: white;
+ background-color: #2c3e50;
+}
+
+/* responsiveness fixes */
+video {
+ max-width: 100%;
+}
+
+/* table of Contents sidebar */
+#toctitle {
+ color: #ffffff;
+}
+
+#toc ul {
+ display: inline;
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+ overflow: hidden;
+}
+
+#toc li {
+ display: block;
+}
+
+#toc a {
+ background-color: #2c3e50;
+ float: left;
+ color: white;
+ text-align: center;
+ padding: 14px 16px;
+ text-decoration: none;
+}
+
+#toc li a:hover {
+ background-color: #1a242f;
+ text-decoration: none;
+}
+
+#toc:after {
+ content: " ";
+ visibility: hidden;
+ display: block;
+ height: 0;
+ clear: both;
+}
+
+@media all and (max-width: 600px) {
+ table {
+ width: 55vw !important;
+ font-size: 3vw;
+ }
+}
(causeway) 04/08: CAUSEWAY-2873: 03-09
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch CAUSEWAY-2873
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit ebb1786b320dbcb542adf69e9a0e882d9f9632f3
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Sat May 25 14:46:45 2024 +0100
CAUSEWAY-2873: 03-09
---
.../petclinic/pages/030-petowner-entity.adoc | 83 ++++++++++++++++------
.../modules/petclinic/pages/100-todo.adoc | 3 +
2 files changed, 65 insertions(+), 21 deletions(-)
diff --git a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
index 383149ad87..529472a39d 100644
--- a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
@@ -517,11 +517,10 @@ public PetOwner updateName( ... ) { ... }
Confirm that prompt is still inline.
* Using a configuration property we can change the default for object actions to use a dialog rather than inline.
-+
-using the Spring boot profile trick from before:
+We'll use the "dev" profile introduced earlier:
+
[source,yaml]
-.application-custom.yaml
+.application-dev.yaml
----
causeway:
viewer:
@@ -529,7 +528,7 @@ causeway:
prompt-style: dialog
----
+
-Remember to activate this new profile (`-Dspring.profiles.active=dev,custom`) and confirm that the `updateName` prompt now uses a sidebar dialog.
+Remember to activate this new profile (`-Dspring.profiles.active=dev`) and confirm that the `updateName` prompt now uses a sidebar dialog.
* We can overide the default dialog style for both service and object actions using further configuration properties.
+
@@ -567,27 +566,21 @@ causeway:
-[#exercise-3-7-derived-name-property]
-== Ex 3.7: Derived name property
-
-The ``PetOwner``'s `firstName` and `lastName` properties are updated using the `updateName` action, but when the action's button is invoked, it only "replaces" the `lastName` property:
-
-image::Owner-updateName-prompt.png[width="400px"]
-
-In this exercise we'll improve the UI by introducing a derived `name` property and then hiding the `firstName` and `lastName`:
+[#exercise-3-9-derived-days-since-last-visit-property]
+== Ex 3.9: Derived 'days since last visit' property
-image::Owner-name.png[width="400px"]
+Not every property has to persisted, nor editable (indeed most properties are not editable).
-When `PetOwner#updateName` is invoked, the prompt we'll want see is:
+For example, it might be useful to calculate the number of days since the pet owner last visited; perhaps for marketing purposes.
-image::Owner-name-updated.png[width="400px"]
+In this exercise we'll see how easy it is to create such a derived property.
=== Solution
[source,bash]
----
-git checkout tags/03-07-derived-PetOwner-name
+git checkout tags/03-09-derived-days-since-last-visit-property
mvn clean install
mvn -pl spring-boot:run
----
@@ -595,18 +588,66 @@ mvn -pl spring-boot:run
=== Tasks
+Locate the `PetOwner` class:
+
+* inject an instance of xref:refguide:applib:index/services/clock/ClockService.adoc[]:
++
+[source,java]
+.PetOwner.java
+----
+@Inject
+@Transient // <.>
+ClockService clockService;
+----
+<.> instructs JPA that this field is not persisted
++
+Note that Apache Causeway allows services to be injected into entities (actually, into pretty much any domain object)
-* Add `getName()` as the derived `name` property:
+* implement `getDaysSinceLastVisit()` method, calculating the number of days since "today".
+
[source,java]
.PetOwner.java
----
-@Transient
-@PropertyLayout(fieldSetId = "name", sequence = "1")
-public String getName() {
- return getFirstName() + " " + getLastName();
+@Property
+@PropertyLayout(fieldSetId = LayoutConstants.FieldSetId.DETAILS, sequence = "3.1") // <.>
+public long getDaysSinceLastVisit() {
+ return Math.abs(ChronoUnit.DAYS.between(getLastVisit(), clockService.getClock().nowAsLocalDate()));
}
----
+<.> positioned just after the `lastVisit` property
+
+* update `PetOwner.columnOrder.txt` to indicate whether this new property should be rendered in standalone tables (returned from finder actions):
++
+[source,text]
+.PetOwner.columnOrder.txt
+----
+name
+telephoneNumber
+emailAddress
+daysSinceLastVisit
+#lastVisit
+#knownAs
+#attachment
+#notes
+#version
+----
+
+* similarly, update `HomePageViewModel#petOwners.columnOrder.txt` to indicate whether this new property should be rendered on the home page:
++
+[source,text]
+.HomePageViewModel#petOwners.columnOrder.txt
+----
+name
+telephoneNumber
+emailAddress
+daysSinceLastVisit
+#lastVisit
+#knownAs
+#attachment
+#notes
+#version
+----
+
* Hide the `lastName` and `firstName` properties, using `@Property(hidden=...)`.
We can also remove the `@PropertyLayout` annotation.
diff --git a/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc b/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc
index b8603a2c3b..66bd2a3312 100644
--- a/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc
@@ -5,3 +5,6 @@ TODO: ideas for future steps:
add validation for telephone number and email address.
+
+
+PetOwner#daysSinceLastVisit could be made into a mixin - eg if these marketing analytics were the responsibility of some other module.
(causeway) 05/08: CAUSEWAY-2873: removes old exercise
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch CAUSEWAY-2873
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit a881ffbad69fcc862ec739361164e4cec7d21978
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Sat May 25 14:48:11 2024 +0100
CAUSEWAY-2873: removes old exercise
---
.../petclinic/pages/030-petowner-entity.adoc | 144 ---------------------
.../modules/petclinic/pages/100-todo.adoc | 5 +
2 files changed, 5 insertions(+), 144 deletions(-)
diff --git a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
index 529472a39d..f0b01273b5 100644
--- a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
@@ -649,150 +649,6 @@ daysSinceLastVisit
----
-* Hide the `lastName` and `firstName` properties, using `@Property(hidden=...)`.
-We can also remove the `@PropertyLayout` annotation.
-+
-[source,java]
-.PetOwner.java
-----
-@LastName
-@Column(length = LastName.MAX_LEN, nullable = false)
-@Getter @Setter @ToString.Include
-@Property(hidden = Where.EVERYWHERE)
-private String lastName;
-
-@FirstName
-@Column(length = FirstName.MAX_LEN, nullable = true)
-@Getter @Setter @ToString.Include
-@Property(hidden = Where.EVERYWHERE)
-private String firstName;
-----
-
-
-* Update the `PetOwner#updateName` to associate with the new `name` property:
-+
-[source,xml]
-----
-@ActionLayout(associateWith = "name",)
-public PetOwner updateName( ... ) {}
-----
-
-
-Run the application and check that it behaves as you expect.
-
-However, if you now try to build the app (`mvn clean install`) then you'll hit test errors, because we have changed the visibility of the `lastName` and `firstName` properties.
-
-We will be looking at tests later on, so if you want to just comment out the failing tests, then do that.
-Alternatively, here are the changes that need to be made:
-
-* update the `PetOwner_IntegTest#name` nested static test class:
-+
-[source,java]
-.PetOwner_IntegTest.java
-----
-@Nested
-public static class name extends PetOwner_IntegTest {
-
- @Test
- public void accessible() {
- // when
- final String name = wrap(petOwner).getName(); // <.>
-
- // then
- assertThat(name).isEqualTo(petOwner.getLastName());
- }
-
- // <.>
-}
-----
-<.> change this line from `getLastName()` to `getName()`
-<.> delete the 'editable' test
-
-* add a new `PetOwner_IntegTest#lastName` nested static test class to check that the `lastName` property can no longer be viewed:
-+
-[source,java]
-.PetOwner_IntegTest.java
-----
-@Nested
-public static class lastName extends PetOwner_IntegTest {
-
- @Test
- public void not_accessible() {
- // expect
- assertThrows(HiddenException.class, ()->{
-
- // when
- wrap(petOwner).getLastName();
- });
- }
-}
-----
-+
-This asserts that the `lastName` property cannot be viewed.
-
-* add a new `PetOwner_IntegTest#firstName` nested static test class to check that the `firstName` property can no longer be viewed.
-+
-[source,java]
-.PetOwner_IntegTest.java
-----
-@Nested
-public static class firstName extends PetOwner_IntegTest {
-
- @Test
- public void not_accessible() {
- // expect
- assertThrows(HiddenException.class, ()->{
-
- // when
- wrap(petOwner).getFirstName();
- });
- }
-}
-----
-
-* update the `PetOwner_IntegTest#updateName` nested static test class, specifically the assertion:
-+
-[source,java]
-.PetOwner_IntegTest.java
-----
-@Nested
-public static class updateName extends PetOwner_IntegTest {
-
-
- @Test
- public void can_be_updated_directly() {
-
- // when
- wrap(petOwner).updateName("McAdam", "Adam"); // <.>
- transactionService.flushTransaction();
-
- // then
- assertThat(petOwner.getLastName()).isEqualTo("McAdam"); // <.>
- assertThat(petOwner.getFirstName()).isEqualTo("Adam"); // <2>
- }
- //...
-}
-----
-<.> provide both `lastName` and `firstName` parameters
-<.> assert on both properties.
-Note that the `petOwner` object cannot be "wrapped".
-
-In case you are wondering, the wrap method is a call to xref:refguide:applib:index/services/wrapper/WrapperFactory.adoc[WrapperFactory], which provides a proxy to the object.
-This proxy emulates the UI, in this case enforcing the "hidden" rule by throwing an exception if it would not be visible.
-For this test, we _want_ to peek under the covers to check the direct state of the entity, therefore we don't wrap the object.
-
-* also update the `Smoke_IntegTest`:
-+
-[source,java]
-.Smoke_IntegTest.java
-----
-...
-assertThat(wrap(fred).getName()).isEqualTo("Freddy"); // <.>
-...
-----
-<.> previously was "wrap(fred).getLastName().
-
-
[#exercise-3-8-add-other-properties-for-petowner]
== Ex 3.8: Add other properties for PetOwner
diff --git a/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc b/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc
index 66bd2a3312..6ebd2b2b7f 100644
--- a/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc
@@ -8,3 +8,8 @@ add validation for telephone number and email address.
PetOwner#daysSinceLastVisit could be made into a mixin - eg if these marketing analytics were the responsibility of some other module.
+
+
+
+hidden properties
+
(causeway) 02/08: CAUSEWAY-2873: 03-06
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch CAUSEWAY-2873
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit fa90b7fa4424bd70117ee9beb6a8d9e0aa1a57b3
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Sat May 25 07:37:23 2024 +0100
CAUSEWAY-2873: 03-06
---
.../images/03-06/home-page-default-columns.png | Bin 0 -> 21722 bytes
...ag-plantuml-md5-41d7321e937c89d99e72b59cadcbe005.png | Bin 0 -> 37003 bytes
...ag-plantuml-md5-5c0ff86629ce9ff23e5d52305493d5c6.svg | 1 +
...ag-plantuml-md5-7e47437805fde198d7791595949fcf17.png | Bin 0 -> 5096 bytes
...ag-plantuml-md5-bc5fc3dd9c919d1677af89bec5416f29.svg | 1 +
.../modules/petclinic/pages/030-petowner-entity.adoc | 7 ++++++-
6 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/antora/components/tutorials/modules/petclinic/images/03-06/home-page-default-columns.png b/antora/components/tutorials/modules/petclinic/images/03-06/home-page-default-columns.png
new file mode 100644
index 0000000000..dc0d1f0aa2
Binary files /dev/null and b/antora/components/tutorials/modules/petclinic/images/03-06/home-page-default-columns.png differ
diff --git a/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-41d7321e937c89d99e72b59cadcbe005.png b/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-41d7321e937c89d99e72b59cadcbe005.png
new file mode 100644
index 0000000000..9b948f4bae
Binary files /dev/null and b/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-41d7321e937c89d99e72b59cadcbe005.png differ
diff --git a/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-5c0ff86629ce9ff23e5d52305493d5c6.svg b/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-5c0ff86629ce9ff23e5d52305493d5c6.svg
new file mode 100644
index 0000000000..6f3b8ba884
--- /dev/null
+++ b/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-5c0ff86629ce9ff23e5d52305493d5c6.svg
@@ -0,0 +1 @@
+<?xml version='1.0' encoding='US-ASCII' standalone='no'?><svg contentStyleType='text/css' height='161px' preserveAspectRatio='none' style='width:141px;height:161px;background:#FFFFFF;' version='1.1' viewBox='0 0 141 161' width='141px' xmlns:xlink='http://www.w3.org/1999/xlink' xmlns='http://www.w3.org/2000/svg' zoomAndPan='magnify'><defs/><g><g id='elem_Owner'><rect codeLine='4' fill='#F1F1F1' height='140.7144' id='Owner' rx='2.5' ry='2.5' style='stroke:#181818;stroke-width:0.5;' width=' [...]
\ No newline at end of file
diff --git a/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-7e47437805fde198d7791595949fcf17.png b/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-7e47437805fde198d7791595949fcf17.png
new file mode 100644
index 0000000000..12035b77b0
Binary files /dev/null and b/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-7e47437805fde198d7791595949fcf17.png differ
diff --git a/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-bc5fc3dd9c919d1677af89bec5416f29.svg b/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-bc5fc3dd9c919d1677af89bec5416f29.svg
new file mode 100644
index 0000000000..a9963efc83
--- /dev/null
+++ b/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-bc5fc3dd9c919d1677af89bec5416f29.svg
@@ -0,0 +1 @@
+<?xml version='1.0' encoding='US-ASCII' standalone='no'?><svg contentStyleType='text/css' height='441px' preserveAspectRatio='none' style='width:520px;height:441px;background:#FFFFFF;' version='1.1' viewBox='0 0 520 441' width='520px' xmlns:xlink='http://www.w3.org/1999/xlink' xmlns='http://www.w3.org/2000/svg' zoomAndPan='magnify'><defs/><g><g id='cluster_pets'><path d='M8.5,6 L38.5,6 A3.75,3.75 0 0 1 41,8.5 L48,28.9429 L282.5,28.9429 A2.5,2.5 0 0 1 285,31.4429 L285,431.5 A2.5,2.5 0 0 1 [...]
\ No newline at end of file
diff --git a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
index 7e3eec8025..4d46d3bc39 100644
--- a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
@@ -12,7 +12,6 @@ In this exercise, we'll add a new property, `knownAs`, to capture the ``PetOwner
We'll also update the fixture script that sets up data to something more realistic.
-
=== Solution
[source,bash]
@@ -384,8 +383,14 @@ The positioning of the collection is also specified in the corresponding layout
By default this will show all of the properties of `PetOwner`.
+image::03-06/home-page-default-columns.png[]
+
+
We can change this by creating a file `HomePageViewModel#petOwners.columnOrder.txt`, alongside the `HomePageViewModel`:
+
+Instead
+
[source,txt]
.HomePageViewModel#petOwners.columnOrder.txt
----
(causeway) 03/08: CAUSEWAY-2873: completes 03-08
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch CAUSEWAY-2873
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit 0a8829e41046c19ed65c133d5cc82371d72806aa
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Sat May 25 14:12:50 2024 +0100
CAUSEWAY-2873: completes 03-08
---
.../images/03-07/new-parameters-are-optional.png | Bin 0 -> 8602 bytes
...antuml-md5-41d7321e937c89d99e72b59cadcbe005.png | Bin 37003 -> 0 bytes
...antuml-md5-5c0ff86629ce9ff23e5d52305493d5c6.svg | 2 +-
...antuml-md5-7e47437805fde198d7791595949fcf17.png | Bin 5096 -> 4464 bytes
...antuml-md5-bc5fc3dd9c919d1677af89bec5416f29.svg | 1 -
.../petclinic/pages/030-petowner-entity.adoc | 102 +++++++++++++--------
.../modules/petclinic/pages/100-todo.adoc | 3 +
7 files changed, 70 insertions(+), 38 deletions(-)
diff --git a/antora/components/tutorials/modules/petclinic/images/03-07/new-parameters-are-optional.png b/antora/components/tutorials/modules/petclinic/images/03-07/new-parameters-are-optional.png
new file mode 100644
index 0000000000..06bedd5db0
Binary files /dev/null and b/antora/components/tutorials/modules/petclinic/images/03-07/new-parameters-are-optional.png differ
diff --git a/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-41d7321e937c89d99e72b59cadcbe005.png b/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-41d7321e937c89d99e72b59cadcbe005.png
deleted file mode 100644
index 9b948f4bae..0000000000
Binary files a/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-41d7321e937c89d99e72b59cadcbe005.png and /dev/null differ
diff --git a/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-5c0ff86629ce9ff23e5d52305493d5c6.svg b/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-5c0ff86629ce9ff23e5d52305493d5c6.svg
index 6f3b8ba884..50b30fc348 100644
--- a/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-5c0ff86629ce9ff23e5d52305493d5c6.svg
+++ b/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-5c0ff86629ce9ff23e5d52305493d5c6.svg
@@ -1 +1 @@
-<?xml version='1.0' encoding='US-ASCII' standalone='no'?><svg contentStyleType='text/css' height='161px' preserveAspectRatio='none' style='width:141px;height:161px;background:#FFFFFF;' version='1.1' viewBox='0 0 141 161' width='141px' xmlns:xlink='http://www.w3.org/1999/xlink' xmlns='http://www.w3.org/2000/svg' zoomAndPan='magnify'><defs/><g><g id='elem_Owner'><rect codeLine='4' fill='#F1F1F1' height='140.7144' id='Owner' rx='2.5' ry='2.5' style='stroke:#181818;stroke-width:0.5;' width=' [...]
\ No newline at end of file
+<?xml version='1.0' encoding='US-ASCII' standalone='no'?><svg contentStyleType='text/css' height='170px' preserveAspectRatio='none' style='width:136px;height:170px;background:#FFFFFF;' version='1.1' viewBox='0 0 136 170' width='136px' xmlns:xlink='http://www.w3.org/1999/xlink' xmlns='http://www.w3.org/2000/svg' zoomAndPan='magnify'><defs/><g><g id='elem_Owner'><rect codeLine='4' fill='#F1F1F1' height='149.1055' id='Owner' rx='2.5' ry='2.5' style='stroke:#181818;stroke-width:0.5;' width=' [...]
\ No newline at end of file
diff --git a/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-7e47437805fde198d7791595949fcf17.png b/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-7e47437805fde198d7791595949fcf17.png
index 12035b77b0..d4fdb061d9 100644
Binary files a/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-7e47437805fde198d7791595949fcf17.png and b/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-7e47437805fde198d7791595949fcf17.png differ
diff --git a/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-bc5fc3dd9c919d1677af89bec5416f29.svg b/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-bc5fc3dd9c919d1677af89bec5416f29.svg
deleted file mode 100644
index a9963efc83..0000000000
--- a/antora/components/tutorials/modules/petclinic/images/diag-plantuml-md5-bc5fc3dd9c919d1677af89bec5416f29.svg
+++ /dev/null
@@ -1 +0,0 @@
-<?xml version='1.0' encoding='US-ASCII' standalone='no'?><svg contentStyleType='text/css' height='441px' preserveAspectRatio='none' style='width:520px;height:441px;background:#FFFFFF;' version='1.1' viewBox='0 0 520 441' width='520px' xmlns:xlink='http://www.w3.org/1999/xlink' xmlns='http://www.w3.org/2000/svg' zoomAndPan='magnify'><defs/><g><g id='cluster_pets'><path d='M8.5,6 L38.5,6 A3.75,3.75 0 0 1 41,8.5 L48,28.9429 L282.5,28.9429 A2.5,2.5 0 0 1 285,31.4429 L285,431.5 A2.5,2.5 0 0 1 [...]
\ No newline at end of file
diff --git a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
index 4d46d3bc39..383149ad87 100644
--- a/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/030-petowner-entity.adoc
@@ -186,12 +186,13 @@ Now add the two new properties:
[source,java]
.PetOwner.java
----
-@Column(length = 40, nullable = true, name = "telephoneNumber")
+@Column(length = 40, nullable = true, name = "telephoneNumber") // <.>
@Getter @Setter
@Property(editing = Editing.ENABLED)
@PropertyLayout(fieldSetId = "contact", sequence = "1.1") // <.>
private String telephoneNumber;
----
+<.> The JPA `@Column` annotation indicates that the property is optional in the database; Causeway also understands this for the domain layer.
<.> This places the property in a new "contact" fieldset group; we'll define that below
* and add the `emailAddress` property:
@@ -339,7 +340,7 @@ When you now run the application you should now find that there are 10 `PetOwner
[#exercise-3-6-update-home-page-to-show-PetOwners]
-== Ex 3.5: Update Home Page to show PetOwners
+== Ex 3.6: Update Home Page to show PetOwners
Every Causeway app can nominate a home page, basically a xref:userguide::view-models.adoc[view model] that's been annotated with xref:refguide:applib:index/annotation/HomePage.adoc[].
Currently the home page for our app is the one we inherited from the starter app, showing a list of ``SimpleObject``s.
@@ -386,10 +387,7 @@ By default this will show all of the properties of `PetOwner`.
image::03-06/home-page-default-columns.png[]
-We can change this by creating a file `HomePageViewModel#petOwners.columnOrder.txt`, alongside the `HomePageViewModel`:
-
-
-Instead
+We can change this by creating a file `HomePageViewModel#petOwners.columnOrder.txt`, alongside the `HomePageViewModel`.
[source,txt]
.HomePageViewModel#petOwners.columnOrder.txt
@@ -405,39 +403,29 @@ emailAddress
TIP: the action "Download .columnOrder.txt files (ZIP)" (available only when prototyping) provides an easy way to obtain this file; you can then update as required.
+TIP: You can config your IDE so that any changes made to this file are picked up automatically without having to restart the app.
+If using IntelliJ for example, use "Reload Changed Classes" action.
-----
-----
-----
-----
-
-----
-----
+[#exercise-3-7-modify-the-menu-action-to-create-petowners]
+== Ex 3.7: Modify the menu action to create PetOwners
-TODO: change the home page to list the pet owners by default.
+If we want to create a new `PetOwner` and specify additional details, at the moment it's a two stage process: create the `PetOwner` (using menu:PetOwners[create]), then set the additional details afterwards.
-*UP TO HERE*
-
-[#exercise-3-4-modify-the-menu-action-to-create-petowners]
-== Ex 3.4: Modify the menu action to create PetOwners
-
-If we want to create a new `PetOwner` and provide their `firstName`, at the moment it's a two stage process: create the `PetOwner` (using `PetOwners#create` action from the menu), then update their name (using the `updateName` action that we just looked at).
-
-In this exercise we'll simplify that workflow by allowing the `firstName` to optionally be specified during the initial create.
+In this exercise we'll simplify that workflow by allowing the additional details to optionally be specified during the create.
=== Solution
[source,bash]
----
-git checkout tags/03-04-modifies-PetOwners-create-action
+git checkout tags/03-07-modifies-PetOwners-create-action
mvn clean install
mvn -pl spring-boot:run
----
=== Tasks
-* update `Orders#create` action, so that the end user can specify a `firstName` when creating a new `PetOwner`:
+* update `PetOwners#create()` method, to allow the additional details to optionally be specified:
+
[source,java]
.PetOwners.java
@@ -445,30 +433,48 @@ mvn -pl spring-boot:run
@Action(semantics = SemanticsOf.NON_IDEMPOTENT)
@ActionLayout(promptStyle = PromptStyle.DIALOG_SIDEBAR)
public PetOwner create(
- @LastName final String lastName,
- @FirstName final String firstName) {
- return repositoryService.persist(PetOwner.withName(lastName, firstName));
+ @Name final String name,
+ @Parameter(maxLength = 40, optionality = Optionality.OPTIONAL)
+ final String knownAs,
+ @Parameter(maxLength = 40, optionality = Optionality.OPTIONAL)
+ final String telephoneNumber,
+ @Parameter(maxLength = 40, optionality = Optionality.OPTIONAL)
+ final String emailAddress) {
+ final var petOwner = PetOwner.withName(name);
+ petOwner.setKnownAs(knownAs);
+ petOwner.setTelephoneNumber(telephoneNumber);
+ petOwner.setEmailAddress(emailAddress);
+ return repositoryService.persist(petOwner);
}
----
+* also update `PetOwner_persona.Builder` fixture script, passing in `null` for the new parameters:
++
+[source,java]
+.PetOwner_persona.java
+----
+val petOwner = PetOwners.create(persona.name, null, null, null);
+----
+When you run the app, confirm that only the `name` parameter is optional:
+image::03-07/new-parameters-are-optional.png[width=80%]
-[#exercise-3-6-prompt-styles]
-== Ex 3.6: Prompt styles
+[#exercise-3-8-prompt-styles]
+== Ex 3.8: Prompt styles
The framework provides many ways to customise the UI, either through the layout files or using the `@XxxLayout` annotations.
Default UI conventions can also be specified using the `application.yml` configuration file.
-In this exercise we'll change the prompt style for both a service (menu) action, ie `PetOwners#create`, and an object action, ie `PetOwner#updateName`.
+In this exercise we'll change the prompt style for both a service (menu) action, ie menu::PetOwners[create], and an object action, ie `PetOwner#updateName`.
=== Solution
[source,bash]
----
-git checkout tags/03-06-prompt-styles
+git checkout tags/03-08-prompt-styles
mvn clean install
mvn -pl spring-boot:run
----
@@ -479,12 +485,36 @@ mvn -pl spring-boot:run
* Service (menu) actions are always shown in a dialog, of which there are two styles: modal prompt, or sidebar.
If not specified explicitly, they will default to dialog modal.
+
-Therefore remove the `@ActionLayout(promptStyle)` for `PetOwners#create` and confirm that the dialog is now shown as a modal prompt.
+Therefore, remove the `@ActionLayout(promptStyle)` for `PetOwners#create`
++
+[source,java]
+.PetOwners.java
+----
+@Action(semantics = SemanticsOf.NON_IDEMPOTENT)
+// @ActionLayout(promptStyle = PromptStyle.DIALOG_SIDEBAR)
+public PetOwner create( ... ) { ... }
+----
++
+Confirm that the dialog is now shown as a modal prompt.
* Object actions can be shown either inline or in a dialog, but default to inline.
If forced to use a dialog, then they default to a sidebar prompt rather than a modal prompt.
+
-Therefore remove the `@ActionLayout(promptStyle)` for `PetOwner#updateName` and confirm that prompt is still inline.
+Remove the `@ActionLayout(promptStyle)` for `PetOwner#updateName`:
+
+[source,java]
+.PetOwner.java
+----
+@Action( ... )
+@ActionLayout(
+ ...
+ // promptStyle = PromptStyle.INLINE,
+ ...
+)
+public PetOwner updateName( ... ) { ... }
+----
++
+Confirm that prompt is still inline.
* Using a configuration property we can change the default for object actions to use a dialog rather than inline.
+
@@ -506,7 +536,7 @@ Remember to activate this new profile (`-Dspring.profiles.active=dev,custom`) an
Switch the defaults so that service actions prefer to use a sidebar dialog, while object actions would use a modal dialog:
+
[source,yaml]
-.application-custom.yaml
+.application-dev.yaml
----
causeway:
viewer:
@@ -516,14 +546,14 @@ causeway:
dialog-mode-for-menu: sidebar
----
-* Optional: now use `@ActionLayout(promptStyle=...)` to override these defaults.
+* Optional: use `@ActionLayout(promptStyle=...)` to override these defaults.
+
Be aware that "inline" makes no sense/is not supported for service actions.
* Finish off the exercises by setting up these defaults to retain the original behaviour:
+
[source,yaml]
-.application-custom.yaml
+.application-dev.yaml
----
causeway:
viewer:
diff --git a/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc b/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc
index a669ffc86b..b8603a2c3b 100644
--- a/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc
+++ b/antora/components/tutorials/modules/petclinic/pages/100-todo.adoc
@@ -2,3 +2,6 @@
TODO: ideas for future steps:
- introduce a meta-annotation
- introduce a custom type for these
+
+
+add validation for telephone number and email address.