You can calculate a purchase date from ownership history and edit sale dates directly in a Turnkey grid; this chapter is for Bootcamp learners building the Person and Car application.
This chapter introduces derived attributesâattributes calculated from other model data rather than storedâand the settings required to edit cells in a ViewModel grid in Turnkey.
Continue from Chapter 6. When you finish, continue with Chapter 8.
What you will build
You will improve the ownership-history model and Person ViewModel so that:
- The association end is renamed from
CarsIUsedToOwnto the clearerCarsPersonUsedToOwn. - Each
HistoricOwnershiphas a derivedDatePurchasedvalue. - A user can edit
DateSoldin theHistoricOwnershipGrid. - The calculated purchase date updates when the relevant sale date becomes available.
- A Person can select a current car through a single-link selector.
Rename the ownership association safely
The name CarsIUsedToOwn is ambiguous because âIâ does not identify the person at the other end of the association. Rename it to CarsPersonUsedToOwn.
- Select the association end currently named
CarsIUsedToOwn. - Do not rename it by typing directly in the association-end name field. A direct change causes references to the old name to fail validation.
- Right-click the association end and choose Rename.
- Enter
CarsPersonUsedToOwnas the new name. - Select Set former name. Use this option whenever you use Rename so MDriven can retain information needed when a later database change script is created.
- Click Toggle Selected to apply the rename.
- Save the model. Saving runs error checking; verify that the model is error-free.
Document the ownership rule
Add a note to the diagram so the calculation has an explicit business rule.
- Right-click the diagram and choose Add note.
- Double-click the note and enter:
We will assume that a person always has 1 car at a time and that when they sell the first car, that person immediately gets a new car on the same day.
- Close the note, resize it, and place it near the ownership model.
For example, if a person sells one car on 2024-06-01, the next ownership record can derive its purchase date as 2024-06-01.
Add a derived DatePurchased attribute
A derived attribute is calculated from an expression. Unlike a persistent attribute, it is not saved as a separate database value.
- Add an attribute named
DatePurchasedto classHistoricOwnership. - Set its type to
DateTime?. - Select
DatePurchasedand use the Property Inspector to change AttributeMode fromPersistenttoDerived. - In the Property Inspector, find DerivationOcl and click the button with three dots to open the OCL editor.
Build the derivation expression
The calculation needs the personâs ownership records in DateSold order. The expression first creates an alias named listinorder with let. It then finds the current HistoricOwnership in that ordered list and retrieves the following ownership recordâs DateSold value.
Enter this expression:
let listinorder=self.Person.HistoricOwnership->orderby(h|h.DateSold) in
(
listinorder->at0(listinorder->indexof0(self)+1)
).DateSold
The let construct creates a reference (an alias) to the ordered collection; it does not define a stored variable. The expression inside the parentheses returns a HistoricOwnership object. Appending .DateSold returns the DateTime? value required by DatePurchased.
Handle the end-of-list case
The expression must also handle an ownership record for which no following record exists. In that case, indexof0(self)+1 cannot identify a valid item, and the derived value must remain null rather than attempting an invalid lookup.
- Test the derivation with multiple ownership rows after completing the grid steps below.
- Return to DerivationOcl and update the expression with a boundary check for the missing following item.
- Save and test again. Verify that the record with no following ownership record shows an empty purchase date and that other rows show the expected date.
Show DatePurchased in the Person ViewModel
A ViewModel defines the data and controls that Turnkey presents to the user.
- Open the
PersonViewModel. - Locate
HistoricOwnershipGrid. - Add
DatePurchasedto the grid as a ViewModel column. - Save the model and test the web application.
Initially, the derived value may be empty because there is no way to enter DateSold in the grid. The next steps enable that editing.
Make DateSold editable in the grid
A grid cell can be edited only when both conditions are true:
- The grid is configured as editable.
- The relevant column does not have a ReadOnly expression that evaluates to true.
Enable editing on the grid
- In the ViewModel editor, select the blue
HistoricOwnershipnesting header. - Click the Tagged values button.
- In the tagged-values window, click Refresh from Wiki. This loads the predefined tagged-value choices, also called the value store.
- Find the
Editabletagged value. - Add
Editable, set its value toTrue, close the window, and save.
Tagged values are additional metadata that you can apply to model elements such as classes, attributes, and ViewModels. You may define values beyond the predefined list, but predefined values have defined meaning for Turnkey and the WECPOF of the WPF engine.
Allow edits in the DateSold column
- Select the
DateSoldViewModel column inHistoricOwnershipGrid. - Clear its ReadOnlyExpression. An empty expression allows editing; an expression that evaluates to true prevents it.
- Save and test the web application.
The DateSold cells now render as editable date pickers. Add ownership rows if needed, then enter different sale dates. Verify that DatePurchased recalculates as the required DateSold becomes available in the related row.
- Click the
DateSoldcolumn heading to sort the grid and check that the calculated dates match the intended ownership order.
Format DatePurchased as an ISO date
The derived model attribute remains a DateTime?. Format the value at the ViewModel column level when you need a predictable browser-independent display.
- Select the
DatePurchasedViewModel column. - Set its expression to:
self.DatePurchased.ToString('yyyy-MM-dd')
- Save and test the web application.
For example, a date displayed according to a browser locale can vary, while this expression displays it as 2024-06-01.
Clean up editable-grid columns
When you make the grid editable, editable columns can render as edit boxes. The Name column is an example: it does not need to be edited in this ownership grid.
- Select the
NameViewModel column. - Set
IsStatictotrue. - Save and verify that the value renders as plain text rather than an edit box.
- Remove the
Namecolumn from the grid. It always represents the same person in this context. - Remove any other helper or unused columns so the grid contains only useful ownership data.
Add a current-car selector
Model the car a person currently owns, then expose that link in the Person ViewModel.
- On the class diagram, drag an association from class
Carto classPerson. - Name the Car-end
CurrentCar. - Name the Person-end
CurrentOwner. - Open the
PersonViewModel. - Right-click in the green ViewModel tree and choose Add a Nesting ViewModelClass.
- Set the nesting type to
SingleLinkWithSetter. - Select
CurrentCaras the link. - Save and test the web application.
The resulting selector lets the user choose the personâs current car through the CurrentCar association.
Verify your result
Before continuing, verify all of the following:
- The association end has been renamed to
CarsPersonUsedToOwnwith a former name recorded. HistoricOwnership.DatePurchasedis a derivedDateTime?attribute.HistoricOwnershipGridis editable.DateSoldcan be edited through a date picker.DatePurchaseddisplays inyyyy-MM-ddformat when a related sale date exists.- The derivation handles the ownership record that has no following item.
- The Person ViewModel includes a selector for
CurrentCar.
