View models
So far the application consists only of domain entities and domain services. However, the framework also supports view models.
A classic use case is to provide a home page or dashboard, but they are also used to represent certain specific business processes when there isn’t necessarily a domain entity required to track the state of the process itself. Some real-world examples include importing/exporting spreadsheets periodically (eg changes to indexation rates), or generating extracts such as a payment file or summary PDF for a quarterly invoice run.
Ex 8.1: Extend the Home Page.
In this exercise we’ll extend the home page by displaying additional data in new collections.
Solution
git checkout tags/3.1.0/08-01-home-page-additional-collections
mvn clean install
mvn -pl webapp spring-boot:run -Dspring-boot.run.jvmArguments="-Dspring.profiles.active=dev"
Tasks
-
update
VisitRepository
to list all visits after a certain time:public interface VisitRepository extends Repository<Visit, Integer> { List<Visit> findByVisitAtAfter(LocalDateTime visitAt); // ... }
-
modify
HomePageViewModel
to show the currentPetOwner
s and anyVisit
s in the future:@Named(SimpleModule.NAMESPACE + ".HomePageViewModel") @DomainObject(nature = Nature.VIEW_MODEL) (1) @HomePage (2) @DomainObjectLayout() public class HomePageViewModel { // ... @Collection @CollectionLayout(tableDecorator = TableDecorator.DatatablesNet.class) public List<Visit> getFutureVisits() { (3) LocalDateTime now = clockService.getClock().nowAsLocalDateTime(); return visitRepository.findByVisitAtAfter(now); } @Inject ClockService clockService; @Inject VisitRepository visitRepository; }
1 indicates that this is a view model. Causeway provides several ways of implementing view models; this is the most straightforward. 2 exactly one view model can be annotated as the @HomePage 3 new collection returning future Visits
s. -
update the
HomePageViewModel.layout.xml
.Here it is in its entirety:
HomePageViewModel.layout.xml<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <bs3:grid xsi:schemaLocation="https://causeway.apache.org/applib/layout/component https://causeway.apache.org/applib/layout/component/component.xsd https://causeway.apache.org/applib/layout/grid/bootstrap3 https://causeway.apache.org/applib/layout/grid/bootstrap3/bootstrap3.xsd" xmlns:cpt="https://causeway.apache.org/applib/layout/component" xmlns:bs3="https://causeway.apache.org/applib/layout/grid/bootstrap3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <bs3:row> <bs3:col span="12"> <bs3:row> <bs3:col span="12" unreferencedActions="true"> <cpt:domainObject/> <cpt:action id="clearHints" hidden="EVERYWHERE"/> <cpt:action id="impersonate" hidden="EVERYWHERE"/> <cpt:action id="impersonateWithRoles" hidden="EVERYWHERE"/> <cpt:action id="stopImpersonating" hidden="EVERYWHERE"/> <cpt:action id="downloadLayoutXml" hidden="EVERYWHERE"/> <cpt:action id="inspectMetamodel" hidden="EVERYWHERE"/> <cpt:action id="rebuildMetamodel" hidden="EVERYWHERE"/> <cpt:action id="downloadMetamodelXml" hidden="EVERYWHERE"/> <cpt:action id="openRestApi" hidden="EVERYWHERE"/> </bs3:col> </bs3:row> </bs3:col> <bs3:col span="6" unreferencedCollections="true"> <bs3:row> <bs3:col span="12"> <cpt:collection id="petOwners" defaultView="table"/> </bs3:col> </bs3:row> </bs3:col> <bs3:col span="6"> <bs3:row> <bs3:col span="12"> <cpt:collection id="futureVisits" defaultView="table"/> </bs3:col> </bs3:row> </bs3:col> </bs3:row> <bs3:row> <bs3:col span="0"> <cpt:fieldSet name="General" id="general" unreferencedProperties="true"/> </bs3:col> </bs3:row> </bs3:grid>
-
add
columnOrder.txt
files for the new collection:HomePageViewModel#futureVisits.columnOrder.txtpet visitAt #version
Ex 8.2: Add a convenience action
View models can have behaviour (actions), the same as entities.
In this exercise we’ll extend the home page by providing a convenience action to book a Visit
for any Pet
of any PetOwner
.
Solution
git checkout tags/3.1.0/08-02-home-page-bookVisit-convenience-action
mvn clean install
mvn -pl webapp spring-boot:run -Dspring-boot.run.jvmArguments="-Dspring.profiles.active=dev"
Tasks
-
add a new finder to
VisitRepository
:VisitRepository.javaVisit findByPetAndVisitAt(Pet pet, LocalDateTime visitAt);
-
create a
bookVisit
action forHomePageViewModel
, as a mixin:HomePageViewModel_bookVisit.java@Action (1) @ActionLayout(associateWith = "futureVisits") @RequiredArgsConstructor public class HomePageViewModel_bookVisit { final HomePageViewModel homePageViewModel; @MemberSupport public Object act( PetOwner petOwner, Pet pet, LocalDateTime visitAt, boolean showVisit) { (2) wrapperFactory.wrapMixin(PetOwner_bookVisit.class, petOwner).act(pet, visitAt); (3) if (showVisit) { return visitRepository.findByPetAndVisitAt(pet, visitAt); } return homePageViewModel; } @MemberSupport public List<PetOwner> autoComplete0Act(final String lastName) { (4) return petOwnerRepository.findByNameContaining(lastName); } @MemberSupport public Set<Pet> choices1Act(PetOwner petOwner) { (5) if(petOwner == null) { return Collections.emptySet(); } return petOwner.getPets(); } @MemberSupport public LocalDateTime default2Act(PetOwner petOwner, Pet pet) { (6) if(petOwner == null || pet == null) { return null; } return factoryService.mixin(PetOwner_bookVisit.class, petOwner).default1Act(); } @MemberSupport public String validate2Act(PetOwner petOwner, Pet pet, LocalDateTime visitAt) { (7) return factoryService.mixin(PetOwner_bookVisit.class, petOwner).validate1Act(visitAt); } @Inject VisitRepository visitRepository; @Inject PetOwnerRepository petOwnerRepository; @Inject WrapperFactory wrapperFactory; @Inject FactoryService factoryService; }
1 declares this class as a mixin action. 2 cosmetic flag to control the UI; either remain at the home page or navigate to the newly created Visit
3 use the WrapperFactory to delegate to the original behaviour "as if" through the UI. If additional business rules were added to that delegate, then the mistake would be detected. 4 Uses an autoComplete supporting method to look up matching PetOwner
s based upon their name.5 Finds the Pet
s owned by thePetOwner
, once selected.6 Computes a default for the 2nd parameter, once the first two are selected. 7 surfaces (some of) the business rules of the delegate mixin. -
update the title of
HomePageViewModel
:HomePageViewModel.layout.xml@ObjectSupport public String title() { return getPetOwners().size() + " pet owners, " + getFutureVisits() + " future visits"; }