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
VisitRepositoryto list all visits after a certain time:public interface VisitRepository extends Repository<Visit, Integer> { List<Visit> findByVisitAtAfter(LocalDateTime visitAt); // ... } -
modify
HomePageViewModelto show the currentPetOwners and anyVisits 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 Visitss. -
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.txtfiles 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
bookVisitaction 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 Visit3 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 PetOwners based upon their name.5 Finds the Pets 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"; }