Goal
To get a better understanding of records and repeaters used within a form, as well as the resulting domain data behind them and their impact on each other.
Starting code
Start tag: lesson/09
Requirements
As anyone who’s watched Spider-Man 2 will know, super hero powers are not always 100% reliable. Therefore, looking ahead we’ll need the ability to remove (hopefully temporarily) any of our hero’s super powers and as such, each power should also have a start date associated with it. Initially, the start date can just be set to the date that the super hero is officially registered.
We also want to add an optional "Secret Identity" name fields for the actual person who is behind the masked (or similarly disguised) super hero. This can be a standard (human) individual name using the first, middle and last name format and should come just below the Super Hero Name field.
Steps
Read the view tree documentation, particularly the building the view tree section, which highlight the impact of record and repeater service form items on the domain data, as well as vice-versa – the impact domain data has on them (or the view nodes that result) when a service transaction’s view tree is built using existing data.
Attribute vs. child record?
It takes a little practise and experience to know when you should use a child record instead of an attribute for some data. Always consider the full lifecycle of the registry data when doing so. Not just registration, but view, search and maintenance (and even CTR) as well as it’s often only when considering how the details will be viewed or maintained that the structure of the data becomes clear.
With that said, the following two conditions are good indicators that a child record should be considered over a simple attribute, especially if both are met.
- Does more than one value make up the logical data? e.g. a name may have a first, middle and last component, addresses have multiple fields, etc.
- Can you have more than one of them active at the same time? e.g. super powers, emails, phone numbers, addresses, etc.
Our super powers always satisfied the second condition, but looking ahead to maintain and the requirement for a start date (and likely an end date too) it now also satisfies the first condition as well. You can also see how something like the start or end date might not necessarily jump out until maintain is considered. We should probably always have had these as child records rather than as an attribute that supported multiple values, but we wanted to keep things as simple as possible to start with, but we’ll convert it to a child record now.
Similarly, the individual name, with its multiple values per name satisfies the first condition, so we’ll use a child record for that too.
As an aside, the start (and end) dates could be derived using the various dates associated with service transactions. So instead of keeping the super power within the current registry data when it wanes, we could just remove it from that version. Then, when we do a view and want to show the history we could look back to previously activated versions of the service transactions for that registry data and build up the list of previous super powers, and use one of the service instance dates (maybe the activatedDate?) to indicate when it was lost or gained. This has the benefit of keeping the domain data on the current active version of the registry data smaller, but the complexity and effort around displaying previous values outweighs this. So keeping the previous values on the current version with some flag (i.e. an end date in this case) to indicate it’s not current is the recommended approach in most cases.
Convert super powers
First, we’ll convert the super powers attribute to a child record. We’ll first do this within the HeroDetails component before deciding whether to extract it out into a component in its own right. Hopefully you’ve read the documentation on repeaters by now and know that where the number of records can be more than one at any time, you need a <repeater> element involved. Good.
So we’ll start by adding in a repeater, then we’ll add a record and use a domain name of oh, I don’t know, SuperPower, and finally give it it’s two attributes – the Name and the StartDate. We’ll also remove the "allowMultiple" attribute from the "heroPowers" data constraint too, since we’ll be using the more standard practise of one-value-per-attribute now that separate repeater child records are used for each power. Once done, you should end up with something like the following being added to the HeroDetails component.
<repeater shortCode="superPowers" min="1" max="5" textKeyPrefix="hero.superPowers">
<record domain="SuperPower" arrayName="powers" objectName="power">
<attribute attribute="Name" dataConstraint="heroPowers"/>
<attribute attribute="StartDate"/>
</record>
</repeater>
Leaving it like that, start a new super hero registration and see how it looks. You’ll see the super powers section looking quite different.
Repeater pillbox
OK, now let’s now look at reinstating our new super power repeater-record configuration to it’s former glory in the UI when it was a multi-valued attribute. Perhaps unsurprisingly, we’ll do this using the same widget we used when it was an attribute, the "pillbox" widget, but this time we’ll put it on the repeater. Just adding that widget will get the super powers field looking remarkably similiar to how it did as an attribute, so we’re on the right track! It’s not quite functional yet though (give it a go to confirm), so take a look at the "pillbox" widget documentation to see if you can figure out why?
There’s some configuration required to tell the pillbox how to interact with the repeater children, which can be done using rules, key-values, etc. or a trusty platform tag… <ui:repeater-pillbox/> in this case. Let’s go with that option. Take a look at the documentation for the pillbox tag (or rather using Ctrl + space in Magellan) and see if you can work out options you should specify.
You may need to add the “ui” namespace to the top of the HeroDetails file first if it’s not already there.
Once done the functionality should be identical to how it was when the super powers were just a lowly multi-valued attribute.
<repeater shortCode="heroPowers" min="1" max="5" widget="pillbox" textKeyPrefix="hero.superPowers">
<ui:repeater-pillbox data-constraint="heroPowers" value-attribute="Name"/>
<record domain="SuperPower" arrayName="powers" objectName="power">
<attribute attribute="Name" dataConstraint="heroPowers"/>
<attribute attribute="StartDate"/>
</record>
</repeater>
Start date
There’s also the requirement where a start date is captured against each super power, so we’d better set that up now too. Because we don’t see this attribute on the screen at all, and we don’t need any rules to be triggered when the value is set or anything, we don’t actually need the view node relating to this attribute, so we can set its value right on the domain. This reduces the clutter within the form and view tree. So we’ll set the start date using the <platform:set-domain-attribute> tag.
Also note that the "activate" rule scope comes with an "activatedDate" variable, which is the Java Date object relating to the date and time the service instance was activated. This is useful as it means you can set multiple date values with the exact same time, instead of using the current date and time per one so they all have slightly different values (i.e. by milliseconds).
Your new repeater-based super power configuration should look something like the following.
<repeater shortCode="heroPowers" min="1" max="5" widget="pillbox" textKeyPrefix="hero.superPowers">
<ui:repeater-pillbox data-constraint="heroPowers" value-attribute="Name"/>
<record domain="SuperPower" arrayName="powers" objectName="power">
<platform:set-domain-attribute scope="activate" attribute="StartDate" value-expression="activatedDate"/>
<attribute attribute="Name" dataConstraint="heroPowers"/>
</record>
</repeater>
Clear your data
Now that we’ve changed the structure of the super powers data any super heroes we had already registered previously will not match the current state. If we were in production then we would need to handle updating the existing data to the new format, but since no one’s relying on our current data we can simply remove it all and start fresh. Technically we could just leave the old data as it is and continue with new data too but the functionality and display for the super powers wouldn’t work for the old super heroes and if we forgot we had old data and went to view or use them we might think there’s a bug, so it tends to be better to clear them out.
To clear your local data, you need to stop the Verne server, close Magellan and do a clean start of the Verne server. If you are not sure how to do this, check here.
Update search configuration
Now that the super powers are stored in child domains rather than within a single attribute, we’ll need to update the search index configuration as well as the search form itself to reflect this new data structure.
The search index is relatively straight forward, which is good given it’ll be our first usage of a nested domain (how exciting! ?). The "SuperPowers" attribute will have to be removed and replaced with the following domain configuration.
<domain name="SuperPower">
<attribute name="Name"/>
</domain>
Pretty simple really. Now we need to update the search form to search against this new structure. For this we’ll update some existing and add a few more options to the SearchCriteriaAttribute component relating to the super powers advanced criteria.
Firstly, add a "documentPath" to target the field that is now nested within a domain (or "document" in the search index data). See if you can add the correct value after reading the "documentPath" documentation.
The next change is a bit unexpected and only due to a clash in the search criteria attributes on the form. Both the super hero name, and now the nested super power name use the attribute name "Name". Whilst these don’t clash in the data because they exist on different domains/documents, they’re both specified against the domain relating to the root record in the search form. So if you allow them to both continue to use the attribute name "Name" they will have the same value, and entering a value in one field will be reflected in the other on the form (go on… give it a try, you know you want to ?). So we need to use a different attribute name for one of them. Let’s use "Power" as the attribute name for the super power advanced search criteria attribute instead. Great, the field values are now independent, but we still want to actually query against the super power’s "Name" field in the search index data, so how do we achieve this? We can simply use the "searchAttributes" option.
One final hoop to jump through. Because the super power search criteria still uses the same data constraint that the registration form does, it no longer supports multiple values. The best way to create a data constraint for the search criteria that’s the same as the registration one, but with this one change is to extend the existing data constraint. And since this is specific to our search form we’re going to add the data constraint right within the search form itself. So add the following below the serviceFormItems element in the "heroSearch" form file.
<dataConstraints>
<!-- As per the standard heroPowers data constraint, but allows multiple values as they're added in a single attribute -->
<dataConstraint name="searchHeroPowers" type="string" extends="heroPowers" allowMultiple="true"/>
</dataConstraints>
All done. There were a few little unexpected gotchas in what was essentially a pretty simple search configuration change but this allowed us to highlight a few extra configuration options and practises, so great stuff ?
Add individual name
Now on the the "Secret Identity" portion of the lesson. As mentioned earlier, since this will have three name fields and is optional (it could even change over time) it satisfies the conditions mentioned above for being a child-record instead of attributes on the root domain. So we’ll add a child record to house the name attributes. Fortunately this is made even easier by the use of the aptly named IndividualName component. If you open this component’s file you’ll see it contains the record, the attributes we’re after, plus some other smarts that we’ll assume is what we’re after.
But can we include it just like we have other components, or does it need to be enclosed within a repeater? The answer, or at least the information that will help you determine the answer, can be found in the building the view tree documentation. Specifically the statement: "A record element not enclosed within a repeater element – the record view node will always be created".
If the name was mandatory and we only wanted exactly one then we wouldn’t need the repeater (for registration), but our name is optional, so we’ll need to wrap it in a repeater so the record only gets added when we want to add the name. So even though we don’t need multiple (i.e. more than one) name records (as we did for the super powers above) we still need the repeater to get that flexibility of having none.
So let’s add a repeater element and set the "min" attribute to "0" (optional) and a max of "1" and add the IndividualName component as its child. This should result in configuration similar to that shown below. Have a play around with the Register a Super Hero service in this form seeing how you can add and remove the individual name record from the form. Also try a few of the options defined by the "repeater" widget (e.g. a displayOption value of "inline" or "modal") to see how that impacts the UI, as well as trying some of the repeater widget variations such as the "repeater-table" and "list" widgets.
<repeater shortCode="secretIdentities" min="0" max="1" textKeyPrefix="hero.secretIdentities" widget="list">
<component:IndividualName baseTextKeyPrefix="hero.secretIdentity"/>
</repeater>
Tip: Use the “reload” link in the bottom right to reload the form after you’ve made the minor changes to the repeater. This is quicker than exiting and starting a new one. It reloads the service form configuration but using the same service transaction, including its current domain data.
Repeater add/remove text
Settling on the more basic repeater "list" widget, you can see in the Text Keys section the different text key types and how they’re used by the widget. As you can see there’s an entry for an "add" type that determines the text for the ‘add’ link or button. So we’ll add a "hero.secretIdentities.add" localised text entry with a value of "Add a Secret Identity" so it’s clear what will be added if we select it.
Alternatively, we could use a localised text entry for that standard "label" type for the repeater. This will add a heading above the ‘add’ button, which is another way to indicate what will be added if it is selected. Of course you could do both, it really depends on what the requirements are.
Individual name texts
As per the individual name component documentation there are a number of text entries relating to it. When added we want the main label for the secret identity name to be "Secret Identity", but we’re happy to just use the existing, default texts for the rest. If only there was an easy way to do this? Yep, you guessed it, there is! 🙂 The <text:inherit-text/> tag can be used within a locale element for precisely these scenarios. Add an entry like the following to the heroes en.xml file before reloading the form to see what a difference that makes.
You may need to add the “text” namespace to the top of the en.xml file first if it’s not already there.
<text:inherit-text prefix="hero.secretIdentity" parent-prefix="individualName">
<text:set name="label" text="Secret Identity"/>
</text:inherit-text>
Final check
Check your form behaves as you’d expect and you can do all the things you could before with these new records on the form. Also complete the registrations for a few super heros and check the super power child domains and that they have their start dates all set to the exact same value (which also matches the "activatedDate" property on the service transaction).
One thing you might notice is that if you don’t specify a secret identity during registration, a blank space appears where it might otherwise be when viewing the super hero, as per Fig 9.1 below.

This can easily be remedied with the following <platform:set-property/> tag added to the secret identity repeater which, in the absence of a rule "scope" or "event" option will be triggered on the "instantiate" rule scope as the default.
<platform:set-property visible="false" when-service-mode="View" when-has-active-children="false"/>
Now view your super heroes again – at least one with a secret identity and one without – and see that this fixes the issue. Perfect.
And that brings us to the end of another nail biting lesson. Well done.
Add tests
Create new tests for search and view services. Understand how to:
- Randomize tests
- Create multiple scenarios using 1 single test via outlines
- Use random variables
- Automate Search and View services
- Filing History record
This can be done by following the steps outlined in lesson 4 of the test training . Also additional lesson ad-hoc to this one can be follow at Lesson5 Upload files and repeaters
Wrap up
This lesson was all about introducing the concept of child records, with or without repeaters, that allow us to build up vast and complex domain data structures. So let’s summarise quickly.
- Record and repeater documentation and their impact on what view nodes are added when building the view tree
- Some basic rules of thumb on how to determine whether a record should be used for any particular form details, as well as when they should be enclosed within a repeater element
- How to configure the pillbox functionality for a repeater
- Using the
<platform:set-domain-attribute/>tag to set values onto the domain without the need for a view node, and using a variable value within the given rule scope to set the domain attribute to - Adding nested domains to search configuration and adding search criteria to query their fields
- Outlined the repeater text key types and their usages
- The IndividualName component and using the
<text:inherit-text/>tag to easily configure a bunch of text keys that are mainly the same as existing ones that have a common prefix

