= View models

:Notice: Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at. http://www.apache.org/licenses/LICENSE-2.0 . Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR  CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

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.



[#exercise-8-1-extend-the-home-page]
== Ex 8.1: Extend the Home Page.

In this exercise we'll extend the home page by displaying additional data in new collections.


=== Solution

[source,bash,subs="attributes+"]
----
git checkout tags/{tag-version}/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:
+
[source,java]
----
public interface VisitRepository extends Repository<Visit, Integer> {

    List<Visit> findByVisitAtAfter(LocalDateTime visitAt);

    // ...
}
----

* modify `HomePageViewModel` to show the current ``PetOwner``s and any ``Visit``s in the future:
+
[source,java]
----
@Named(SimpleModule.NAMESPACE + ".HomePageViewModel")
@DomainObject(nature = Nature.VIEW_MODEL)                                   // <.>
@HomePage                                                                   // <.>
@DomainObjectLayout()
public class HomePageViewModel {

    // ...

    @Collection
    @CollectionLayout(tableDecorator = TableDecorator.DatatablesNet.class)
    public List<Visit> getFutureVisits() {                                  // <.>
        LocalDateTime now = clockService.getClock().nowAsLocalDateTime();
        return visitRepository.findByVisitAtAfter(now);
    }

    @Inject ClockService clockService;
    @Inject VisitRepository visitRepository;

}
----
<.> indicates that this is a xref:userguide::view-models.adoc[view model].
Causeway provides several ways of implementing view models; this is the most straightforward.
<.> exactly one view model can be annotated as the xref:refguide:applib:index/annotation/HomePage.adoc[@HomePage]
<.> new collection returning future ``Visits``s.

* update the `HomePageViewModel.layout.xml`.
+
Here it is in its entirety:
+
[source,xml]
.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:
+
[source,text]
.HomePageViewModel#futureVisits.columnOrder.txt
----
pet
visitAt
#version
----


[#exercise-8-2-add-a-convenience-action]
== 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

[source,bash,subs="attributes+"]
----
git checkout tags/{tag-version}/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`:
+
[source,java]
.VisitRepository.java
----
Visit findByPetAndVisitAt(Pet pet, LocalDateTime visitAt);
----

* create a `bookVisit` action for `HomePageViewModel`, as a mixin:
+
[source,java]
.HomePageViewModel_bookVisit.java
----
@Action                                                                                 // <.>
@ActionLayout(associateWith = "futureVisits")
@RequiredArgsConstructor
public class HomePageViewModel_bookVisit {

    final HomePageViewModel homePageViewModel;

    @MemberSupport
    public Object act(
            PetOwner petOwner, Pet pet, LocalDateTime visitAt,
            boolean showVisit) {                                                        // <.>
        wrapperFactory.wrapMixin(PetOwner_bookVisit.class, petOwner).act(pet, visitAt); // <.>
        if (showVisit) {
            return visitRepository.findByPetAndVisitAt(pet, visitAt);
        }
        return homePageViewModel;
    }
    @MemberSupport
    public List<PetOwner> autoComplete0Act(final String lastName) {                     // <.>
        return petOwnerRepository.findByNameContaining(lastName);
    }
    @MemberSupport
    public Set<Pet> choices1Act(PetOwner petOwner) {                                   // <.>
        if(petOwner == null) {
            return Collections.emptySet();
        }
        return petOwner.getPets();
    }
    @MemberSupport
    public LocalDateTime default2Act(PetOwner petOwner, Pet pet) {                      // <.>
        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) {     // <.>
        return factoryService.mixin(PetOwner_bookVisit.class, petOwner).validate1Act(visitAt);
    }

    @Inject VisitRepository visitRepository;
    @Inject PetOwnerRepository petOwnerRepository;
    @Inject WrapperFactory wrapperFactory;
    @Inject FactoryService factoryService;
}
----
<.> declares this class as a mixin action.
<.> cosmetic flag to control the UI; either remain at the home page or navigate to the newly created `Visit`
<.> use the xref:refguide:applib:index/services/wrapper/WrapperFactory.adoc[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.
<.> Uses an xref:refguide:applib-methods:prefixes.adoc#autoComplete[autoComplete] supporting method to look up matching ``PetOwner``s based upon their name.
<.> Finds the ``Pet``s owned by the `PetOwner`, once selected.
<.> Computes a default for the 2^nd^ parameter, once the first two are selected.
<.> surfaces (some of) the business rules of the delegate mixin.

* update the title of `HomePageViewModel`:
+
[source,xml]
.HomePageViewModel.layout.xml
----
@ObjectSupport public String title() {
    return getPetOwners().size() + " pet owners, " +
           getFutureVisits() + " future visits";
}
----

