skip to Main Content

Xplan Xplained

A reference guide for working with Lists

<:=myArticles.extend([‘Lists:’,’a reference guide’]):>

This article takes us through lists: how they work and how even basic use of them could cut your coding + maintenance in half.

Lists are one of the most flexible and versatile data types we come across in Xplan xmerge.

  • Lists are powerful due to the fact that they can contain any number of different data types and objects in the one list;
  • They are a sequence making them iterable (we can loop over them);
  • They are mutable; meaning they are dynamic, we can change and modify them as we go;
  • They are ordered, sortable and sliceable

Whilst you may not have used them explicitly yet or in the fashion we’re going to go into below, invariably many of the things we work with in Xmerge are list objects. Multi fields are probably the simplest example of a list object and when we break it down to .value as we did in this article, we have a pure list.

The article will build up on some of the basics of lists first, before getting into the good stuff which could have a high impact on how you build your documents.  It will then go through the more technical or less used aspects of lists, which are essential for reference material but more often used in complex or custom solutions.

Identifying Lists

From a coding perspective they are identified and defined by:

  • Encapsulated (start and end) with brackets [ ]
  • Each element (or item) in a list is separated by a comma

You can test this by picking a multi field with data in it and accessing the values eg:

<:=$client.client_interests.value:>
# [u’IPO’, u’Music’, u’Tennis’]:>

Note: the u is just identifying these as Unicode objects, when you do something with your list or object these won’t be present, eg if you loop over $client.client_interests.value they won’t appear

Creating Lists

As they are encapsulated by brackets [], we can simply create an empty list via:

<:let myList=[]:>
creates a variable named myList as an empty list

Below is a couple of examples with differing data types so you can see how they are written and as they appear:

<:let myList=['Hello', 'this', 'list','has 4 items to it']:>
<:let myList=[1, 3.14, 99, 'text data etc']:>

Items in the list are stored (or written) in the same manner as you would write them normally, so text or string items are encapsulated with ” as they would be outside of a list.  Main thing is to make sure each item is separated by a comma.

Looping over a list

A list can be looped over the same way as you would be use to:

<:for variable in object:>

Looping over your own variable:

<:let myList=['This', 'List', 'contains', 5, 'items']:>
<:for item in myList:><:=item:><:=end:>
# This List contains 5 items

Likewise for field data, like a multi, we just need to access the direct list sequence, apart from that just the same as above.

<:for item in $client.client_interests.value:>
<:=item:>
<:end:>
# IPO
# Music
# Tennis

So that’s the broad strokes. Below, we’ll look at the way you make life easier with these lists.

The good stuff: iterating over an entity list to stop repetition and cut down code

It was mentioned at the start that lists are powerful because they can contain many different data types – Including entity assignments/objects. So here is a different way to look at how you go about coding, with a key outcome of reducing repetition.

A simple example

<:for entity in [$client, $partner]:>
<:=entity.first_name:> <:=entity.last_name:> (<:=entity.entity_id:>)
<
:end:>

# John Smith (2371)
# Jane Smith (2372)
 

In the above we’ve encapsulated $client and $partner into a list[] so that we can go over both of them with the ‘entity’ variable iterator. The result, is no longer having to switch $client/$partner – making the coding more streamlined for a lot of areas. 

Hopefully even from this simple example lots of potential benefits and ideas of the many places you can use this, might be coming to mind. 

Let’s apply that to something a bit bigger like insurance tables. For instance, you would typically build your documents like this: 

  1. Check to see if client or partner have any insurance
  2. If client insurances
  3. Client insurance table
  4. If partner and partner insurances
  5. Partner insurance table

The end result, is you basically replicating the coding for both $client and $partner with two separate tables of the same thing for each of them. When you do this across everything (assets, income, superannuation, annuities, portfolios, insurance, advice tools, custom groups) it adds up to considerably more work, especially to maintain over time.

Using the list iteration approach you literally cut that in half (at least),  potentially more for some areas depending on how far you go. 

A quick example of how this could be applied to insurance tables is: 

<:for entity in [$client, $partner]:>
<:if len(entity.insurance_by_cover.filter(‘policy_status=Inforce’)):>
<:=entity.preferred_name:> insurance cover

<:for item in entity.insurance_by_cover.filter(‘policy_status=Inforce’):>
<:=item.coy:> - <:=item.plan_name:>
<:end:>
<:end:>
<:end:>

The above would loop through both client and partners insurances (if there are any) and we would only ever need the one table that covers it all. 

Using a basic, but typical insurance table, the two attachments below illustrate the before and after approaches.

Improving the Iteration 

In the above we’re iterating through the $client/$partner entities directly, although we could assign them to a variable and just iterate through that instead.  An example of this would be:

<:let entityList=[$client, $partner] if $client.is_individual and $partner else [$client]:>

Iterating over the above would just be:  <:for entity in entityList:>

The benefits of this approach:

  1. we only need to define it once at the start;
  2. we could add in redundancy for further fidelity or conditioning as needed based on the doc and purpose
  3. Less repetition, we just repeat the variable and any changes to that need only be made once.

We could potentially expand on this a lot taking it to include related entities as well, but that would be very dependent on the document and what we’re looking to output. In large documents it can be more helpful to have second list for when related entities are needed as they won’t be needed in everything or at the same time as client/partner data would be. 

This style coding will always come in handy because it can be used in a standalone document or inside a wizard. 

If I was running this through a wizard that limited the output by Client/Partner/Joint I could also link in with the wizards filter field, in this case $client.document_is_for 

<:let entityList=[$client, $partner] if str($client.document_is_for.text)==’Joint’ and $client.is_individual and $partner else [$partner] if str($client.document_is_for.text)==’Partner’ and $client.is_individual and $partner else [$client]:> 

Related Entities

If you want to loop over all of a clients related entities, for perhaps a portfolio report or many other things, you can use the following coding:

<:for entity in list($superfund)+list($trust)+list($company)+list($partnership):>

Entity Selector

Alternatively, if you are using the entity selector, it’s doing the same thing for you but based on entities selected at the start. Often you will see it written like this:

<:let set = [x for x in $client.scenario.scenario_related_entities]:> 

This is a good example of how taking a second to make your code more readable could make the difference.  The below code is more readable and tells the story better.   

<:let entityList=[entity for entity in $client.scenario.scenario_related_entities]:> 

These small changes not only help with teaching others but make it more likely those supporting the documents (who are often different from those building them) can more easily work out what is expected to happen.

Differentiating Client / Partner / Other

As you expand on this, there are cases where you will want, or need to, differentiate between the client, partner or other entities. To do this use, I would recommend just using the entity id. 

<:if entity.entity_id==$client.entity_id:> or
<:if entity.entity_id<>:$client.entity_id:>
 

This should give you a good basis to start thinking differently about the way you go about coding some sections, particularly with tables.

Accessing items in a list

Each element in a list has a corresponding index number that represents its position in the list.  Using this, is how we can access specific or individual items as can be required.

Note:  Just like with strings the first position is always 0 

Using a scoping list as an example to show the index and corresponding data:

<:let scopeList=[‘Budgeting’,’Personal Insurance’,’Superannuation’,’Wealth Accumulation’]:> 

Index Position

[0] = Budgeting
[1] = Personal Insurance
[2] = Superannuation
[3] = Wealth Accumulation 

We can access one specific item via:

<:=scopeList[0]:>
#Budgeting
<:=scopeList[1]:>

#Personal Insurance

Alternatively, if we were using our client_interests field:

<:=$client.client_interests.value[0]:>
# IPO

Slicing

In many ways slicing with lists is similar to what we covered with slicing strings.  The key difference between strings and lists being with a list the slicing is for whole items not just individual characters.

We’ll use the following list for these examples:
<:let myList=[‘Budgeting’,’Personal Insurance’,’Superannuation’,’Wealth Accumulation’]:>

Code . What it does . Output example
.
<:=myList[start:end]:>

This is the general structure of slicing where the start and end represent the positions you want to slice through

  When slicing the end item is always -1 from the actual index/position.
<:=myList[start:]:>

Returns all the items from the start position nominated through to the end of the list

  <:=myList[2:]:>
# Superannuation, Wealth Accumulation
<:=myList[:end]:>

Returns all the items from the start of the list finishing at the nominated end position (-1)

  <:=myList[:2]:>
# Budgeting, Personal Insurance
     
<:=myList[-1]:>

Returns the last position

  #Wealth Accumulation
<:=myList[-2]:>

Returns the last two position

#Superannuation, Wealth Accumulation
<:=myList[:-1]:>

Returns everything except the last position

#Budgeting, Personal Insurance, Superannuation
<:=myList[0:2]:>

Up to position 2, -1

#Budgeting, Personal Insurance
<:=myList[2:4]:>

From position 2 to 4 (-1, so 3) 

#Superannuation, Wealth Accumulation

Common operations

Operator
.
What it does
.
Code example
.
len

Returns the length (len) of an object.
Short for length, it will count the number of items within the list.  Note lists within a list count as 1 item.

.

  <:=len(myList):>
# 4
in

In is an operator that can be used to check if X exists (in) Y.

If x is found to exist in Y it returns a true value as the condition is met.

Remember to ensure you are comparing like for like.  List items are case sensitive.

 

  <:if ‘Budgeting’ in myList:>
Yes
<:else
No
<:end:>
# Yes
not in

Same as above but checks if X is not in Y

  <:if ‘Budget’ not in myList:>
Concatenation (+)

Can be used to join entire lists together.

Both objects must be lists or list sequences converted so you are + like for like.

  <:let List1=[1,2,3,4]:>
<:let List2=[5,6,7,8]:>
<:let List3=List1+List2:>
# 1,2,3,4,5,6,7,8

List built in methods

We will use the following list for some of these examples
<:let myList=['text0','text1']:>

Method
.
What it does
.
Code example
.
.append()

Appends a single specified object to an existing list.

For multiple objects you will need to add each item, individually or sequentially.

<:=myList.append(‘text2’):>
<:=myList:>
# [‘text0’, ‘text1’, ‘text2’]<:=myList.append(‘text2’, ‘text3’):>
# Err: append takes exactly one argument (2 given)<:=myList.append([‘text2’, ‘text3’]:>
<:=myList:>
# [‘text0’, ‘text1’,[‘text2’, ‘text3’]]
# You can see this creates a list within a list called a nested list

.
.extend() Adds the contents of a sequence to the existing list. This treats every input as a sequence so where .append would have added ‘text2’ as another list item this added each element of it as an item (because strings are sequences)

So when we encapsulate it with the string sequence we get the desired result.

.

<:=myList.extend(‘text2’):>
<:=myList:>
# [text0, ‘text1’ ,’t’,’e’,’x’,’t’,’2’]<:=myList.extend([‘text2’, ‘text3’]:>
<:=myList:>
# [‘text0’, ‘text1’, ‘text2’, ‘text3’]
.
.count(‘x’)

Used to count how many times the item specified at x occurs in the list

<:=myList.extend([‘text2’,’text2’,’text3’]:>
<:=myList.count(‘text2’):>
# 2
<:=myList.count(‘text’):>
# 0
Needs to be specific

.
.index(‘x’)

Returns the lowest index position for the object specified at ‘x’

<:let mylist=[‘item0’,’item1’,’item2’,’item1’]:>
<:=myList.index(‘item1’):>
# Just like slicing with string, list items start at 0.
.
.insert(indexpos, ‘x’)

Inserts the item specified at ‘x’ into the index position (indexpos) stated at the start

<:let myList=[‘text1’, ‘text2’,’text3’]:>
<:=myList.insert(0, ‘text0’):>
<:=myList:>
# [‘text0’,’text1’,’text2’,’text3’]

.
.pop()
.pop(index position)

Removes the last item from a list or removes the item at the specified index position

<:let myList=[‘text0’, ‘text1’,’text2’]:>
<:=myList.pop():>
# [‘text0’, ‘text1’]<:let myList=[‘text1’, ‘text2’,’text3’]:>
<:=myList.pop(1):>
# [‘text0’, ‘text2’]
.
.remove(‘x’)

Removes the first instance of the item ‘x’. Note: duplicates will remain

<:let myList=[‘text0’, ‘text1’,’text2’]:>
<:=myList.remove(‘text1’):>
<:=myList:>
# [‘text0’, ‘text2’]
.
.reverse()

Reverses the list order

<:let myList=[‘text0’, ‘text1’,’text2’]:>
<:=myList.reverse():>
<:=myList:>
# [‘text2, ‘text1’, ‘text0’]
.
.sort()

Will sort the list by basic alphanumeric rules i.e, numbers before letters.

<:let myList=[99, 1, ‘zzz’, ‘abc’]:>
<:=myList.sort():>
<:=myList:>
# [1, 99, ‘abc’, ‘zzz]
.

Concluding thoughts

The practical examples above are just to get you started.  Keep your eye out for other areas where you can end duplication like this in your templates and you should start to notice plenty of areas &  possibilities for lists. 

Over subsequent articles we will add some more so you get a few other ideas.  We’ll also tackle advanced sorting via a separate article to go into more detail around that.

Back To Top