Getting started (Part 2) - Working with lists

Overview

In the previous tutorial, we covered simple and complex types as well as building basic function logic. A key area that was not touched upon is that of the ListTYP .


Please note the terms ‘Process’ and ‘Custom Type’ have been depreciated and have been replaced with ‘Function’ and ‘Type’ respectively. More details here.

The explanations are mostly for Linx5 users. We`ve included a sample for Linx6 users.

Feel free to contact support@linx.software and we'll assist.

What do you need?

The list type is something you’ll encounter throughout your application development and can be very useful. The list type behaves differently from the basic types that we explored earlier.

A list is just a collection of related “things” or “items” or in coding terms an “array”;

Key concepts of a list in Linx:

  • A list has a type i.e. List<String>, List<Integer>, List<CustomType>
  • A list is stored as a JSON string
  • Individual Items are added to a list using a AddToListFNC.
  • A whole list can be set at once using a SetValueFNC , as long as the referenced data matches the structure of the list type.
  • Individual items can be extracted by referencing the index of the item.
  • To access individual items you need to loop through a list using a ForEachFNC or a DoWhileFNC

A list’s type

List contain items, and these items must all conform to the same structure and this structure must conform to a basic or user-defined type. The type of list indicated by List<TypeName> i.e. List<String> or List<Project.customType>.

When defining a list type, like the others, you are able to define it locally within the scope of the function by dragging a list type from the plugins panel onto the canvas. You can then select the type of list from the drop down in the properties panel.

image

When defining a list as an input parameter or output result, you first need to select the type as ‘List’.

image

This will default the type of list as List<String>.
image

If you want to alter the list’s type, use the type editor image and select the appropriate type.

image

image

All items in the list must conform to the same structure as the type of list.

You are also able to set the type of list to a user-defined type such as ‘customer’:
image

Tutorial steps

Resources

Open the provided sample application in your Linx Designer to help along your learning journey:
Linx5 - Solution.lsoz (20.4 KB)
Linx6 - Linx6.zip (28.9 KB)

Setting a list’s initial values

When working with a list type defined within the scope of a function, you are able to add initial item values by expanding the value property. This will open up the list editor to which you can add your initial values.

To give you an idea, we’re going to create a list of customer names.

Add a new function to your application and give it the name of ‘GenerateCustomerList’.

Drag a list type from the plugins panel onto the canvas, this will create an empty instance of a list.

Rename the list to ‘ListOfCustomerNames’.

image

For the type of list, leave it as List<String> for now.

Now we are going to add a few ‘customer names’ to the list as initial values. This is done by expanding the value property and adding the initial values one by one.

image

The values will be stored as a JSON string:
image

When you debug the ‘GenerateCustomerList’ function, you will see all the items in the ‘ListOfCustomerNames’ list in the debug values panel:

Even though all the items are visible in the debug values panel, you are unable to access specific items within drop down references and only the whole list:

image

Iterating through list values

In order to access specific list items, you need to iterate through the list item by item.

This is achieved using a ForEachFNC . This function allows you to loop through lists and for each item in the list, there is a execution path with its own scope.

To demonstrate, add a ForEach function to the ‘GenerateCustomerList’ function and rename it ‘ForEach_Customer’. For the list to loop through, reference the ‘ListOfCustomerNames’ list:

image

Now add a string type within the execution path of the loop and rename it to ‘CurrentCustomerName’. When a function or type is placed within the loop , it has access to whatever item is currently being iterated through. To access this value, reference the current for each’s loop value like below:

image

However, any functions or types included in the loop will only exist within the scope of that particular loop.

To demonstrate, debug the ‘GenerateCustomerList’ function and step over into the loop:

You will see that the ‘CurrentCustomerName’ exists only within the scope of the current loop.

To demonstrate the scope concept more, we are going to add a type to hold the last name in the list. The ‘LastCustomerName’ will be initialized above the ForEach function so that it is within scope.

Then, inside of the loop, we are going to use a SetValue to alter the value of ‘LastCustomerName’ to whatever the current loops value is. When the loop completes, the last name in the list will remain as the value of ‘LastCustomerName’.

When the loop completes, the ‘LastCustomerName’ type still exists within the scope of the function unlike ‘CurrentCustomerName’:

Adding to a list

In order to add items to a list dynamically within the function you need to make use of the AddToListFNC . This adds a single items to the list.

To demonstrate, lets build up a list of customer’s first names.

We can achieve this by adding a new list type to the ‘GenerateCustomerList’ function to the beginning of the function so that it is within scope of the ForEach.

Next, within the loop of the ForEach, we want to add the current customer’s first name to our new list. This can be done by adding an AddToList function within the loop.

image

The first name is extracted from the current loops value by performing a string extraction method that extracts the characters up until the first occurance of a " " character in the name.

=ForEach_Customer.Loop.Substring(0, ForEach_Customer.Loop.IndexOf(" "))

The items are added one by one and when the function completes there is a list with customer names and with just their first names.

["Peter","Stacy","Matthew"]

Creating our own loop

We’ve already seen how to iterate through a list using a ForEach function which exposes each item as a data object. Lets explore using a DoWhileFNC to create a loop execution branch which will continue to execute based off a condition.

In our case, we can use it to loop up until the length of the list, for each iteration, we will store a count variable in the form of an integer type. This integer type will have an initial value of 0 since list indexes start at 0.

Our condition will check if the value of ‘ItemCount’ is less than the total count of values in the ‘ListOfCustomerFirstNames’ list.

= ItemCount < ListOfCustomerFirstNames.Count()

We can then reference the current item by reference the count variable as the index of the item.

Now we need to increase our ‘ItemCount’ variable as we have performed a loop. Otherwise our condition would never be fail and the loop would continue to run infinitely.

To do this we can use a SetValue function to the the value of ‘ItemCount’ to itself plus one.

The expression will take the current value of ‘ItemCount’ and add 1. When this value reaches the total count of items in the list i.e. 3, then the loop will stop.

Learn:

More details on referencing list items by index can be found here

Updating list values

Often, you are looping through a list and want update the current loop’s item. Updating a list with a basic type in Linx involves building a new list up and then assigning the original list to this value.

To demonstrate, we’re going to “update” the ‘ListOfCustomerFirstNames’ list and transform all the items to uppercase.

To do this, we first need to create a “temporary” list to store the new values. Then, for each loop of the DoWhile function, we need to add the current value of the item at the index of the ‘ItemCount’ which can be taken from ‘CurrentCustomerFirstName’. We then need to perform a text transformation to upper case using the .ToUpper() method.

Now we need to assign the ‘TempList’ to the ‘ListOfCustomerFirstNames’. As these lists are of the same type, we can assign the value of ‘TempList’ to ‘ListOfCustomerFirstNames’ using a SetValue function. This will “overwrite” the current values of the list.

When you debug the function now, you should see the temporary list being built up like below:

When the function completes, the values of the temporary list will be assigned to the ‘ListOfCustomerFirstNames’.

Lists of a user defined type

So far, we have dealt with lists of the basic types like string and integer. With Linx, you are also able to create a list whose items conform to a user-defined type. This allows you to store multi-level data objects in a list and access the different properties.

When a list has its type set as a user-defined type, it behaves the same way as basic list types in terms of loop, setting and adding items.

image

To give you a practical idea of working with custom type lists, we are going to create a function that will initially have a list of ‘customer’ items with some details missing. For each ‘customer’ item we will make a call to the ‘GetCustomerDetails’ function we created in the previous tutorial, passing in the required input parameters. When a customer is returned from the function, we will update our customer list with the new details.

Lets add a new function to our application and give it the name of ‘GenerateCustomerDetailsList’.

Then we are going to define a list of the type ‘customer’.

image

Add some ‘customers’ to the list, you only need to fill out the first name, last name , birthday and balance fields, the rest will be calculated and returned from our sub-function. When adding items to lists, take not of the levels of objects when you expand the field editor.

image

Because user types are structured objects, each level needs to be set using the field editor image which will expose the fields of that level.

image

When you debug the function, you will see nested ‘customer’ types within the list:

Now we are going to iterate through the list of customers using a ForEach function.

For every customer object, we are going to call the ‘GetCustomerDetails’ custom user function.

This is done by dragging the ‘GetCustomerDetails’ function from the solution explorer onto the canvas, within the execution path of the loop.

For the input parameters, reference the current loop’s appropriate fields.

If you debug the ‘GenerateCustomerDetailsList’ notice how our ‘customer’ is “enriched” and returned:

Lets now “update” our customer’s list, like before, we need to create and build up a temporary list of the type ‘customer’.

image

We then need to add the details returned from the ‘GetCustomerDetails’ function to the temp list and then assign the temp list to the ‘ListOfCustomers’.

When you now debug the function, the ‘ListOfCustomers’ will be updated with the new details:

Nested lists

In Linx, a list can be made of items of a complex type containing basic types. This complex type can also contain a sub-list. That is to say a list can contain items, and those items can also contain list and so on.

When a list is nested in an custom type it needs to be iterated through just like a normal list. If this custom type is then also contained in a list, to access list items in the lower level list, you will first need to iterate through the top list, then for each item in the list iterate through the sub-list.

To demonstrate, we are going to add a list field to the ‘customer’ type. This will be a list containing text “tags” related to the customer. So each ‘customer’ will also contain a list specific to that customer.

First, we need to alter the structure of our ‘customer’ type to contain a list of the type List<String>.

Once updated, if you go back into the ‘GenerateCustomerDetailsList’ function and expand the items in the ‘ListOfCustomers’, you will see Linx has added the field for you to work with:
image

You can then add nested list items:

However, no initial values are needed for the ‘tags’ list as we are going to modify the ‘GetCustomerDetails’ function to create a list of tags for us.

Go back to the ‘GetCustomerDetails’ function and an IfElse function to make some decisions.

image

image

If the customer is a pensioner then we will add the tag “pensioner” to the result ‘customer’, if the account is in debt then we will add the tag “overdue”.

Because both of these conditions might be met, we need to configure the IfElse function to continue evaluating even when a condition is met, this is done by unchecking the stop when true property:
image

Now we are going to add the relevant tag as an an item as the result customer.tags list which can be done with an AddToList function like before.

However, as we are setting a field of ‘customer’ before setting the whole ‘customer’ object this will throw and error:
image

When dealing with multi-leveled custom types, when setting them, you first need to instantiate the whole custom type with a SetValue and reference the highest level object. Using the field editor you can then set the specific fields. If you then want to modify a specific field only using a SetValue then this will have to be done after the whole object has been set.

In our case, move the ‘SetValue_Result’ function above the ‘IfElse’.

Now, the whole ‘customer’ result object is set first and then the tags are added:

In the’ GenerateCustomerDetailsList’ function we now have a list of ‘customer’ items which contains a nest list of ‘tags’ per each ‘customer’.

[
   {
      "first_name":"Matthew",
      "last_name":"JENKINS",
      "full_name":"Matthew Jenkins",
      "birthday":"1956-01-10T00:00:00",
      "age":64,
      "is_pensioner":false,
      "account_balance":-5000.0,
      "monthly_fee":-50.0,
      "tags":[
         "overdue"
      ]
   },
   {
      "first_name":"Stacy",
      "last_name":"JOHNSON",
      "full_name":"Stacy Johnson",
      "birthday":"1990-02-23T00:00:00",
      "age":30,
      "is_pensioner":false,
      "account_balance":3000.0,
      "monthly_fee":30.0,
      "tags":[
         
      ]
   },
   {
      "first_name":"Jeffery",
      "last_name":"HIGGERS",
      "full_name":"Jeffery Higgers",
      "birthday":"1912-04-09T00:00:00",
      "age":108,
      "is_pensioner":true,
      "account_balance":34005.0,
      "monthly_fee":340.05,
      "tags":[
         "pensioner"
      ]
   },
   {
      "first_name":"Jacob",
      "last_name":"REEVES",
      "full_name":"Jacob Reeves",
      "birthday":"1921-03-03T00:00:00",
      "age":99,
      "is_pensioner":true,
      "account_balance":-560.55,
      "monthly_fee":-5.6055,
      "tags":[
         "pensioner",
         "overdue"
      ]
   }
]

Lets say we now want to access these nested ‘tag’ items.

To do this, we first need to loop through the updated list i.e. ‘ListOfCustomers’.

For each ‘customer’ object we need to loop through the customer.tagslist. This can be done by nesting another ForEach function, within the loop execution path of the top level ForEach function. You will then be able to access that particular item’s fields and nested list values.

image

However, like before, any logic that is defined within a loop only exists in that loop. In order to maintain ‘states’ , you need to add “global” types to your function by placing them above the particular looping logic.

To demonstrate, we are going to build up a string per customer which will contain the customer name (first level loop) and then add on the tags per customer (second loop).

To do this, we need to declare a string type within the loop of the ‘ForEach_CustomerDetail’ but above the execution of the nested ForEach function, this way, logic within the nested ForEach can access the string that was declared.

This string will be built from an expression like below:

="The customer " + ForEach_CustomerDetail.Loop.full_name + " has the following tags: " 

This means that each loop of the first level loop will re-initiate this variable with the current customers name.

Now we want to add whatever tags are part of the ‘customer’ to this ‘CustomerMessage’. This can be done by adding a SetValue function within the nested ForEach loop:

In the above expression, the value of ‘CustomerMessage’ is set as itself with the current tag appended to the string.

Lets say, we now want to add this summary to a ‘customer’ type just for demonstrations sake. To do this, we can just add field called ‘customer_message’ to the ‘customer’ type.

It was mentioned earlier that to update a list you need to rebuild a list and then reassign it to the original list, however this is not completely accurate. Because of the way data is structured, you’re unable to update a whole list item object, however, you are able to updated any nested properties. This is done by using a SetValue to set the value of the the ForEach.Loop.fieldname inside the actual loop.

To demonstrate, we are going to update the value of ‘customer.customer_message’ tobe our local string type after all the tags have been looped through. We can add a SetValue function below the scope of the ‘ForEach_Tag’ loop but still within the ‘ForEach_CustomerDetail’ loop:

Now after the ‘CustomerMessage’ string has been built up from the list of tags, its added to the main ‘customer’ object.

image

Now lets return the whole customer list as an output result of the ‘GenerateCustomerDetailsList’.

image

Next steps?

Well done! :fire: hopefully you now have a better understanding and are more comfortable using the list type in Linx.

In the next tutorial we will explorer working with the FilePLG . Files, how to read and write to them and perform directory operations among other things. We will extend our application built so far to incorporate the ‘customer’ functionality with file operations.